Lua에서 neovim 플러그인을 작성하는 방법

neovim 개발자가 자신을 위해 설정한 목표 중 하나는 루아를viml의 첫 번째 스크립트 언어 대체품으로 만드는 것이다.버전 0.4 이후, 그것의'해석기'와'stdlib'는 편집기에 내장되어 있습니다.Lua는 배우기 쉽고 속도가 빨라서gamedev 커뮤니티에서 널리 사용된다.내가 보기에 그것의 학습 곡선도viml보다 훨씬 낮다. 이것은 초보자들이 그들의 여정을 시작하도록 격려하고neovim의 기능을 확장하거나 일회성 목적으로만 간단한 스크립트를 만들 수 있다.그럼 한번 해볼까요?우리가 최근에 처리한 파일을 표시하기 위해 간단한 플러그인을 작성합시다.우리는 어떻게 명명해야 합니까?대개"내가 뭘 했어?!"그것은 보기에 이렇다.

플러그인 디렉터리 구조


우리의 플러그인은 적어도 두 개의 디렉터리가 있어야 한다. plugin 우리는 그것의 메인 파일과 lua 전체 코드 라이브러리를 가지고 있다.물론, 만약 우리가 정말 그것을 원한다면, 우리는 모든 것을 하나의 서류에 넣을 수 있지만, 이 녀석(또는 소녀)이 되지 마세요.그래서plugin/whid.vimlua/whid.lua가능합니다.우리는 다음과 같은 측면에서 착수할 수 있다.
" in plugin/whid.vim
if exists('g:loaded_whid') | finish | endif " prevent loading file twice

let s:save_cpo = &cpo " save user coptions
set cpo&vim " reset them to defaults

" command to run our plugin
command! Whid lua require'whid'.whid()

let &cpo = s:save_cpo " and restore after
unlet s:save_cpo

let g:loaded_whid = 1
let s:save_cpo = &cpo 플러그인을 방해하는 사용자 정의 coptions (단일 문자 플래그 시퀀스) 를 방지하는 일반적인 방법입니다.우리 자신의 목적상 이 줄이 부족하면 나쁘지 않을 수도 있지만, 이것은 좋은 방법으로 여겨진다. (적어도vim 도움말 파일에 따라)그리고 command! Whid lua require'whid'.whid() 플러그인이 필요한 루아 모듈과 주요 기능을 호출합니다.

부동 창


좋아, 우리 재미있는 일부터 시작합시다.우리는 물건을 전시할 수 있는 곳을 만들어야 한다.고맙습니다.neovim(지금도vim)은 부동창이라고 불리는 깔끔한 특성을 가지고 있습니다.운영 체제에 있는 것처럼 다른 창 위에 표시되는 창입니다.
-- in lua/whid.lua

local api = vim.api
local buf, win

local function open_window()
  buf = api.nvim_create_buf(false, true) -- create new emtpy buffer

  api.nvim_buf_set_option(buf, 'bufhidden', 'wipe')

  -- get dimensions
  local width = api.nvim_get_option("columns")
  local height = api.nvim_get_option("lines")

  -- calculate our floating window size
  local win_height = math.ceil(height * 0.8 - 4)
  local win_width = math.ceil(width * 0.8)

  -- and its starting position
  local row = math.ceil((height - win_height) / 2 - 1)
  local col = math.ceil((width - win_width) / 2)

  -- set some options
  local opts = {
    style = "minimal",
    relative = "editor",
    width = win_width,
    height = win_height,
    row = row,
    col = col
  }

  -- and finally create it with buffer attached
  win = api.nvim_open_win(buf, true, opts)
