So far, we’ve learned a lot about working with data within a program, and how to include more Lua into a program, but not how to import and export data from a file. Lua has plenty of builtin features to handle this via the I/O Library. The I/O library works with both text and binary files.
Lua is Unicode agnostic. This means that all strings are treated effectively as binary data rather than any kind of encoding. Because of this, you rarely need to use a Unicode library except for more specific use cases.
Open and Close
Before we get into either reading or writing, we need to discuss opening and closing files. Your workflow will typically involve opening a file, doing whatever with it, then explicitly closing it. When you exit the program, the file should close as well, but that’s considered poor practice.
To open a file:
io.open( [filename] [, [mode]] )
The filename is where the file is, and the mode is listed in the table below (or following the same mode as in C for fopen). If there is no mode, r is implied.
r | (Default) Open the file read only. |
---|---|
w | Open the file to write. Overwrites contents or makes a new file. |
a | Append the file. Write to the end or make a new file. Repositioning operations are ignored. |
r+ | Open the file in read and write mode. The file must exist. |
w+ | Open the file to write. Clears existing contents or makes a new file. |
a+ | Append the file with read mode. Write to the end or make a new file. Repositioning operations affect read mode. |
You can also use the b modifier at the end of the mode in order to open the file in binary mode. For example:
io.open( [file], "r+b" )
io.open returns a file handle object. When working with opening files, you should assign the file handle to a variable to do anything useful.
local filehandle = io.open( "mycontents.txt", "rb" )
Once you’re done with a file, you need to close it.
local filehandle = io.open( "mycontents.txt", "rb" )
…
filehandle:close()
Reading Files
Once you open your file, you can read it. Reading is done via:
filehandle:read( "[option]" [or: [number]] )
The options correspond to the following table for Lua 5.1 and 5.2:
*line | *l | Reads a single line from the file. |
---|---|---|
*all | *a | Reads all of the content from the file. |
*number | *n | Reads a number from the file. This reads until the end of a valid sequence. |
[number] | (integer) | Reads [number] characters (bytes) from the file. Does not use quotes. |
Lua 5.3 is adds the following (but is backwards compatible):
a | Reads all of the content from the file. |
---|---|
l | Reads a single line from the file. |
L | Reads a single line from the file, but keeps the end of line character(s) |
n | Reads a number from the file. This reads until the end of a valid sequence. |
[number] | Reads [number] characters (bytes) from the file. Does not use quotes. |
These basically all return nil if there isn’t a valid sequence, except for the “all” options which return an empty string as long as the file is valid.
Let’s see it all in action with test.txt:
123
line 2
123456789
(Note the new line at the end of test.txt)
Let’s try each of our options out for reading and see what we get. We’re going to start the whole file over and over again to avoid any issues.
#!/usr/bin/lua5.1
local file = io.open( "test.txt", "r" )
local contents = file:read( "*all" )
print( contents )
file:close()
This gets us:
./file1.lua
123
line 2
123456789
Let’s see all of our options smashed together now that we’ve seen the basic structure:
#!/usr/bin/lua5.1
print( "*all" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "*all" )
print( contents )
file:close()
print()
print( "*line" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "*line" )
print( contents )
file:close()
print()
print( "*number" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "*number" )
print( contents )
file:close()
print()
print( "6" )
local file = io.open( "test.txt", "r" )
local contents = file:read( 6 )
print( contents )
file:close()
Which gets us:
./file1.lua
*all
123
line 2
123456789
*line
123
*number
123
6
123
li
Let’s do the same with Lua 5.3:
#!/usr/bin/lua5.3
print( "a" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "a" )
print( contents )
file:close()
print()
print( "l" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "l" )
print( contents )
file:close()
print()
print( "L" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "L" )
print( contents )
file:close()
print()
print( "n" )
local file = io.open( "test.txt", "r" )
local contents = file:read( "n" )
print( contents )
file:close()
print()
print( "6" )
local file = io.open( "test.txt", "r" )
local contents = file:read( 6 )
print( contents )
file:close()
And we get:
./file1.lua
a
123
line 2
123456789
l
123
L
123
n
123
6
123
li
Moving Around In Files
Lua offers a seek function. Seek works the same in 5.1, 5.2, and 5.3.
[file]:seek( "[start]" [, [offset]] )
This has the following starting points:
set | Set to start from the beginning of the file. |
---|---|
cur | Move from the current position the file is at. |
end | Move from the end of the file. |
Since we start at the beginning of the file, we’d get the following with cur:
#!/usr/bin/lua5.3
print( "a" )
local file = io.open( "test.txt", "r" )
file:seek("cur", 3)
local contents = file:read( "a" )
print( contents )
file:close()
print()
print( "l" )
local file = io.open( "test.txt", "r" )
file:seek("cur", 3)
local contents = file:read( "l" )
print( contents )
file:close()
print()
print( "L" )
local file = io.open( "test.txt", "r" )
file:seek("cur", 3)
local contents = file:read( "L" )
print( contents )
file:close()
print()
print( "n" )
local file = io.open( "test.txt", "r" )
file:seek("cur", 3)
local contents = file:read( "n" )
print( contents )
file:close()
print()
print( "6" )
local file = io.open( "test.txt", "r" )
file:seek("set", 3)
local contents = file:read( 6 )
print( contents )
file:close()
Which gets us:
./file2.lua
a
line 2
123456789
l
L
n
nil
6
line
Let’s test a few other methods:
#!/usr/bin/lua5.3
print( "a" )
local file = io.open( "test.txt", "r" )
file:seek("cur", 3)
local contents = file:read( "a" )
print( contents )
print()
print( "l" )
file:seek("set")
local contents2 = file:read( "l" )
print( contents2 )
print()
print( "n" )
file:seek("end", -3)
local contents3 = file:read( "n" )
print( contents3 )
file:close()
Which gets us:
./file3.lua
a
line 2
123456789
l
123
n
89
[file]:seek() gets you the current position for later usage.
More File Operations
[file]:flush() syncs the data to disk for the file.
The Lua Manual has a lot more than we can include here. See these for a reference to some of the functions as written in 5.3.
Writing Files
Luckily, in Lua, writing is way easier than reading. You just use [file]:write( [contents] ) to write to the given file.
#!/usr/bin/lua5.1
local file = io.open( "testout.txt", "w" )
file:write( "here's a linen" )
file:write( "and anothern" )
file:write( "and yet anothern" )
file:close()
local file2 = io.open( "testout.txt", "r" )
local content = file2:read( "*a" )
print( content )
file2:close()
This gets you:
/file3.lua
here's a line
and another
and yet another
(Note: the extra end line)
What happens if you don’t use /n (the newline control character)?
Making It Cleaner
We’re going to get into Lua Idioms with the assert function. This function basically makes Lua check whether the condition is going to succeed or not and gracefully fail with an error.
Assert:
assert( [condition] [, [error message]] )
Let’s see it in action:
#!/usr/bin/lua5.1
local file = assert( io.open( "testout.txt", "w" ) )
file:write( "here's a linen" )
file:write( "and anothern" )
file:write( "and yet anothern" )
file:close()
local file2 = assert( io.open( "testout-DOESNOTEXIST.txt", "r" ) )
local content = file2:read( "*a" )
print( content )
file2:close()
Where we get:
/file3.lua
/usr/bin/lua5.1: ./file3.lua:9: testout-DOESNOTEXIST.txt: No such file or directory
stack traceback:
[C]: in function 'assert'
./file3.lua:9: in main chunk
[C]: ?
We’ve now cleanly errored out of the program without trying to read a file which does not exist.
Let’s learn about io.type( [filehandle] ). This is extremely useful for checking on if a file is able to get a file handle. io.type( [filehandle] ) returns either “file” (the handle is valid), “closed file” (the handle is closed), or nil (it failed). Here’s an example:
#!/usr/bin/lua5.1
local file = assert( io.open( "testout.txt", "w" ) )
file:write( "here's a linen" )
file:write( "and anothern" )
file:write( "and yet anothern" )
file:close()
local file2 = io.open( "testout-DOESNOTEXIST.txt", "r" )
if( io.type( file2 ) == "file" ) then
local content = file2:read( "*a" )
print( content )
file2:close()
else
print( "--error--" )
end
This gets us:
./file3.lua
--error--
Now, instead of just exiting out, we have error handling. This is far cleaner than just using assert and exiting out. The assert function still has its place for debugging, but now we’ve made our program fault resilient and graceful as long as we handle it correctly.
I have written a function getScriptDir
which uses the debug information like a few other people have suggested, but this one is going to work everytime (at least in Windows). But the thing is there are quite a few lines of code as it uses another function string.cut
which i have created, which separates a string every given pattern, and puts it into a table.
function string.cut(s,pattern)
if pattern == nil then pattern = " " end
local cutstring = {}
local i1 = 0
repeat
i2 = nil
local i2 = string.find(s,pattern,i1+1)
if i2 == nil then i2 = string.len(s)+1 end
table.insert(cutstring,string.sub(s,i1+1,i2-1))
i1 = i2
until i2 == string.len(s)+1
return cutstring
end
function getScriptDir(source)
if source == nil then
source = debug.getinfo(1).source
end
local pwd1 = (io.popen("echo %cd%"):read("*l")):gsub("\","/")
local pwd2 = source:sub(2):gsub("\","/")
local pwd = ""
if pwd2:sub(2,3) == ":/" then
pwd = pwd2:sub(1,pwd2:find("[^/]*%.lua")-1)
else
local path1 = string.cut(pwd1:sub(4),"/")
local path2 = string.cut(pwd2,"/")
for i = 1,#path2-1 do
if path2[i] == ".." then
table.remove(path1)
else
table.insert(path1,path2[i])
end
end
pwd = pwd1:sub(1,3)
for i = 1,#path1 do
pwd = pwd..path1[i].."/"
end
end
return pwd
end
Note: if you want to use this function in another OS than Windows, you have to change the io.popen("echo %cd%")
in the line 15 to whatever command gives you present working directory in your OS, e.g. io.popen("pwd")
for Linux, and the pwd2:sub(2,3) == ":/"
in the line 18 to whatever represents the root directory in your OS, e.g. pwd2:sub(1,1) == "/"
for Linux.
Note2: if you don’t provide the source
variable to the function via debug.getinfo(1).source
when calling it, then it will return the path to the directory of the file containing this function. Therefore, if you want to get the directory of a file which you called via dofile
or loadfile
, you will have to give it the source, like this: getScriptDir(debug.getinfo(1).source)
.
Мне нужно прочитать с файла в функции. файл в той же папке что и код.
Я передаю так : local inputFileName = [[ts.txt]]
Но выдает ошибку:
ts.txt: No such file or directory)
задан 17 ноя 2019 в 19:43
7
Если вы запускаете скрипт из той же директории, всё должно рабоать.
Если вы в другой директории, то либо перейдите в неё:
$ cd /path/to/dir
$ ls
script.lua
ts.txt
$ lua ./script.lua
Либо прописывайте абсолютный путь. Путь можно прописать в переменной
окружения:
# Unix:
export FILES_PATH="/path/to/dir"
:: Windows:
SET FILES_PATH="/path/to/dir"
И получить в скрипте:
local dir = os.getenv("FILES_PATH")
path = dir .. "/" .. "ts.txt"
ответ дан 17 ноя 2019 в 21:08
Ainar-GAinar-G
15.9k3 золотых знака24 серебряных знака41 бронзовый знак
18
Если вам надо открыть файл, который лежит в той же папке, что и текущий скрипт, то вам надо сперва определить полный путь к вашему скрипту, а затем, используя его, сконструировать и передать полный путь к файлу.
Определить путь к скрипту можно несколькими способами, например вот так:
function script_path()
local str = debug.getinfo(2, "S").source:sub(2)
return str:match("(.*[/\])") or ".\"
end
Ну а дальше всё просто:
local inputFileName = script_path() .. "ts.txt"
ответ дан 17 ноя 2019 в 20:29
zedzed
8,8262 золотых знака19 серебряных знаков34 бронзовых знака
2
Начало работы с Lua в Neovim
Содержание
- Введение
- Изучение языка Lua
- Имеющиеся туториалы по написанию плагинов на Lua для Neovim
- Связанные плагины
- Куда класть файлы Lua
- init.lua
- Другие файлы Lua
- Предостережения
- Советы
- Заметка относительно пакетов
- Использование Lua в Vimscript
- :lua
- :luado
- :luafile
- luafile vs require():
- luaeval()
- v:lua
- Предостережения
- Советы
- Пространство имён vim
- Советы
- Использование Vimscript из Lua
- vim.api.nvim_eval()
- Предостережения
- vim.api.nvim_exec()
- vim.api.nvim_command()
- Советы
- vim.api.nvim_eval()
- Управление опции vim
- Использование функций API
- Использование мета-аксессоров
- Предостережения
- Управление внутренними переменными vim
- Использование функций API
- Использование мета-аксессоров
- Предостережения
- Вызов функций Vimscript
- vim.call()
- vim.fn.{function}()
- Советы
- Предостережения
- Определение сопоставлений клавиш
- Определение пользовательских команд
- Определение автокоманд
- Определение синтаксиса/подсветки
- Общие советы и рекомендации
- Настройка линтеров/языковых серверов
- luacheck
- sumneko/lua-language-server
- coc.nvim
- Настройка линтеров/языковых серверов
- Разное
- vim.loop
- vim.lsp
- vim.treesitter
- Transpilers
Created by gh-md-toc
Введение
Интеграция Lua в Neovim в качестве языка с первоклассной поддержкой превращает её в одну из важнейших особенностей редактора. Тем не менее, количество учебных материалов по написанию плагинов на Lua значительно меньше таковых на Vimscript.
Это руководство является попыткой предоставления необходимой информации для написания плагинов на Lua.
Это руководство предполагает, что пользователь использует последнюю версию Neovim nightly build.
Так как версия 0.5 Neovim находится на стадии разработки, имейте в виду, что API, которые находятся в активной разработке нестабильны и могут быть подвержены изменениям до релиза.
Изучение Lua
Если вы незнакомы с языком, имеется большое количество материалов для изучения:
- Ресурс Learn X in Y minutes page about Lua позволит вам пробежаться по основам языка
- Если вы предпочитаете видеоуроки, то у Дерека Банаса (Derek Banas) имеется часовой видеоурок 1-hour tutorial on the language
- Сайт lua-users wiki содержит большое количество полезной информации относительно Lua
- Сайт official reference manual for Lua должен дать исчерпывающую информацию о языке
Следует заметить, что Lua является очень чистым и простым языком. Язык легко изучить, особенно если имеется опыт использования аналогичного скриптового языка наподобие Javascript. Возможно, вы разбираетесь в Lua больше, чем вы представляете!
Заметка: версия Lua, встроенное в Neovim является LuaJIT 2.1.0, что поддерживает совместимость с Lua 5.1 (с некоторыми исключениями в виде расширений версии 5.2)
Имеющиеся туториалы по написанию плагинов на Lua для Neovim
Было написано несколько туториалов, чтобы помочь людям написать плагины на Lua. Некоторые из них значительно помогли для написания этого руководства. Большое спасибо их авторам.
- teukka.tech – From init.vim to init.lua
- 2n.pl – How to write neovim plugins in Lua
- 2n.pl – How to make UI for neovim plugins in Lua
- ms-jpq – Neovim Async Tutorial
Связанные плагины
- Vimpeccable – Плагин, помогающий написать .vimrc на Lua
- plenary.nvim – Все функции Lua, которые я не хоче переписывать
- popup.nvim – Имплементация API Всплывающих окон vim(vim Popup API) для Neovim
- nvim_utils
- nvim-luadev – REPL/дебаг консоль для плагинов Neovim, написанных на lua
- nvim-luapad – Интерактивный Neovim скратчпад для встроенного движка Lua
- nlua.nvim – Lua Разработка для Neovim
- BetterLua.vim – Лучшая синтаксическая подсветка Lua в Vim/NeoVim
Куда класть файлы Lua
init.lua
Neovim поддерживает загрузку файла init.lua
вместо init.vim
для конфигурации.
Для справок:
:help config
Другие файлы Lua
Файлы Lua обычно находятся внутри папки lua/
в вашей runtimepath
(для большинства пользователей это папка ~/.config/nvim/lua
на *nix-овых системах и ~/AppData/Local/nvim/lua
для Windows). Вы можете вызвать эти файлы с помощью require()
в качестве Lua модулей.
В качестве примера возьмем следующую структуру папок:
📂 ~/.config/nvim
├── 📁 after
├── 📁 ftplugin
├── 📂 lua
│ ├── 🌑 myluamodule.lua
│ └── 📂 other_modules
│ ├── 🌑 anothermodule.lua
│ └── 🌑 init.lua
├── 📁 pack
├── 📁 plugin
├── 📁 syntax
└── 🇻 init.vim
Lua код ниже загрузит модуль myluamodule.lua
:
Заметьте отсутствие расширения файла .lua
.
Аналогично, загрузка other_modules/anothermodule.lua
выполняется следующим образом:
require('other_modules.anothermodule') -- or require('other_modules/anothermodule')
Разделители путей обозначены либо точкой .
либо слэшем /
.
Папка содержащая файл init.lua
может быть загружена напрямую без необходимости уточнять имя файла.
require('other_modules') -- загружает other_modules/init.lua
Для большей информации: :help lua-require
Предостережения
В отличие от файлов с расширением .vim, файлы с расширением .lua не загружаются автоматически, если они находятся в специальных папках runtimepath
. К примеру, Neovim загрузит plugin/foo.vim
, но не загрузит plugin/foo.lua
.
Также смотрите:
- Issue #12670
Советы
Некоторые Lua плагины могут имет идентичные имена файлов внутри папки lua/
. Это может привести к коллизии пространств имён.
Если два плагина имеют файл lua/main.lua
, То вызов require('main')
неопределён: какой файл необходимо загрузить?
Поэтому это хорошая идея создать пространство имен вашей конфигурации или плагина с помощью папки в самом верхнем уровне, наподобие: lua/plugin_name/main.lua
Заметка относительно пакетов
Обновление: если вы используете последнюю ночную сборку, это [больше не проблема] (neovim/neovim#13119), и вы можете спокойно пропустить этот раздел.
Если вы используете функцию packages
или основанного на ней менеджера подключаемых модулей (например, packer.nvim, minpac или vim-packager), при использовании плагинов Lua следует помнить о некоторых вещах.
Пакеты в папке start
загружаются только после считывания вашего init.vim
. Это означает, что пакет не добавляется в runtimepath
до тех пор, пока Neovim не закончит обработку файла. Это может вызвать проблемы, если плагин ожидает, что вы загрузите(require
) модуль Lua или вызовете автоматически загружаемую функцию.
Предполагая, что в пакете start/foo
есть файл lua/bar.lua
, выполнение кода ниже в init.vim
вызовет ошибку, потому что runtimepath
еще не обновлен:
Вы должны использовать команду packadd! foo
перед тем как вызвать модуль через require
.
packadd! foo lua require('bar')
Добавление !
к packadd
означает, что Neovim поместит пакет в runtimepath
без загрузки каких-либо скриптов в его папках plugin
или ftdetect
.
Также смотрите:
:help :packadd
- Issue #11409
Использование Lua в Vimscript
:lua
Эта команда выполняет фрагмент кода Lua.
:lua require('myluamodule')
Многострочные скрипты возможны с использованием синтаксиса heredoc:
echo "Here's a bigger chunk of Lua code" lua << EOF local mod = require('mymodule') local tbl = {1, 2, 3} for k, v in ipairs(tbl) do mod.method(v) end print(tbl) EOF
Примечание: каждая команда :lua
имеет свою собственную область видимости, и переменные, объявленные с ключевым словом local
, недоступны вне команды. Это не сработает:
:lua local foo = 1 :lua print(foo) " выводит 'nil' вместо '1'
Примечание 2: функция print()
в Lua ведет себя аналогично команде :echomsg
. Его вывод сохраняется в истории сообщений и может быть подавлен командой :silent
.
Также смотрите:
:help :lua
:help :lua-heredoc
:luado
Эта команда выполняет фрагмент кода Lua, который воздействует на диапазон строк в текущем буфере. Если диапазон не указан, вместо него используется весь буфер. Строка возвращаемая из блока, используется для определения того, чем должна быть заменена каждая строка в диапазоне.
Следующая команда заменит каждую строку в текущем буфере текстом hello world
:
:luado return 'hello world'
Также предусмотрены две неявные переменные line
и linenr
. line
– это текст строки, по которой выполняется итерация, а linenr
– ее номер. Следующая команда сделает каждую строку, номер которой делится на 2, в верхний регистр:
:luado if linenr % 2 == 0 then return line:upper() end
Также смотрите:
:help :luado
:luafile
Эта команда считывает файл Lua.
:luafile ~/foo/bar/baz/myluafile.lua
Эта команда аналогична команде :source
для файлов .vim или встроенной функции dofile()
в Lua.
Также смотрите:
:help :luafile
luafile vs require():
Вам может быть интересно, в чем разница между lua require()
и luafile
, и стоит ли вам использовать одно вместо другого. У них разные варианты использования:
require()
:- это встроенная функция Lua. Это позволяет вам использовать модульную систему Lua
- ищет модули в папках
lua
в вашемruntimepath
- отслеживает, какие модули были загружены, и предотвращает повторный парсинг и выполнение скрипта. Если вы измените файл, содержащий код для модуля, и попытаетесь
require()
второй раз во время работы Neovim, модуль на самом деле не будет обновляться
:luafile
:- является Ex командой. Не поддерживает модули
- принимает абсолютный или относительный путь к рабочей папке текущего окна
- выполняет содержимое скрипта независимо от того, выполнялся ли он раньше
:luafile
также может быть полезен, если вы хотите запустить файл Lua, над которым вы работаете:
luaeval()
Эта встроенная функция Vimscript оценивает выражение Lua в форме строки и возвращает ее значение. Типы данных Lua автоматически преобразуются в типы Vimscript (и наоборот).
" Вы можете сохранить результат в переменной let variable = luaeval('1 + 1') echo variable " 2 let concat = luaeval('"Lua".." is ".."awesome"') echo concat " 'Lua is awesome' " Таблицы в виде списков преобразуются в списки Vim. let list = luaeval('{1, 2, 3, 4}') echo list[0] " 1 echo list[1] " 2 " Обратите внимание, что в отличие от таблиц Lua, списки Vim индексируются с нуля " Таблицы в виде словарей конвертируются в словари Vim. let dict = luaeval('{foo = "bar", baz = "qux"}') echo dict.foo " 'bar' " То же самое для логических значений и значений nil echo luaeval('true') " v:true echo luaeval('nil') " v:null " Вы можете создавать алиас в Vimscript для функций Lua. let LuaMathPow = luaeval('math.pow') echo LuaMathPow(2, 2) " 4 let LuaModuleFunction = luaeval('require("mymodule").myfunction') call LuaModuleFunction() " Также можно передавать функции Lua в качестве значений функциям Vim. lua X = function(k, v) return string.format("%s:%s", k, v) end echo map([1, 2, 3], luaeval("X"))
luaeval()
принимает необязательный второй аргумент, который позволяет передавать данные в выражение. Затем вы можете получить доступ к этим данным из Lua, используя волшебную глобальную переменную _A
:
echo luaeval('_A[1] + _A[2]', [1, 1]) " 2 echo luaeval('string.format("Lua is %s", _A)', 'awesome') " 'Lua is awesome'
Также смотрите:
:help luaeval()
v:lua
Эта глобальная переменная Vim позволяет вам вызывать глобальные функции Lua прямо из Vimscript. Опять же, типы данных Vim преобразуются в типы Lua и наоборот.
call v:lua.print('Hello from Lua!') " 'Hello from Lua!' let scream = v:lua.string.rep('A', 10) echo scream " 'AAAAAAAAAA' " Загрузка модулей работает call v:lua.require('mymodule').myfunction() " Как насчет неплохой статусной строки? lua << EOF function _G.statusline() local filepath = '%f' local align_section = '%=' local percentage_through_file = '%p%%' return string.format( '%s%s%s', filepath, align_section, percentage_through_file ) end EOF set statusline=%!v:lua.statusline() " Также работает в сопоставлениях выражений lua << EOF function _G.check_back_space() local col = vim.fn.col('.') - 1 if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then return true else return false end end EOF inoremap <silent> <expr> <Tab> pumvisible() ? '<C-n>' : v:lua.check_back_space() ? '<Tab>' : completion#trigger_completion()
Также смотрите:
:help v:lua
:help v:lua-call
Предостережения
Эта переменная может использоваться только для вызова функций. Следующий код всегда будет вызывать ошибку:
" Создание алиасов не работает let LuaPrint = v:lua.print " Доступ к словарям не работает echo v:lua.some_global_dict['key'] " Использование функции в качестве значения не работает echo map([1, 2, 3], v:lua.global_callback)
Советы
Вы можете получить подсветку синтаксиса Lua внутри файлов .vim, поместив let g: vimsyn_embed = 'l'
в свой файл конфигурации. См. :help g:vimsyn_embed
для получения дополнительной информации об этой опции.
Пространство имён vim
Neovim предоставляет глобальную переменную vim
, которая служит точкой входа для взаимодействия с её API из Lua. Она предоставляет пользователям расширенную “стандартную библиотеку” функций, а также различные подмодули.
Некоторые примечательные функции и модули включают:
vim.inspect
: вывод Lua объектов (полезно для проверки таблиц)vim.regex
: использование регулярных выражений Vim из Luavim.api
: модуль, который предоставляет функции API (тот же API, что используют удалённые(remote) плагины)vim.loop
: модуль, который предоставляет функционал цикла событий Neovim (с использованием LibUV)vim.lsp
: модуль, который управляет встроенным клиентом LSPvim.treesitter
: модуль, который предоставляет функционал библиотеки tree-sitter
Этот список ни в коем случае не является исчерпывающим. Если вы хотите узнать больше о том, что делает переменная vim
, :help lua-stdlib
и :help lua-vim
вам в помощь :). В качестве альтернативы вы можете выполнить :lua print (vim.inspect (vim))
, чтобы получить список всех модулей.
Советы
Писать print(vim.inspect(x))
каждый раз, когда вы хотите проверить содержимое объекта, может оказаться довольно утомительным. Возможно, стоит иметь где-нибудь в вашей конфигурации глобальную функцию-оболочку:
function _G.dump(...) local objects = vim.tbl_map(vim.inspect, {...}) print(unpack(objects)) end
Затем вы можете очень быстро проверить содержимое объекта в своем коде или из командной строки:
Кроме того, вы можете обнаружить, что встроенных функций Lua иногда не хватает по сравнению с тем, что вы найдете в других языках (например, os.clock()
возвращает значение только в секундах, а не в миллисекундах). Обязательно посмотрите Neovim stdlib (и vim.fn
, подробнее об этом позже), вероятно, в нем есть то, что вы ищете.
Использование Vimscript из Lua
vim.api.nvim_eval()
Эта функция оценивает строку выражения Vimscript и возвращает ее значение. Типы данных Vimscript автоматически преобразуются в типы Lua (и наоборот).
Это Lua-эквивалент функции luaeval()
в Vimscript.
-- Типы данных конвертируются правильно print(vim.api.nvim_eval('1 + 1')) -- 2 print(vim.inspect(vim.api.nvim_eval('[1, 2, 3]'))) -- { 1, 2, 3 } print(vim.inspect(vim.api.nvim_eval('{"foo": "bar", "baz": "qux"}'))) -- { baz = "qux", foo = "bar" } print(vim.api.nvim_eval('v:true')) -- true print(vim.api.nvim_eval('v:null')) -- nil
TODO: возможно ли, чтобы vim.api.nvim_eval()
возвращала funcref
?
Предостережения
В отличие от luaeval()
, vim.api.nvim_eval()
не предоставляет неявную переменную _A
для передачи данных в выражение.
vim.api.nvim_exec()
Эта функция оценивает фрагмент кода Vimscript. Она принимает строку, содержащую исходный код для выполнения, и логическое значение, чтобы определить, должен ли вывод кода возвращаться функцией (вы можете сохранить вывод в переменной, для примера).
local result = vim.api.nvim_exec( [[ let mytext = 'hello world' function! MyFunction(text) echo a:text endfunction call MyFunction(mytext) ]], true) print(result) -- 'hello world'
TODO: в документации указано, что скриптовая область действия(s:
) поддерживается, но запуск этого фрагмента с переменной скриптовой области действия вызывает ошибку. Почему?
vim.api.nvim_command()
Эта функция выполняет команду ex. Она принимает строку, содержащую команду для выполнения.
vim.api.nvim_command('new') vim.api.nvim_command('wincmd H') vim.api.nvim_command('set nonumber') vim.api.nvim_command('%s/foo/bar/g')
Примечание: vim.cmd
– более короткий alias для этой функции.
Советы
Поскольку вам нужно передавать строки этим функциям, вам часто приходится экранировать обратный слэш:
vim.cmd('%s/\Vfoo/bar/g')
Строковые литералы проще использовать, поскольку они не требуют экранирующих символов:
vim.cmd([[%s/Vfoo/bar/g]])
Управление опции vim
Использование функций API
Neovim предоставляет набор функций API для изменения опции или получения её текущего значения:
- Глобальные опции:
vim.api.nvim_set_option()
vim.api.nvim_get_option()
- Локальные опции буферов:
vim.api.nvim_buf_set_option()
vim.api.nvim_buf_get_option()
- Локальные опции окон:
vim.api.nvim_win_set_option()
vim.api.nvim_win_get_option()
Они принимают строку, содержащую имя опции, которую нужно установить / получить, а также значение, которое вы хотите установить.
Логические параметры (например, (no)number
) должны иметь значение true
или false
:
vim.api.nvim_set_option('smarttab', false) print(vim.api.nvim_get_option('smarttab')) -- false
Неудивительно, что параметры строки должны быть строками:
vim.api.nvim_set_option('selection', 'exclusive') print(vim.api.nvim_get_option('selection')) -- 'exclusive'
Числовые опции принимают число:
vim.api.nvim_set_option('updatetime', 3000) print(vim.api.nvim_get_option('updatetime')) -- 3000
Локальные опции буффера и окна также нуждаются в номере буфера или номере окна (использование 0
установит/получит опцию для текущего буфера/окна)
vim.api.nvim_win_set_option(0, 'number', true) vim.api.nvim_buf_set_option(10, 'shiftwidth', 4) print(vim.api.nvim_win_get_option(0, 'number')) -- true print(vim.api.nvim_buf_get_option(10, 'shiftwidth')) -- 4
Использование мета-аксессоров
Если вы хотите установить параметры более “идиоматическим” способом, доступны несколько мета-аксессуаров. По сути, они обертывают вышеуказанные функции API и позволяют управлять параметрами, как если бы они были переменными:
vim.o.{option}
: глобальные опцииvim.bo.{option}
: локальные опции буффераvim.wo.{option}
: локальные опции окна
vim.o.smarttab = false print(vim.o.smarttab) -- false vim.bo.shiftwidth = 4 print(vim.bo.shiftwidth) -- 4
Вы можете указать номер для опций, локальных для буфера и для локального окна. Если номер не указан, используется текущий буфер / окно:
vim.bo[4].expandtab = true -- тоже самое что и vim.api.nvim_buf_set_option(4, 'expandtab', true) vim.wo.number = true -- тоже самое что и vim.api.nvim_win_set_option(0, 'number', true)
Также смотрите:
:help lua-vim-internal-options
Предостережения
В Lua нет эквивалента команде :set
, вы либо устанавливаете параметр глобально, либо локально.
Также смотрите:
:help :setglobal
:help global-local
Управление внутренними переменными vim
Использование функций API
Как и у параметров, внутренние переменные имеют собственный набор функций API:
- Глобальные переменные (
g:
):vim.api.nvim_set_var ()
vim.api.nvim_get_var ()
vim.api.nvim_del_var ()
- Переменные буфера (
b:
):vim.api.nvim_buf_set_var ()
vim.api.nvim_buf_get_var ()
vim.api.nvim_buf_del_var ()
- Оконные переменные (
w:
):vim.api.nvim_win_set_var ()
vim.api.nvim_win_get_var ()
vim.api.nvim_win_del_var ()
- Переменные вкладки (
t:
):vim.api.nvim_tabpage_set_var ()
vim.api.nvim_tabpage_get_var ()
vim.api.nvim_tabpage_del_var ()
- Предопределенные переменные Vim (
v:
):vim.api.nvim_set_vvar ()
vim.api.nvim_get_vvar ()
За исключением предопределенных переменных Vim, они также могут быть удалены (команда :unlet
является эквивалентом в Vimscript). Локальные переменные (l:
), скриптовые переменные (s:
) и аргументы функции (a:
) не могут быть изменены, поскольку они имеют смысл только в контексте Vimscript, Lua имеет свои собственные правила области видимости
Если вы не знакомы с тем, что делают эти переменные, :help internal-variables
описывает их подробно.
Эти функции принимают строку, содержащую имя переменной для изменения/получения/удаления, а также значение, которое вы хотите установить.
vim.api.nvim_set_var('some_global_variable', { key1 = 'value', key2 = 300 }) print(vim.inspect(vim.api.nvim_get_var('some_global_variable'))) -- { key1 = "value", key2 = 300 } vim.api.nvim_del_var('some_global_variable')
Переменные, которые ограничены буфером, окном или вкладкой, также получают номер (использование 0
изменит/получит/удалит переменную для текущего буфера/окна/вкладки):
vim.api.nvim_win_set_var(0, 'some_window_variable', 2500) vim.api.nvim_tab_set_var(3, 'some_tabpage_variable', 'hello world') print(vim.api.nvim_win_get_var(0, 'some_window_variable')) -- 2500 print(vim.api.nvim_buf_get_var(3, 'some_tabpage_variable')) -- 'hello world' vim.api.nvim_win_del_var(0, 'some_window_variable') vim.api.nvim_buf_del_var(3, 'some_tabpage_variable')
Использование мета-аксессоров
Внутренними переменными можно управлять более интуитивно с помощью этих мета-аксессоров:
vim.g.{name}
: глобальные переменныеvim.b.{name}
: буферные переменныеvim.w.{name}
: переменные окнаvim.t.{name}
: переменные вкладкиvim.v.{name}
: предопределенные переменные Vim
vim.g.some_global_variable = { key1 = 'value', key2 = 300 } print(vim.inspect(vim.g.some_global_variable)) -- { key1 = "value", key2 = 300 }
Чтобы удалить одну из этих переменных, просто присвойте ей nil
:
vim.g.some_global_variable = nil
Предостережения
В отличие от мета-аксессоров опций, вы не можете указать число для переменных с областью буфера/окна/вкладки.
Кроме того, вы не можете добавлять/обновлять/удалять ключи из словаря, хранящегося в одной из этих переменных. Например, этот фрагмент кода Vimscript не работает:
let g:variable = {} lua vim.g.variable.key = 'a' echo g:variable " {}
Это известная проблема:
- Issue #12544
Вызов функций Vimscript
vim.call()
vim.call()
вызывает функцию Vimscript. Это может быть встроенная функция Vim или пользовательская функция. Опять же, типы данных конвертируются из Lua в Vimscript и обратно.
Она принимает имя функции, за которым следуют аргументы, которые вы хотите передать этой функции:
print(vim.call('printf', 'Hello from %s', 'Lua')) local reversed_list = vim.call('reverse', { 'a', 'b', 'c' }) print(vim.inspect(reversed_list)) -- { "c", "b", "a" } local function print_stdout(chan_id, data, name) print(data[1]) end vim.call('jobstart', 'ls', { on_stdout = print_stdout }) vim.call('my#autoload#function')
See also:
:help vim.call()
vim.fn.{function}()
vim.fn
does the exact same thing as vim.call()
, but looks more like a native Lua function call:
print(vim.fn.printf('Hello from %s', 'Lua')) local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' }) print(vim.inspect(reversed_list)) -- { "c", "b", "a" } local function print_stdout(chan_id, data, name) print(data[1]) end vim.fn.jobstart('ls', { on_stdout = print_stdout })
Хэши #
не являются допустимыми символами для идентификаторов в Lua, поэтому функции автозагрузки должны вызываться с таким синтаксисом:
vim.fn['my#autoload#function']()
Также смотрите:
:help vim.fn
Советы
Neovim имеет обширную библиотеку мощных встроенных функций, которые очень полезны для плагинов. Смотрите :help vim-function
для списка в алфавитном порядке и :help function-list
для списка функций, сгруппированных по темам.
Предостережения
Некоторые функции Vim, которые должны возвращать логическое значение 1
или 0
. В Vimscript это не проблема, поскольку 1
истинно, а 0
ложно, что позволяет использовать такие конструкции:
if has('nvim') " do something... endif
Однако в Lua ложными считаются только false
и nil
, числа всегда оцениваются как true
, независимо от их значения. Вы должны явно проверить 1
или 0
:
if vim.fn.has('nvim') == 1 then -- do something... end
Определение сопоставлений клавиш
Neovim предоставляет список функций API для установки, получения и удаления сопоставлений:
- Для глобальных сопоставлений:
vim.api.nvim_set_keymap()
vim.api.nvim_get_keymap()
vim.api.nvim_del_keymap()
- Для локальных сопоставлений:
vim.api.nvim_buf_set_keymap()
vim.api.nvim_buf_get_keymap()
vim.api.nvim_buf_del_keymap()
Начнем с vim.api.nvim_set_keymap()
и vim.api.nvim_buf_set_keymap()
Первым аргументом, переданным в функцию, является строка, содержащая имя режима, для которого сопоставление будет действовать:
Строчное значение | Страница помощи | Затронутые режимы | Эквивалент Vimscript |
---|---|---|---|
'' (пустая строка) |
mapmode-nvo |
Normal, Visual, Select, Operator-pending | :map |
'n' |
mapmode-n |
Normal | :nmap |
'v' |
mapmode-v |
Visual and Select | :vmap |
's' |
mapmode-s |
Select | :smap |
'x' |
mapmode-x |
Visual | :xmap |
'o' |
mapmode-o |
Operator-pending | :omap |
'!' |
mapmode-ic |
Insert and Command-line | :map! |
'i' |
mapmode-i |
Insert | :imap |
'l' |
mapmode-l |
Insert, Command-line, Lang-Arg | :lmap |
'c' |
mapmode-c |
Command-line | :cmap |
't' |
mapmode-t |
Terminal | :tmap |
Второй аргумент – это строка, содержащая левую часть отображения (ключ или набор ключей, запускающих команду, определенную в сопоставлении). Пустая строка эквивалентна <Nop>
, который отключает ключ.
Третий аргумент – это строка, содержащая правую часть сопоставления (команду для выполнения).
Последний аргумент – это таблица, содержащая логические параметры для сопоставления, как определено в :help :map-arguments
(включая noremap
и исключая buffer
).
Сопоставления локальных буферов также принимают номер буфера в качестве первого аргумента (0
устанавливает сопоставление для текущего буфера).
vim.api.nvim_set_keymap('n', '<leader><Space>', ':set hlsearch!<CR>', { noremap = true, silent = true }) -- :nnoremap <silent> <leader><Space> :set hlsearch<CR> vim.api.nvim_buf_set_keymap(0, '', 'cc', 'line(".") == 1 ? "cc" : "ggcc"', { noremap = true, expr = true }) -- :noremap <buffer> <expr> cc line('.') == 1 ? 'cc' : 'ggcc'
vim.api.nvim_get_keymap()
принимает строку, содержащую краткое имя режима, для которого вы хотите получить список сопоставлений (см. таблицу выше). Возвращаемое значение – это таблица, содержащая все глобальные сопоставления для режима.
print(vim.inspect(vim.api.nvim_get_keymap('n'))) -- :verbose nmap
vim.api.nvim_buf_get_keymap ()
принимает дополнительный номер буфера в качестве своего первого аргумента (0
получит сопоставления для текущего буфера)
print(vim.inspect(vim.api.nvim_buf_get_keymap(0, 'i'))) -- :verbose imap <buffer>
vim.api.nvim_del_keymap()
принимает режим и левую часть сопоставления.
vim.api.nvim_del_keymap('n', '<leader><Space>') -- :nunmap <leader><Space>
Опять же, vim.api.nvim_buf_del_keymap ()
принимает номер буфера в качестве своего первого аргумента, где 0
представляет текущий буфер.
vim.api.nvim_buf_del_keymap(0, 'i', '<Tab>') -- :iunmap <buffer> <Tab>
Определение пользовательских команд
В настоящее время в Lua нет интерфейса для создания пользовательских команд. Тем не менее, планы имеются:
- Pull request #11613
В настоящее время вам, вероятно, лучше создавать команды в Vimscript.
Определение автокоманд
Augroup-ы и autcommand-ы еще не имеют интерфейса, но над ним работают:
- Pull request #12378
А пока вы можете создавать автокоманды в Vimscript или использовать эту оболочку из norcalli/nvim_utils
Определение синтаксиса/подсветки
Синтаксический API все еще находится в стадии разработки. Вот пара указателей:
- Issue #9876
- tjdevries/colorbuddy.vim, библиотека для создания цветовых схем в Lua
:help lua-treesitter
Общие советы и рекомендации
Настройка линтеров/языковых серверов
Если вы используете линтеры и/или языковые серверы для диагностики и автозаполнения для проектов Lua, возможно, вам придется настроить для них параметры, специфичные для Neovim. Вот несколько рекомендуемых настроек для популярных инструментов:
luacheck
Вы можете заставить luacheck распознать глобал vim
, поместив эту конфигурацию в ~/.luacheckrc
(или $XDG_CONFIG_HOME/luacheck/.luacheckrc
):
Языковой сервер Alloyed/lua-lsp использует luacheck для обеспечения линтинга и читает тот же файл.
Для получения дополнительной информации о том, как настроить luacheck
, обратитесь к его документации
sumneko/lua-language-server
Пример конфигурации для sumneko/lua-language-server (в примере используется встроенный клиент LSP, но конфигурация для другого клиента LSP должна быть идентична):
require'lspconfig'.sumneko_lua.setup { settings = { Lua = { runtime = { -- Заставьте языковой сервер распознавать глобальные переменные LuaJIT, такие как `jit` и` bit` version = 'LuaJIT', - Настройте путь к lua path = vim.split(package.path, ';'), }, diagnostics = { - Заставьте языковой сервер распознавать глобальную переменную `vim` globals = {'vim'}, }, workspace = { -- Сделать так, чтобы сервер знал о рантайм файлах Neovim library = { [vim.fn.expand('$VIMRUNTIME/lua')] = true, [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true, }, }, }, }, }
Для получения дополнительной информации о настройке sumneko/lua-language-server см. “Setting without VSCode”
coc.nvim
Источник автодополнения rafcamlet/coc-nvim-lua для coc.nvim предоставляет элементы автодополнения для библиотеки Neovim stdlib.
TODO:
- Горячая перезагрузка модулей
vim.validate()
?- Добавить материал о модульных тестах? Я знаю, что Neovim использует фреймворк busted, но я не знаю, как использовать его для плагинов.
- Лучшие практики? Я не Lua мастер, поэтому не знаю
- Как использовать пакеты LuaRocks (wbthomason/packer.nvim?)
Разное
vim.loop
vim.loop
– это модуль, который предоставляет API LibUV . Некоторые ресурсы:
- Official documentation for LibUV
- Luv documentation
- teukka.tech – Using LibUV in Neovim
Также смотрите:
:help vim.loop
vim.lsp
vim.lsp
– это модуль, который управляет встроенным клиентом LSP. Репозиторий neovim/nvim-lspconfig содержит конфигурации по умолчанию для популярных языковых серверов.
Поведение клиента можно настроить с помощью обработчиков “lsp-handlers”. Для дополнительной информации:
:help lsp-handler
- neovim/neovim#12655
- How to migrate from diagnostic-nvim
Вы также можете взглянуть на плагины, построенные вокруг клиента LSP:
- nvim-lua/completion-nvim
- RishabhRD/nvim-lsputils
Также смотрите:
:help lsp
vim.treesitter
vim.treesitter
– это модуль, который управляет интеграцией библиотеки Tree-sitter в Neovim. Если вы хотите узнать больше о Tree-sitter, вам может быть интересна эта презентация (38:37).
Организация nvim-treeitter размещает различные плагины, использующие преимущества библиотеки.
See also:
:help lua-treesitter
Транспайлеры
Одним из преимуществ использования Lua является то, что вам фактически не нужно писать код Lua! Для этого языка доступно множество транспайлеров.
- Moonscript
Вероятно, один из самых известных транспилеров для Lua. Добавляет множество удобных функций, таких как классы, списковое включение или функциональные литералы. Плагин svermeulen/nvim-moonmaker позволяет писать плагины и настройку Neovim непосредственно в Moonscript.
- Fennel
Lisp, который компилируется в Lua. Вы можете написать конфигурацию и плагины для Neovim в Fennel с помощью плагина Olical/aniseed. Кроме того, плагин Olical/conjure предоставляет интерактивную среду разработки, которая поддерживает Fennel (среди других языков).
Другие интересные проекты:
- TypeScriptToLua/TypeScriptToLua
- teal-language/tl
- Haxe
- SwadicalRag/wasm2lua
- hengestone/lua-languages
2.15. Скрипты Lua
1. Введение
Double Commander может выполнять скрипты Lua с помощью команды cm_ExecuteScript.
Параметры скриптов должны передаваться как есть, без экранирования (без кавычек или “”), для этого необходимо использовать переменную %”0: например, %"0%p0
для файла под курсором вместо %p0
, %"0%D
для текущего каталога вместо %D
и так далее.
Для получения списка всех выделенных файлов мы можем использовать %LU или внутренние команды cm_SaveSelectionToFile или cm_CopyFullNamesToClip.
Также на языке Lua можно писать информационные плагины, примеры можно найти в папке программы (plugins/wdx/scripts).
В Вики есть страница, посвящённая написанию плагинов.
Ограничения: поддерживаются только следующие типы данных
- ft_numeric_32 – 32-битное целое число со знаком;
- ft_numeric_64 – 64-битное целое число со знаком;
- ft_numeric_floating – число с плавающей запятой;
- ft_boolean – логический (булев) тип: true или false;
- ft_multiplechoice – значение, допускающее ограниченное количество вариантов выбора;
- ft_string – текстовая строка (используйте UTF-8);
- ft_fulltext – полный текст (многострочный текст), используется только для поиска с помощью плагинов;
- ft_datetime – для даты/времени: возвращаемая дата будет сконвертирована в отформатированную строку даты и времени (значение зависит от ваших региональных настроек).
Примечание: При сохранении скриптов используйте кодировку UTF-8 без BOM.
Более подробную информацию о языке программирования Lua можно найти на официальном веб-сайте Lua.
(наверх)
2. Необходимая DLL
Чтобы использовать скрипты Lua, нам требуется интерпретатор Lua, Double Commander поддерживает версии 5.1 – 5.4.
По умолчанию DC ищет файл с именем lua5.1.dll (Windows), liblua5.1.so.0 (Unix или GNU/Linux) или liblua5.1.dylib (macOS(*)) в своём каталоге и в системном. Мы можем изменить имя файла (и путь) в параметре Библиотека Lua.
Мы можем использовать DLL-файл проекта LuaJIT.
LuaJIT сочетает в себе высокоскоростной интерпретатор, написанный на ассемблере, и JIT-компилятор. Также мы получаем библиотеку FFI: с её помощью в скриптах можно будет вызывать внешние функции C и использовать структуры данных C.
Дистрибутивы DC для Windows включают в себя DLL-файл Lua (в DC 0.9.7 и новее из проекта LuaJIT), в иных случаях мы можем найти и установить его с помощью менеджера пакетов или скомпилировать. Если используем 64-битную версию DC, то DLL-файл также должен быть 64-битным.
(наверх)
3. Библиотеки функций Double Commander
Double Commander предлагает несколько библиотек функций для скриптов Lua, список представлен ниже.
Список библиотек | ||
---|---|---|
Библиотека | Имя функции | Краткое описание |
Double Commander |
DC | Специфические функции Double Commander |
System |
SysUtils | Различные системные функции |
Clipboard |
Clipbrd | Различные функции для работы с буфером обмена |
Dialogs |
Dialogs | Взаимодействие с пользователем |
UTF-8 |
LazUtf8 | Строковые функции для работы с UTF-8 |
OS |
OS | Функции, связанные с операционной системой |
(наверх)
3.1. Библиотека DC
Эта библиотека содержит специфические функции Double Commander, все функции представлены в таблице DC
.
Библиотека DC | |
---|---|
Функция | Описание |
DC.LogWrite |
DC.LogWrite(Message, MsgType, bForce, bLogFile) Пишет сообщение в окно протокола. Описание:
|
DC.CurrentPanel |
iPanel = DC.CurrentPanel() Получить активную панель: возвращает 0, если активна левая панель, или 1, если правая. DC.CurrentPanel(iPanel) Сделать активной: левую панель, если iPanel равно 0, или правую, если равно 1. |
DC.ExecuteCommand |
DC.ExecuteCommand(Command, Param1, Param2,…,ParamX) Вызывает внутреннюю команду Double Commander Command с параметрами Param1..ParamX. Мы можем подставить столько Param…, сколько поддерживает команда Command. |
Примечание: Скрипты работают в основном потоке Double Commander, поэтому иногда последовательное выполнение команд для навигации может не сработать (например, большие каталоги, медленный диск), в этом случае попробуйте отключить Загружать список файлов в отдельном потоке в настройках.
(наверх)
3.1.1. Пример с DC.ExecuteCommand
В этом примере мы написали простой скрипт, который сделает следующее:
- перенесёт фокус на правую панель
- закроет все открытые вкладки
- перейдёт в указанную папку
- перенесёт фокус на левую панель
- закроет все открытые вкладки
- перейдёт в указанный каталог
- откроет новую вкладку
- перейдёт в указанный каталог
-- 1. Перенести фокус на правую панель. DC.ExecuteCommand("cm_FocusSwap", "side=right") -- 2. Закрыть все вкладки. DC.ExecuteCommand("cm_CloseAllTabs") -- 3. Перейти в указанный каталог. DC.ExecuteCommand("cm_ChangeDir", "E:\FakeKey\Documents\Music") -- 4. Перенести фокус на левую панель. DC.ExecuteCommand("cm_FocusSwap", "side=left") -- 5. Закрыть все вкладки. DC.ExecuteCommand("cm_CloseAllTabs") -- 6. Перейти в указанный каталог. DC.ExecuteCommand("cm_ChangeDir", "C:\Users\Public\Music") -- 7. Открыть новую вкладку. DC.ExecuteCommand("cm_NewTab") -- 8. Перейти в указанный каталог. DC.ExecuteCommand("cm_ChangeDir", "E:\VirtualMachines\ShareFolder")
Для выполнения нашего скрипта мы можем создать кнопку на панели инструментов с внутренней командой cm_ExecuteScript.
Предположим, наш скрипт E:scriptsluamusic.lua
, мы можем настроить эту кнопку следующим образом:
Кроме того, для редактирования наших скриптов мы можем использовать встроенный редактор Double Commander.
Если файл имеет расширение .lua
, то он будет распознан редактором и открыт с подсветкой синтаксиса, специфичного для языка Lua:
(наверх)
3.2. Библиотека System
Эта библиотека содержит различные системные функции, все они представлены в таблице SysUtils
.
Библиотека System | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Функция | Описание | ||||||||||
SysUtils.Sleep |
SysUtils.Sleep(Milliseconds) Приостанавливает выполнение скрипта на указанное количество миллисекунд (Milliseconds), выполнение сценария будет продолжено после истечения указанного времени. |
||||||||||
SysUtils.GetTickCount |
SysUtils.GetTickCount() Возвращает количество интервалов времени (тиков) с момента старта системы. Это полезно для измерения времени, но не следует делать каких-либо предположений относительно количества времени в интервале между тиками. |
||||||||||
SysUtils.FileExists |
bFlagExists = SysUtils.FileExists(FileName) Проверяет, существует ли в файловой системе определённый файл. FileExists возвращает |
||||||||||
SysUtils.DirectoryExists |
bFlagExists = SysUtils.DirectoryExists(Directory) Проверяет существование Directory в файловой системе и что это действительно каталог. Если это так, то функция возвращает |
||||||||||
SysUtils.FileGetAttr |
Attr = SysUtils.FileGetAttr(FileName) Возвращает в Attr атрибуты файла FileName. Подробное описание возвращаемого значения смотрите здесь. |
||||||||||
SysUtils.FindFirst |
Handle, FindData = SysUtils.FindFirst(Path) Ищет файлы, совпадающие с Path (можно использовать маски). Если файл не найден, Handle будет равно В случае успеха функция вернёт дескриптор поиска Handle для последующих вызовов Таблица FindData содержит информацию о найденном файле или каталоге. Поля таблицы FindData:
|
||||||||||
SysUtils.FindNext |
Result, FindData = SysUtils.FindNext(Handle) Находит следующее вхождение поиска, инициированного FindFirst, Handle – дескриптор поиска, возвращённый функцией FindFirst. В случае успеха функция вернёт Result не равный Замечание: За последним вызовом |
||||||||||
SysUtils.FindClose |
SysUtils.FindClose(Handle) Завершает серию вызовов Вызов этой функции абсолютно необходим, иначе могут возникнуть большие утечки памяти. |
||||||||||
SysUtils.CreateDirectory |
bResult = SysUtils.CreateDirectory(Directory) Создаёт цепочку каталогов, Directory – полный путь каталога. Возвращает |
||||||||||
SysUtils.CreateHardLink |
bResult = SysUtils.CreateHardLink(Path, LinkName) Создаёт жёсткую ссылку LinkName на файл Path. Возвращает |
||||||||||
SysUtils.CreateSymbolicLink |
bResult = SysUtils.CreateSymbolicLink(Path, LinkName) Создаёт символьную ссылку LinkName на файл Path. Возвращает |
||||||||||
SysUtils.ReadSymbolicLink |
sTarget = SysUtils.ReadSymbolicLink(LinkName, Recursive) Читает имя файла, на который указывает символьная ссылка LinkName. Если Recursive равно Возвращает путь, на который указывает символьная ссылка LinkName, или пустую строку, если ссылка недействительна или файл, на который она указывает, не существует и значение Recursive равно |
||||||||||
SysUtils.ExtractFileName |
sName = SysUtils.ExtractFileName(FileName) Возвращает имя файла из полного (имя+путь) имени файла. Имя файла содержит все символы после последнего символа разделителя каталогов (“/” или “”) или буквы диска. |
||||||||||
SysUtils.ExtractFilePath |
sPath = SysUtils.ExtractFilePath(FileName) Возвращает путь из полного имени файла (включая букву диска). Путь содержит все символы до последнего символа разделителя каталогов (“/” или “”), включая сам разделитель каталогов. |
||||||||||
SysUtils.ExtractFileDir |
sDir = SysUtils.ExtractFileDir(FileName) Возвращает только каталог из FileName, включая букву диска. Результат НЕ содержит разделитель каталогов в конце, в отличие от |
||||||||||
SysUtils.ExtractFileDrive |
sDrive = SysUtils.ExtractFileDrive(FileName) Возвращает диск из полного имени файла. Обратите внимание, некоторые операционные системы не поддерживают буквы дисков. |
||||||||||
SysUtils.ExtractFileExt |
sExt = SysUtils.ExtractFileExt(FileName) Возвращает расширение файла (все символы после последней точки, включая сам символ “.”). |
||||||||||
SysUtils.GetAbsolutePath |
sName = SysUtils.GetAbsolutePath(FileName, BaseDirectory) Возвращает абсолютный (полный) путь к файлу:
|
||||||||||
SysUtils.GetRelativePath |
sName = SysUtils.GetRelativePath(FileName, BaseDirectory) Возвращает имя файла относительно указанного каталога:
Если FileName и BaseDirectory содержат одно и то же значение, функция вернёт пустую строку (“”). |
||||||||||
SysUtils.MatchesMask |
bResult = SysUtils.MatchesMask(FileName, Mask, MaskOptions) Возвращает MaskOptions (необязательный параметр, по умолчанию 0) задаётся как сумма следующих значений:
|
||||||||||
SysUtils.MatchesMaskList |
bResult = SysUtils.MatchesMaskList(FileName, MaskList, Separator, MaskOptions) Возвращает Separator и MaskOptions (см. выше) – необязательные параметры. |
||||||||||
SysUtils.PathDelim |
SysUtils.PathDelim Символ, используемый в текущей операционной системе для разделения имён каталогов в полном имени файла. В системах Unix/Linux разделителем каталогов будет “/” и “” в Windows. |
(наверх)
3.2.1. Описание возвращаемого значения SysUtils.FileGetAttr
FileGetAttr
возвращает атрибуты FileName в виде ИЛИ-совокупности следующих констант:
Константы, используемые в возвращаемом значении SysUtils.FileGetAttr | |
---|---|
Значение | Описание |
0x00000001 |
Файл только для чтения. |
0x00000002 |
Скрытый файл. В Unix/Linux это значит, что имя файла начинается с точки. |
0x00000004 |
Системный файл. В Unix/Linux это может быть символьное или блочное физическое устройство, именованный канал (FIFO). |
0x00000008 |
Метка тома (означает, что файл содержит идентификатор (ID) тома). Только для DOS/Windows 9x на обычной файловой системе FAT (не VFAT или FAT32). |
0x00000010 |
Каталог. |
0x00000020 |
Архивный. Не используется в Unix/Linux. |
0x00000400 |
Символьная ссылка. |
Примечание: В случае ошибки функция возвращает -1. |
Смотрите пример в следующей секции.
(наверх)
3.2.2. Пример с SysUtils.FileGetAttr
Пример скрипта ниже использует SysUtils.FileGetAttr
.
Если параметр окажется каталогом, то скрипт откроет его в новой вкладке активной панели и переключится на неё.
local params = {...} local myfileattr if #params == 1 then -- Мы получили один параметр? myfileattr = SysUtils.FileGetAttr(params[1]) if myfileattr > 0 then -- Получили значение атрибута? if math.floor(myfileattr / 0x00000010) % 2 ~= 0 then -- 0x00000010 присутствует? Тогда это каталог. DC.ExecuteCommand("cm_NewTab") DC.ExecuteCommand("cm_ChangeDir", params[1]) end end end
В приведённом выше примере params[1] является первым параметром, переданным скрипту.
При использовании внутренней команды cm_ExecuteScript это будет первый параметр, переданный после имени файла скрипта.
Итак, в нашем примере мы можем создать на панели инструментов кнопку, как показано ниже:
В этом примере скрипту будет передан параметр %”0%p: имя выделенного в активной панели файла или папки, без кавычек.
(наверх)
3.2.3. Пример использования FindFirst, FindNext и FindClose
В следующем примере мы просканируем содержимое каталога, переданного первым параметром, и сохраним полученные данные в текстовый файл с именем, переданным вторым параметром.
Это даст нам хорошее представление об использовании FindFirst
, FindNext
и FindClose
.
local params = {...} if #params == 2 then -- У нас есть два параметра? local Result = nil local OutputFile = nil OutputFile = io.output(params[2]) local Handle,FindData = SysUtils.FindFirst(params[1] .. "\*") if Handle ~= nil then repeat io.write(FindData.Name .. "r") io.write(FindData.Size .. "r") io.write("---------------r") Result,FindData = SysUtils.FindNext(Handle) until Result == nil SysUtils.FindClose(Handle) io.close(OutputFile) end end
В приведённом выше примере нам нужно передать скрипту два параметра:
- params[1] – каталог, содержимое которого мы хотим получить
- params[2] – имя файла для сохранения результата работы скрипта
Настраиваем кнопку на панели инструментов с внутренней командой cm_ExecuteScript и передаём параметрами всё необходимое:
В этом примере скрипту в качестве первого параметра будет передано %”0%Ds: имя каталога активной панели, без кавычек.
(наверх)
3.3. Библиотека Clipboard
Double Commander предоставляет скриптам Lua библиотеку функций для работы с буфером обмена.
Библиотека Clipboard | |
---|---|
Функция | Описание |
Clipbrd.Clear |
Clipbrd.Clear() Очищает содержимое буфера обмена. |
Clipbrd.GetAsText |
StringVar = Clipbrd.GetAsText() Получает содержимое буфера обмена как строку StringVar. |
Clipbrd.SetAsText |
Clipbrd.SetAsText(StringVar) Помещает в буфер обмена строку текста StringVar. |
Clipbrd.SetAsHtml |
Clipbrd.SetAsHtml(Html) Добавляет в буфер обмена текст Html в формате HTML (формат буфера обмена Содержимое может быть вставлено в приложения, поддерживающие этот формат буфера обмена, такие как MS Word, LO Writer и другие. Правильным будет сохранять данные с помощью и Например, у нас может быть следующее:
Если мы запустим Блокнот и попытаемся вставить текст из буфера обмена, то будет вставлен текст, скопированный с помощью |
(наверх)
3.3.1. Пример работы с буфером обмена
В следующем примере используются три функции, связанные с буфером обмена: Clear
, GetAsText
и SetAsText
.
Это относительно большой скрипт, но было бы неплохо собрать вместе несколько функций, с которыми мы познакомились ранее.
Предполагается, что в нашей активной панели открыт каталог с большим количеством текстовых файлов.
Также предполагается, что в настоящее время в буфере обмена одно слово и что скрипт в качестве единственного параметра получит текущий каталог активной панели.
Скрипт будет сканировать папку, читать содержимое файлов, один за другим, и искать слово из буфера обмена.
Затем имена файлов, которые содержат это словом, будут скопированы в буфер обмена.
Далее скрипт вызовет внутреннюю команду cm_LoadSelectionFromClip и все эти файлы будут выделены.
Кроме того, в конце вернём обратно в буфер обмена слово, которое мы искали.
local params = {...} local Result = nil local myfileattr local bFound = false local sCompleteFilename = "" local InputFile = nil local line = "" local iPosS local iPosE local sFileToSelect = "" local sSearchString = "" if #params == 1 then -- Мы получили наш параметр? sSearchString = Clipbrd.GetAsText() -- Получаем выражение для поиска. Clipbrd.Clear() -- Очищаем буфер обмена. DC.ExecuteCommand("cm_MarkUnmarkAll") -- Убедимся, что ничего не выделено. -- Начинаем сканировать все файлы нашего каталога один за другим. local Handle,FindData = SysUtils.FindFirst(params[1] .. "\*") if Handle ~= nil then repeat sCompleteFilename = params[1] .. "\" .. FindData.Name myfileattr = SysUtils.FileGetAttr(sCompleteFilename) if myfileattr > 0 then -- Получили значение атрибута? -- Нам нужен файл, не каталог! if math.floor(myfileattr / 0x00000010) % 2 == 0 then -- Начинаем читать файл построчно, до конца файла ИЛИ до совпадения. InputFile = io.open(sCompleteFilename, "r") bFound = false while bFound == false do line = InputFile:read() if line == nil then break end iPosS, iPosE = string.find(line, sSearchString) if iPosS ~= nil then bFound = true end end if bFound == true then sFileToSelect = sFileToSelect .. FindData.Name .. "n" end io.close(InputFile) end end Result,FindData = SysUtils.FindNext(Handle) until Result == nil SysUtils.FindClose(Handle) end -- Если что-то нашли, то выделяем! if sFileToSelect ~= "" then Clipbrd.SetAsText(sFileToSelect) DC.ExecuteCommand("cm_LoadSelectionFromClip") end Clipbrd.SetAsText(sSearchString) -- Восстанавливаем содержимое буфера обмена. end
(наверх)
3.4. Библиотека Dialogs
Эта библиотека позволяет нашим скриптам взаимодействовать с пользователем, показывая сообщения, выдавая запросы и т.д.
Библиотека Dialogs | |
---|---|
Функция | Описание |
Dialogs.MessageBox |
ButPressed = Dialogs.MessageBox(Message, Title, ButFlags) Показывает окно сообщения, функция вернёт нажатую пользователем кнопку. Описание:
|
Dialogs.InputQuery |
bAck, sAnswer = Dialogs.InputQuery(Title, Msg, bMask, sDefault) Показывает диалоговое окно с полем ввода текста. Описание:
|
Dialogs.InputListBox |
sAnswer = Dialogs.InputListBox(Title, Msg, Items, sDefault) Показывает диалоговое окно с возможностью выбора из списка элементов. Описание:
|
(наверх)
3.4.1. Кнопки Dialogs.MessageBox
Кнопки, отображаемые в окне Dialogs.MessageBox
, задаются одним из следующих значений:
Константы для кнопок в Dialogs.MessageBox | |
---|---|
Значение константы | Отображаемые кнопки, слева направо |
0x0000 |
|
0x0001 |
|
0x0002 |
|
0x0003 |
|
0x0004 |
|
0x0005 |
|
(наверх)
3.4.2. Стиль окна Dialogs.MessageBox
Стиль окна Dialogs.MessageBox
задаётся одним из следующих значений:
Константы, задающие значок и стиль окна Dialogs.MessageBox | |
---|---|
Значение константы | Стиль окна |
0x0040 |
Информационное окно |
0x0030 |
Окно предупреждения |
0x0020 |
Окно подтверждения |
0x0010 |
Окно ошибки |
(наверх)
3.4.3. Кнопка по умолчанию в Dialogs.MessageBox
Кнопка по умолчанию (активная) в Dialogs.MessageBox
задаётся одним из следующих значений:
Константы, задающие кнопку по умолчанию в Dialogs.MessageBox | |
---|---|
Значение константы | Кнопка по умолчанию |
0x0000 |
Первая кнопка слева |
0x0100 |
Вторая кнопка слева |
0x0200 |
Третья кнопка слева |
(наверх)
3.4.4. Возвращаемое значение Dialogs.MessageBox
Число, возвращаемое функцией Dialogs.MessageBox
, указывает на кнопку, которую нажал пользователь, и может быть одним из следующих:
Возвращаемое значение Dialogs.MessageBox | |
---|---|
Значение константы | Нажатая кнопка |
0x0000 |
Кнопка не нажата |
0x0001 |
|
0x0002 |
|
0x0003 |
|
0x0004 |
|
0x0005 |
|
0x0006 |
|
0x0007 |
Примечание: Если закрыть окно, нажав крестик “x” в углу окна или клавишу Esc, то функция вернёт 0x0002
, как нажатие кнопки Отмена.
(наверх)
3.4.5. Пример использования Dialogs.MessageBox
Маленький скрипт, использующий Dialogs.MessageBox
:
-- Отображаемые кнопки MB_OK = 0x0000 MB_OKCANCEL = 0x0001 MB_ABORTRETRYIGNORE = 0x0002 MB_YESNOCANCEL = 0x0003 MB_YESNO = 0x0004 MB_RETRYCANCEL = 0x0005 -- Стиль окна MB_ICONINFORMATION = 0x0040 MB_ICONWARNING = 0x0030 MB_ICONQUESTION = 0x0020 MB_ICONERROR = 0x0010 -- Кнопка по умолчанию MB_DEFBUTTON1 = 0x0000 MB_DEFBUTTON2 = 0x0100 MB_DEFBUTTON3 = 0x0200 -- Возвращаемое значение mrNone = 0x0000 mrOK = 0x0001 mrCancel = 0x0002 mrAbort = 0x0003 mrRetry = 0x0004 mrIgnore = 0x0005 mrYes = 0x0006 mrNo = 0x0007 ButFlags = MB_YESNO + MB_ICONQUESTION + MB_DEFBUTTON2 ButPressed = Dialogs.MessageBox("Хотите завершить?", "Вопрос", ButFlags) if ButPressed == mrYes then DC.ExecuteCommand("cm_Exit") end
(наверх)
3.4.6. Пример использования Dialogs.InputQuery
Маленький скрипт, использующий Dialogs.InputQuery
:
bAck, sAnswer = Dialogs.InputQuery("Идентификация", "Введите ваше имя:", false, "Иван") if bAck == true then Dialogs.MessageBox("Привет, " .. sAnswer .. "!", "Добро пожаловать!", 0x0040) end
(наверх)
3.5. Библиотека UTF-8
Эта библиотека обеспечивает базовую поддержку кодировки UTF-8.
Все функции представлены в таблице LazUtf8
.
Библиотека UTF-8 | |
---|---|
Функция | Описание |
LazUtf8.Pos |
Result = LazUtf8.Pos(SearchText, SourceText, Offset) Поиск подстроки в строке, начиная с определённой позиции. Поиск чувствителен к регистру. Возвращает позицию первого вхождения подстроки SearchText в строке SourceText, поиск начинается с позиции Offset (по умолчанию 1). Если в SourceText не найдено совпадение с SearchText после заданного Offset, то функция возвращает ноль. |
LazUtf8.Copy |
Result = LazUtf8.Copy(Source, Index, Count) Копирует (извлекает) часть строки. Функция возвращает Count символов из строки Source, начиная с позиции Index. Если Count больше длины строки Source, то результат усекается. Если Index больше длины строки Source, то возвращается пустая строка. |
LazUtf8.Length |
Result = LazUtf8.Length(String) Возвращает количество символов в строке с учётом кодировки UTF-8. |
LazUtf8.UpperCase |
Result = LazUtf8.UpperCase(String) Возвращает строку, в которой все буквы в нижнем регистре (строчные) заменены на буквы в верхнем регистре (прописные или заглавные). |
LazUtf8.LowerCase |
Result = LazUtf8.LowerCase(String) Возвращает строку, в которой все буквы в верхнем регистре (прописные или заглавные) заменены на буквы в нижнем регистре (строчные). |
LazUtf8.ConvertEncoding |
Result = LazUtf8.ConvertEncoding(String, FromEnc, ToEnc) Конвертирует кодировку String из FromEnc в ToEnc. Список поддерживаемых значений кодировок:
Специальные значения кодировок (примеры). В Windows (английкий или русский языки):
В Linux (английкий или русский языки):
|
(наверх)
3.6. Библиотека OS
Эта библиотека содержит функции, связанные с операционной системой, в которой запущен Double Commander.
Список доступных функций:
Библиотека OS | |
---|---|
Функция | Описание |
os.execute |
ResultCode = os.execute(Command) Выполнить Command, аналогично введённой в командной строке, будет возвращён код завершения. Command может быть:
|
os.tmpname |
TempFileName = os.tmpname() Возвращает строку с именем файла, который может быть использован в качестве временного файла. |
os.remove |
bResult, ErrorStr, ErrorNum = os.remove(FileName) Удалить файл с именем FileName. Если удалось, функция вернёт Если не удалось, функция вернёт:
|
os.rename |
bResult, ErrorStr, ErrorNum = os.rename(OldName, NewName) Переименовать файл OldName в NewName. Если удалось, функция вернёт Если не удалось, функция вернёт:
|
os.getenv |
Value = os.getenv(VariableName) Получить в Value значение переменной окружения VariableName. |
os.setenv |
os.setenv(VariableName, Value) Добавить или изменить переменную окружения VariableName. В случае ошибки функция возвращает -1. |
os.unsetenv |
os.unsetenv(VariableName) Удалить переменную окружения VariableName. В случае ошибки функция возвращает -1. |
(наверх)