end
이 파일의 맨 위에서, 우리는 가장 높은 범위 내에서 winbuf 변수를 정의했는데, 이것은 보통 다른 함수에 인용된다.비어 있습니다. 이 때, 버퍼는 우리가 결과를 놓을 곳입니다.나열되지 않은 버퍼 (첫 번째 매개 변수) 와 임시 버퍼 (두 번째 매개 변수, 참조 :h scratch-buffer 로 생성됩니다.숨길 때 삭제bufhidden = wipe로 설정합니다.nvim_open_win(buf, true, opts)를 사용하여 새 창을 만들고 이전에 만든 버퍼를 이 창에 연결합니다.두 번째 논점은 새로운 창을 초점으로 만들었다.widthheight는 모두 말하지 않아도 안다.rowcol 는 편집기relative = "editor"의 왼쪽 상단에서 계산된 창의 시작 위치입니다.style = "minimal" 창 모양을 구성하는 데 편리한 옵션입니다. 여기에서 줄 번호나 맞춤법 오류 강조 표시와 같은 불필요한 옵션을 사용하지 않습니다.
지금 우리는 부동 창이 생겼지만, 우리는 그것을 더욱 좋아 보일 수 있다.Neovim은 현재 border 같은 작은 위젯을 지원하지 않기 때문에 우리가 직접 만들어야 합니다.이것은 매우 간단하다.우리는 첫 번째보다 조금 큰 다른 부동창이 필요하다. 그 밑에 놓아라.
local border_opts = {
  style = "minimal",
  relative = "editor",
  width = win_width + 2,
  height = win_height + 2,
  row = row - 1,
  col = col - 1
}
우리는 네모난 그림 문자로 그것을 채울 것이다.
local border_buf = api.nvim_create_buf(false, true)

local border_lines = { '╔' .. string.rep('═', win_width) .. '╗' }
local middle_line = '║' .. string.rep(' ', win_width) .. '║'
for i=1, win_height do
  table.insert(border_lines, middle_line)
end
table.insert(border_lines, '╚' .. string.rep('═', win_width) .. '╝')

api.nvim_buf_set_lines(border_buf, 0, -1, false, border_lines)
-- set bufer's (border_buf) lines from first line (0) to last (-1)
-- ignoring out-of-bounds error (false) with lines (border_lines)
물론 우리는 정확한 순서에 따라 창문을 열어야 한다.그리고 하나 더.두 창문은 시종 함께 닫아야 한다.만약 첫 번째 폐쇄 이후에도 국경이 여전히 존재한다면, 그것은 매우 이상할 것이다.현재viml 자동 명령이 가장 좋은 해결 방안이다.
local border_win = api.nvim_open_win(border_buf, true, border_opts)
win = api.nvim_open_win(buf, true, opts)
api.nvim_command('au BufWipeout <buffer> exe "silent bwipeout! "'..border_buf)

데이터 가져오기


우리의 플러그인은 우리가 처리한 최신 파일을 표시하는 데 목적을 두고 있다.우리는 간단한git 명령을 사용하여 이 점을 실현할 것이다.유사:
git diff-tree --no-commit-id --name-only -r HEAD
함수를 만듭니다. 함수는 예쁜 창에 데이터를 넣을 것입니다.우리는 그것을 자주 호출할 것이다. 따라서 우리는 그것을 명명할 것이다 update_view.
local function update_view()
  -- we will use vim systemlist function which run shell
  -- command and return result as list
  local result = vim.fn.systemlist('git diff-tree --no-commit-id --name-only -r HEAD')

  -- with small indentation results will look better
  for k,v in pairs(result) do
    result[k] = '  '..result[k]
  end

  api.nvim_buf_set_lines(buf, 0, -1, false, result)
end
은마르코프 모형...만약 우리가 현재 파일만 찾을 수 있다면, 그것은 그다지 불편하다.보기를 업데이트하기 위해 이 함수를 직접 호출할 것이기 때문에, 파라미터를 받아들이고, 비교적 오래되거나 새로운 상태를 표시할 것인지에 대한 정보를 제공해야 합니다.
local position = 0

local function update_view(direction)
  position = position + direction
  if position < 0 then position = 0 end -- HEAD~0 is the newest state

  local result = vim.fn.systemlist('git diff-tree --no-commit-id --name-only -r  HEAD~'..position)

  -- ... rest of the code 
end
너는 여기에 또 무엇이 부족한지 아니?우리 플러그인의 제목입니다.가운데에 있었어!이 함수는 우리가 일부 텍스트를 중간에 두는 것을 도울 것이다.
local function center(str)
  local width = api.nvim_win_get_width(0)
  local shift = math.floor(width / 2) - math.floor(string.len(str) / 2)
  return string.rep(' ', shift) .. str
end
  api.nvim_buf_set_lines(buf, 0, -1, false, {
      center('What have i done?'),
      center('HEAD~'..position),
      ''
    })
end
포인트가 좋을 거예요.우리는 몇 가지 옵션을 선택할 수 있다. 사용자 정의 문법 파일 (줄 번호에 따라 패턴을 일치시킬 수 있음) 을 정의하거나 일반 텍스트 대신 가상 텍스트 주석을 사용하거나.위치 기반 강조 표시nvim_buf_add_highlight를 사용할 수 있습니다.그러나 우선, 우리는 우리의 하이라이트를 발표해야 한다.색상을 설정하는 대신 기존 하이라이트 그룹에 연결합니다.이렇게 하면 사용자 색상과 일치합니다.
" in plugin/whid.vim after set cpo&vim

hi def link WhidHeader      Number
hi def link WhidSubHeader   Identifier
이제 강조 표시를 추가합니다.
api.nvim_buf_add_highlight(buf, -1, 'WhidHeader', 0, 0, -1)
api.nvim_buf_add_highlight(buf, -1, 'WhidSubHeader', 1, 0, -1)
우리는 그룹화되지 않은 하이라이트 buf 로 버퍼 -1 에 하이라이트를 추가합니다.우리는 이 곳에서 이름 공간 id를 전달할 수 있습니다. 이것은 그룹의 모든 강조 표시를 한 번에 제거할 수 있지만, 우리의 경우, 우리는 이렇게 할 필요가 없습니다.다음은 줄 번호입니다. 마지막 두 개의 매개 변수는 시작과 끝 (바이트 인덱스) 열 범위입니다.
전체 함수는 다음과 같습니다.
local function update_view(direction)
  -- Is nice to prevent user from editing interface, so
  -- we should enabled it before updating view and disabled after it.
  api.nvim_buf_set_option(buf, 'modifiable', true)

  position = position + direction
  if position < 0 then position = 0 end

  local result = vim.fn.systemlist('git diff-tree --no-commit-id --name-only -r  HEAD~'..position)
  for k,v in pairs(result) do
    result[k] = '  '..result[k]
  end

  api.nvim_buf_set_lines(buf, 0, -1, false, {
      center('What have i done?'),
      center('HEAD~'..position),
      ''
  })
  api.nvim_buf_set_lines(buf, 3, -1, false, result)

  api.nvim_buf_add_highlight(buf, -1, 'WhidHeader', 0, 0, -1)
  api.nvim_buf_add_highlight(buf, -1, 'whidSubHeader', 1, 0, -1)

  api.nvim_buf_set_option(buf, 'modifiable', false)
end

사용자 입력


이제 우리는 플러그인을 상호작용을 가지게 해야 한다.현재 미리 보기의 상태를 변경하거나 파일을 선택하고 열 수 있는 간단한 기능만 있습니다.우리의 플러그인은 맵을 통해 사용자의 입력을 받을 것입니다.키를 누르면 동작을 촉발합니다.LuaAPI에서 매핑을 정의하는 방법을 살펴보겠습니다.
api.nvim_buf_set_keymap(buf, 'n', 'x', ':echo "wow!"<cr>', { nowait = true, noremap = true, silent = true })
첫 번째 매개 변수는 보통 버퍼입니다.이 매핑의 범위는 그것으로 한정될 것이다.다음은 모드의 짧은 이름입니다.우리는 정상 모드 n 에서 우리의 모든 맵을 정의했다.그리고 "오른쪽"키 조합에 비추는 "좌"키 조합 (나는 예시로 x 을 선택합니다) (neovim에게 명령행 모드에 들어가서 viml을 입력하고 enter<cr>를 누르라고 알려줍니다.)마지막으로 선택의 여지가 있다.우리는neovim이 일치하는 모드에서 바로 맵을 터치하기를 원합니다. 따라서 다른 맵 nowait 을 통해 맵을 터치하지 않고 사용자에게 입력 noremap 을 표시하지 않도록 로고를 설정합니다.이것은 매우 긴 선이기 때문에 우리는 몇 개의 쓰기 조작을 절약하기 위해 수조를 사용한다.
local function set_mappings()
  local mappings = {
    ['['] = 'update_view(-1)',
    [']'] = 'update_view(1)',
    ['<cr>'] = 'open_file()',
    h = 'update_view(-1)',
    l = 'update_view(1)',
    q = 'close_window()',
    k = 'move_cursor()'
  }

  for k,v in pairs(mappings) do
    api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"whid".'..v..'<cr>', {
        nowait = true, noremap = true, silent = true
      })
  end
end
사용하지 않은 키를 비활성화할 수도 있습니다.
local other_chars = {
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
}
for k,v in ipairs(other_chars) do
  api.nvim_buf_set_keymap(buf, 'n', v, '', { nowait = true, noremap = true, silent = true })
  api.nvim_buf_set_keymap(buf, 'n', v:upper(), '', { nowait = true, noremap = true, silent = true })
  api.nvim_buf_set_keymap(buf, 'n',  '<c-'..v..'>', '', { nowait = true, noremap = true, silent = true })
end

공공 기능


좋습니다. 하지만 맵에 새로운 함수가 언급되었습니다.그것들을 좀 봅시다.
local function close_window()
  api.nvim_win_close(win, true)
end

-- Our file list start at line 4, so we can prevent reaching above it
-- from bottm the end of the buffer will limit movment
local function move_cursor()
  local new_pos = math.max(4, api.nvim_win_get_cursor(win)[1] - 1)
  api.nvim_win_set_cursor(win, {new_pos, 0})
end

-- Open file under cursor
local function open_file()
  local str = api.nvim_get_current_line()
  close_window()
  api.nvim_command('edit '..str)
end
우리의 서류 명세서는 매우 간단하다.우리는 커서 밑에 한 줄만 그려서 네비엠에게 편집하라고 알려주면 된다.물론 우리는 더욱 복잡한 메커니즘을 세울 수 있다.우리는 줄 번호 (심지어 열) 를 얻어서 그것에 따라 특정한 조작을 촉발할 수 있다.그것은 보기와 논리를 분리할 수 있게 할 것이다.하지만 우리의 목적으로는 충분하다.
그러나 만약 우리가 먼저 이 함수를 내보내지 않는다면, 그 중의 어떤 함수도 호출할 수 없다.파일의 밑에 공공 사용 가능한 함수가 있는 관련 그룹을 되돌려줍니다.
return {
  whid = whid,
  update_view = update_view,
  open_file = open_file,
  move_cursor = move_cursor,
  close_window = close_window
}
물론 주요 기능도 있습니다!
local function whid()
  position = 0 -- if you want to preserve last displayed state just omit this line
  open_window()
  set_mappings()
  update_view(0)
  api.nvim_win_set_cursor(win, {4, 0}) -- set cursor on first list entry
end

전체 플러그인...


... 약간의 개선을 통해 (평론을 찾아서 그것들을 찾을 수 있다.)
" plugin/whid.vim
if exists('g:loaded_whid') | finish | endif

let s:save_cpo = &cpo
set cpo&vim

hi def link WhidHeader      Number
hi def link WhidSubHeader   Identifier

command! Whid lua require'whid'.whid()

let &cpo = s:save_cpo
unlet s:save_cpo

let g:loaded_whid = 1
-- lua/whid.lua
local api = vim.api
local buf, win
local position = 0

local function center(str)
  local width = api.nvim_win_get_width(0)
  local shift = math.floor(width / 2) - math.floor(string.len(str) / 2)
  return string.rep(' ', shift) .. str
end

local function open_window()
  buf = api.nvim_create_buf(false, true)
  local border_buf = api.nvim_create_buf(false, true)

  api.nvim_buf_set_option(buf, 'bufhidden', 'wipe')
  api.nvim_buf_set_option(buf, 'filetype', 'whid')

  local width = api.nvim_get_option("columns")
  local height = api.nvim_get_option("lines")

  local win_height = math.ceil(height * 0.8 - 4)
  local win_width = math.ceil(width * 0.8)
  local row = math.ceil((height - win_height) / 2 - 1)
  local col = math.ceil((width - win_width) / 2)

  local border_opts = {
    style = "minimal",
    relative = "editor",
    width = win_width + 2,
    height = win_height + 2,
    row = row - 1,
    col = col - 1
  }

  local opts = {
    style = "minimal",
    relative = "editor",
    width = win_width,
    height = win_height,
    row = row,
    col = col
  }

  local border_lines = { '╔' .. string.rep('═', win_width) .. '╗' }
  local middle_line = '║' .. string.rep(' ', win_width) .. '║'
  for i=1, win_height do
    table.insert(border_lines, middle_line)
  end
  table.insert(border_lines, '╚' .. string.rep('═', win_width) .. '╝')
  api.nvim_buf_set_lines(border_buf, 0, -1, false, border_lines)

  local border_win = api.nvim_open_win(border_buf, true, border_opts)
  win = api.nvim_open_win(buf, true, opts)
  api.nvim_command('au BufWipeout <buffer> exe "silent bwipeout! "'..border_buf)

  api.nvim_win_set_option(win, 'cursorline', true) -- it highlight line with the cursor on it

  -- we can add title already here, because first line will never change
  api.nvim_buf_set_lines(buf, 0, -1, false, { center('What have i done?'), '', ''})
  api.nvim_buf_add_highlight(buf, -1, 'WhidHeader', 0, 0, -1)
end

local function update_view(direction)
  api.nvim_buf_set_option(buf, 'modifiable', true)
  position = position + direction
  if position < 0 then position = 0 end

  local result = vim.fn.systemlist('git diff-tree --no-commit-id --name-only -r  HEAD~'..position)
  if #result == 0 then table.insert(result, '') end -- add  an empty line to preserve layout if there is no results
  for k,v in pairs(result) do
    result[k] = '  '..result[k]
  end

  api.nvim_buf_set_lines(buf, 1, 2, false, {center('HEAD~'..position)})
  api.nvim_buf_set_lines(buf, 3, -1, false, result)

  api.nvim_buf_add_highlight(buf, -1, 'whidSubHeader', 1, 0, -1)
  api.nvim_buf_set_option(buf, 'modifiable', false)
end

local function close_window()
  api.nvim_win_close(win, true)
end

local function open_file()
  local str = api.nvim_get_current_line()
  close_window()
  api.nvim_command('edit '..str)
end

local function move_cursor()
  local new_pos = math.max(4, api.nvim_win_get_cursor(win)[1] - 1)
  api.nvim_win_set_cursor(win, {new_pos, 0})
end

local function set_mappings()
  local mappings = {
    ['['] = 'update_view(-1)',
    [']'] = 'update_view(1)',
    ['<cr>'] = 'open_file()',
    h = 'update_view(-1)',
    l = 'update_view(1)',
    q = 'close_window()',
    k = 'move_cursor()'
  }

  for k,v in pairs(mappings) do
    api.nvim_buf_set_keymap(buf, 'n', k, ':lua require"whid".'..v..'<cr>', {
        nowait = true, noremap = true, silent = true
      })
  end
  local other_chars = {
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
  }
  for k,v in ipairs(other_chars) do
    api.nvim_buf_set_keymap(buf, 'n', v, '', { nowait = true, noremap = true, silent = true })
    api.nvim_buf_set_keymap(buf, 'n', v:upper(), '', { nowait = true, noremap = true, silent = true })
    api.nvim_buf_set_keymap(buf, 'n',  '<c-'..v..'>', '', { nowait = true, noremap = true, silent = true })
  end
end

local function whid()
  position = 0
  open_window()
  set_mappings()
  update_view(0)
  api.nvim_win_set_cursor(win, {4, 0})
end

return {
  whid = whid,
  update_view = update_view,
  open_file = open_file,
  move_cursor = move_cursor,
  close_window = close_window
}
이제 루아네옴 스크립트를 위한 간단한 TUI를 작성할 수 있는 기본 지식을 갖추어야 합니다.즐겁게 놀다
코드도 여기서 찾을 수 있습니다https://github.com/rafcamlet/nvim-whid

좋은 웹페이지 즐겨찾기