nvim config updates
This commit is contained in:
parent
559939e2f4
commit
adc494721a
20 changed files with 1108 additions and 30 deletions
|
|
@ -1,5 +1,5 @@
|
|||
-- Useful tools for lua
|
||||
Utils = require('utils')
|
||||
Util = require('utils')
|
||||
|
||||
-- Load basic configuration
|
||||
require("config")
|
||||
|
|
|
|||
|
|
@ -36,3 +36,18 @@ for ext, ft in pairs(filetypes) do
|
|||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local spell_filetypes = {
|
||||
'markdown',
|
||||
'text',
|
||||
'gitcommit'
|
||||
}
|
||||
|
||||
for _, ext in ipairs(spell_filetypes) do
|
||||
vim.api.nvim_create_autocmd('FileType', {
|
||||
pattern = ext,
|
||||
callback = function()
|
||||
vim.cmd 'setlocal spell'
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@ map({ 'n', 'v' }, '<A-u>', ':bp<CR>', { silent = true })
|
|||
map({ 'n', 'v' }, '<A-i>', ':bn<CR>', { silent = true })
|
||||
|
||||
-- Remap for dealing with word wrap
|
||||
map({ 'n', 'v' }, 'k', 'v:count == 0 ? "gk" : "k"', { expr = true, silent = true })
|
||||
map({ 'n', 'v' }, 'j', 'v:count == 0 ? "gj" : "j"', { expr = true, silent = true })
|
||||
map({ 'n', 'v' }, 'k', 'v:count == 0 ? "gk" : "k"', { expr = true, silent = true })
|
||||
|
||||
map({ 'n', 'v' }, 'о', 'v:count == 0 ? "gj" : "j"', { expr = true, silent = true })
|
||||
map({ 'n', 'v' }, 'л', 'v:count == 0 ? "gk" : "k"', { expr = true, silent = true })
|
||||
|
||||
-- Duplicate leader feats to gradually move to
|
||||
|
||||
|
|
@ -27,6 +30,11 @@ map({ 'n', 'v' }, '<leader>Y', '"+y$')
|
|||
map({ 'n', 'v' }, '<leader>p', '"+p<ESC>')
|
||||
map({ 'n', 'v' }, '<leader>P', '"+P<ESC>')
|
||||
|
||||
map({ 'n', 'v' }, '<leader>н', '"+y')
|
||||
map({ 'n', 'v' }, '<leader>н', '"+y$')
|
||||
map({ 'n', 'v' }, '<leader>з', '"+p<ESC>')
|
||||
map({ 'n', 'v' }, '<leader>З', '"+P<ESC>')
|
||||
|
||||
-- buffer size
|
||||
map({ 'n', 'v' }, '<leader>n', '<C-w>10<')
|
||||
map({ 'n', 'v' }, '<leader>m', '<C-w>10>')
|
||||
|
|
|
|||
|
|
@ -35,4 +35,28 @@ vim.o.timeoutlen = 300
|
|||
vim.o.splitbelow = true
|
||||
vim.o.splitright = true
|
||||
|
||||
vim.diagnostic.config({ virtual_text = true })
|
||||
vim.diagnostic.config({
|
||||
virtual_text = true,
|
||||
signs = true,
|
||||
virtual_lines = false,
|
||||
update_in_insert = true,
|
||||
float = {
|
||||
source = 'if_many',
|
||||
format = function(d)
|
||||
return ("[%d:%d] %s"):format(
|
||||
d.lnum + 1,
|
||||
d.end_col + 1,
|
||||
d.message
|
||||
)
|
||||
end,
|
||||
}
|
||||
})
|
||||
|
||||
-- fold related stuff
|
||||
vim.o.foldcolumn = '0'
|
||||
vim.o.foldlevel = 99
|
||||
vim.o.foldlevelstart = 99
|
||||
vim.o.foldenable = true
|
||||
|
||||
-- spelling
|
||||
vim.o.spelllang = 'en,ru'
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ local servers = {
|
|||
svls = {},
|
||||
ts_ls = {},
|
||||
yamlls = require('lsp.yamlls'),
|
||||
-- codebook = {}
|
||||
}
|
||||
|
||||
-- vim.lsp.set_log_level('info')
|
||||
vim.lsp.set_log_level('DEBUG')
|
||||
|
||||
local on_attach = function(_, bufnr)
|
||||
local nmap = function(keys, func)
|
||||
|
|
@ -27,26 +28,33 @@ local on_attach = function(_, bufnr)
|
|||
end
|
||||
|
||||
nmap('<leader>rn', vim.lsp.buf.rename)
|
||||
nmap('<leader>ca', vim.lsp.buf.code_action)
|
||||
nmap('<leader>w', vim.lsp.buf.code_action)
|
||||
|
||||
nmap('gd', require('telescope.builtin').lsp_definitions)
|
||||
nmap('gr', require('telescope.builtin').lsp_references)
|
||||
nmap('gt', require('telescope.builtin').lsp_type_definitions)
|
||||
local telescope_func = function(name)
|
||||
return function()
|
||||
require('telescope.builtin')[name]({
|
||||
show_line = false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
nmap('gd', telescope_func("lsp_definitions"))
|
||||
nmap('gr', telescope_func("lsp_references"))
|
||||
nmap('gt', telescope_func("lsp_type_definitions"))
|
||||
|
||||
-- nmap('gd', vim.lsp.buf.definition)
|
||||
-- nmap('gr', vim.lsp.buf.references)
|
||||
-- nmap('gt', vim.lsp.buf.type_definition)
|
||||
|
||||
nmap('<leader>sd', require('telescope.builtin').diagnostics)
|
||||
nmap('<leader>ds', require('telescope.builtin').lsp_document_symbols)
|
||||
nmap('<leader>as', require('telescope.builtin').lsp_dynamic_workspace_symbols)
|
||||
nmap('<leader>sd', telescope_func("diagnostics"))
|
||||
nmap('<leader>ds', telescope_func("lsp_document_symbols"))
|
||||
nmap('<leader>as', telescope_func("lsp_dynamic_workspace_symbols"))
|
||||
|
||||
nmap("<leader>i", vim.diagnostic.open_float)
|
||||
nmap('K', vim.lsp.buf.hover)
|
||||
nmap('<C-k>', vim.lsp.buf.signature_help)
|
||||
|
||||
nmap('gI', require('telescope.builtin').lsp_implementations)
|
||||
-- nmap('gI', vim.lsp.buf.implementation)
|
||||
nmap('gD', vim.lsp.buf.declaration)
|
||||
|
||||
nmap('<leader>F', vim.lsp.buf.format)
|
||||
|
|
|
|||
67
.config/nvim/lua/plugins/diffview.lua
Normal file
67
.config/nvim/lua/plugins/diffview.lua
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
return {
|
||||
'sindrets/diffview.nvim',
|
||||
config = function()
|
||||
vim.keymap.set('n', '<leader>dv', ':DiffviewOpen<CR>', { silent = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "DiffviewViewEnter",
|
||||
callback = function()
|
||||
vim.keymap.del('n', 'Ā')
|
||||
|
||||
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if vim.api.nvim_buf_is_loaded(buf)
|
||||
and vim.api.nvim_buf_get_name(buf) == ""
|
||||
and vim.bo[buf].buftype == "" then
|
||||
vim.api.nvim_buf_delete(buf, { force = true })
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "DiffviewViewLeave",
|
||||
callback = function()
|
||||
vim.keymap.set('n', 'Ā', ':NvimTreeToggle<CR>', { silent = true })
|
||||
end,
|
||||
})
|
||||
|
||||
local actions = require("diffview.actions")
|
||||
require('diffview').setup({
|
||||
view = {
|
||||
merge_tool = {
|
||||
disable_diagnostics = false
|
||||
},
|
||||
},
|
||||
keymaps = {
|
||||
view = {
|
||||
-- The `view` bindings are active in the diff buffers, only when the current
|
||||
-- tabpage is a Diffview.
|
||||
{ "n", "<tab>", actions.select_next_entry, { desc = "Open the diff for the next file" } },
|
||||
{ "n", "<s-tab>", actions.select_prev_entry, { desc = "Open the diff for the previous file" } },
|
||||
{ "n", "[F", actions.select_first_entry, { desc = "Open the diff for the first file" } },
|
||||
{ "n", "]F", actions.select_last_entry, { desc = "Open the diff for the last file" } },
|
||||
{ "n", "gf", actions.goto_file_edit, { desc = "Open the file in the previous tabpage" } },
|
||||
{ "n", "<C-w><C-f>", actions.goto_file_split, { desc = "Open the file in a new split" } },
|
||||
{ "n", "<C-w>gf", actions.goto_file_tab, { desc = "Open the file in a new tabpage" } },
|
||||
{ "n", "Ā", actions.toggle_files, { desc = "Toggle the file panel." } },
|
||||
{ "n", "g<C-x>", actions.cycle_layout, { desc = "Cycle through available layouts." } },
|
||||
{ "n", "<M-p>", actions.prev_conflict, { desc = "In the merge-tool: jump to the previous conflict" } },
|
||||
{ "n", "<M-n>", actions.next_conflict, { desc = "In the merge-tool: jump to the next conflict" } },
|
||||
{ "n", "gh", actions.conflict_choose("ours"), { desc = "Choose the OURS version of a conflict" } },
|
||||
{ "n", "gl", actions.conflict_choose("theirs"), { desc = "Choose the THEIRS version of a conflict" } },
|
||||
{ "n", "gb", actions.conflict_choose("base"), { desc = "Choose the BASE version of a conflict" } },
|
||||
{ "n", "ga", actions.conflict_choose("all"), { desc = "Choose all the versions of a conflict" } },
|
||||
{ "n", "gx", actions.conflict_choose("none"), { desc = "Delete the conflict region" } },
|
||||
{ "n", "gH", actions.conflict_choose_all("ours"), { desc = "Choose the OURS version of a conflict for the whole file" } },
|
||||
{ "n", "gL", actions.conflict_choose_all("theirs"), { desc = "Choose the THEIRS version of a conflict for the whole file" } },
|
||||
{ "n", "gB", actions.conflict_choose_all("base"), { desc = "Choose the BASE version of a conflict for the whole file" } },
|
||||
{ "n", "gA", actions.conflict_choose_all("all"), { desc = "Choose all the versions of a conflict for the whole file" } },
|
||||
{ "n", "gX", actions.conflict_choose_all("none"), { desc = "Delete the conflict region for the whole file" } },
|
||||
},
|
||||
file_panel = {
|
||||
{ "n", "Ā", actions.toggle_files, { desc = "Toggle the file panel" } },
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
return {
|
||||
'RRethy/vim-illuminate', config = function()
|
||||
require('illuminate').configure({
|
||||
delay = 0,
|
||||
delay = 100,
|
||||
filetypes_denylist = {
|
||||
'NvimTree',
|
||||
'TelescopePrompt',
|
||||
}
|
||||
})
|
||||
end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ return {
|
|||
{ 'norcalli/nvim-colorizer.lua', opts={ '*' }, dependencies = { 'navarasu/onedark.nvim' } },
|
||||
{ 'wakatime/vim-wakatime', event = 'VeryLazy' },
|
||||
{ 'williamboman/mason.nvim', opts = {} },
|
||||
{ dir = '~/.local/share/nvim/preseed/' }, -- 'https://git.netizen.se/vim-preseed',
|
||||
{ 'hky/vim-preseed' }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
return {
|
||||
'nvim-telescope/telescope.nvim',
|
||||
branch = '0.1.x',
|
||||
dependencies = {
|
||||
'navarasu/onedark.nvim',
|
||||
'nvim-lua/plenary.nvim',
|
||||
'xiyaowong/telescope-emoji.nvim',
|
||||
'nvim-telescope/telescope-ui-select.nvim',
|
||||
{ 'nvim-telescope/telescope-fzf-native.nvim', build = 'make' },
|
||||
},
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ return {
|
|||
}
|
||||
|
||||
require('telescope').load_extension('fzf')
|
||||
require('telescope').load_extension('ui-select')
|
||||
require('telescope').load_extension('emoji')
|
||||
|
||||
vim.keymap.set('n', '<leader>/', function()
|
||||
|
|
@ -40,11 +41,11 @@ return {
|
|||
vim.keymap.set('n', '<leader>af', require('telescope.builtin').git_files)
|
||||
vim.keymap.set('n', '<leader>sf', require('telescope.builtin').find_files)
|
||||
vim.keymap.set('n', '<leader>.', require('telescope').extensions.emoji.emoji)
|
||||
vim.keymap.set('n', 'z=', require('telescope.builtin').spell_suggest)
|
||||
|
||||
|
||||
-- Use this to add more results without clearing the trouble list
|
||||
-- local add_to_trouble = require("trouble.sources.telescope").add
|
||||
|
||||
local telescope = require("telescope")
|
||||
-- local telescope = require("telescope")
|
||||
end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,5 +40,9 @@ return {
|
|||
vim.o.termguicolors = true
|
||||
vim.cmd.colorscheme 'onedark'
|
||||
vim.cmd.hi 'clear @spell'
|
||||
|
||||
vim.cmd.hi 'IlluminatedWordText ctermbg=15 gui=underline,bold guibg=#1a1a1a'
|
||||
vim.cmd.hi 'IlluminatedWordRead ctermbg=15 gui=underline,bold guibg=#1a1a1a'
|
||||
vim.cmd.hi 'IlluminatedWordWrite ctermbg=15 gui=underline,bold guibg=#1a1a1a'
|
||||
end,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
-- TODO: migrate to `main` when it's complete and staable
|
||||
|
||||
return {
|
||||
'nvim-treesitter/nvim-treesitter',
|
||||
dependencies = {
|
||||
'nvim-treesitter/nvim-treesitter-context',
|
||||
{
|
||||
'nvim-treesitter/nvim-treesitter-textobjects',
|
||||
'nvim-treesitter/nvim-treesitter-context'
|
||||
branch = 'master',
|
||||
}
|
||||
},
|
||||
build = ':TSUpdate',
|
||||
branch = 'master',
|
||||
build = function()
|
||||
local queries_path = vim.fn.stdpath('data') .. '/lazy/nvim-treesitter/queries/dockerfile'
|
||||
local dockerfile_queries = vim.fn.stdpath('config') .. '/treesitter/dockerfile/queries/'
|
||||
|
||||
vim.fn.system({ 'rm', '-r', queries_path })
|
||||
vim.fn.mkdir(queries_path, 'p')
|
||||
for file in vim.fs.dir(dockerfile_queries) do
|
||||
vim.fn.system({ 'cp', dockerfile_queries .. file, queries_path })
|
||||
end
|
||||
|
||||
vim.cmd('TSUpdate')
|
||||
end,
|
||||
config = function()
|
||||
vim.defer_fn(function()
|
||||
--- @diagnostic disable-next-line: missing-fields
|
||||
|
|
@ -17,6 +34,16 @@ return {
|
|||
require('treesitter-context').setup {
|
||||
max_lines = 6
|
||||
}
|
||||
|
||||
local parser_config = require('nvim-treesitter.parsers').get_parser_configs()
|
||||
parser_config.dockerfile = {
|
||||
install_info = {
|
||||
url = '/home/wzray/.config/nvim/treesitter/dockerfile',
|
||||
files = { "src/parser.c", "scanner.c" },
|
||||
generate_requires_npm = false,
|
||||
requires_generate_from_grammar = true,
|
||||
},
|
||||
}
|
||||
end, 0)
|
||||
end
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ return {
|
|||
},
|
||||
config = function()
|
||||
vim.defer_fn(function()
|
||||
vim.o.foldcolumn = '0'
|
||||
vim.o.foldlevel = 99
|
||||
vim.o.foldlevelstart = 99
|
||||
vim.o.foldenable = true
|
||||
|
||||
vim.keymap.set('n', 'zR', require('ufo').openAllFolds)
|
||||
vim.keymap.set('n', 'zM', require('ufo').closeAllFolds)
|
||||
|
||||
|
|
|
|||
22
.config/nvim/spell/en.utf-8.add
Normal file
22
.config/nvim/spell/en.utf-8.add
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
configs
|
||||
config
|
||||
lol
|
||||
auth
|
||||
nginx
|
||||
keepalive
|
||||
hivemind
|
||||
broadcasted
|
||||
GC
|
||||
dns
|
||||
CP
|
||||
JSON
|
||||
Unmarshaler
|
||||
UnmarshalJSON
|
||||
Marshaler
|
||||
MarshalJSON
|
||||
TextMarshaler
|
||||
MarshalText
|
||||
UnsupportedValueError
|
||||
HTMLEscape
|
||||
func
|
||||
ООП
|
||||
BIN
.config/nvim/spell/en.utf-8.add.spl
Normal file
BIN
.config/nvim/spell/en.utf-8.add.spl
Normal file
Binary file not shown.
BIN
.config/nvim/spell/ru.utf-8.spl
Normal file
BIN
.config/nvim/spell/ru.utf-8.spl
Normal file
Binary file not shown.
BIN
.config/nvim/spell/ru.utf-8.sug
Normal file
BIN
.config/nvim/spell/ru.utf-8.sug
Normal file
Binary file not shown.
491
.config/nvim/treesitter/dockerfile/grammar.js
Normal file
491
.config/nvim/treesitter/dockerfile/grammar.js
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
module.exports = grammar({
|
||||
name: "dockerfile",
|
||||
|
||||
extras: ($) => [/\s+/, $.line_continuation, $.comment],
|
||||
externals: ($) => [
|
||||
$.heredoc_marker,
|
||||
$.heredoc_line,
|
||||
$.heredoc_end,
|
||||
$.heredoc_nl,
|
||||
$.error_sentinel,
|
||||
],
|
||||
|
||||
rules: {
|
||||
source_file: ($) => repeat(seq($._instruction, "\n")),
|
||||
|
||||
_instruction: ($) =>
|
||||
choice(
|
||||
$.from_instruction,
|
||||
$.run_instruction,
|
||||
$.cmd_instruction,
|
||||
$.label_instruction,
|
||||
$.expose_instruction,
|
||||
$.env_instruction,
|
||||
$.add_instruction,
|
||||
$.copy_instruction,
|
||||
$.entrypoint_instruction,
|
||||
$.volume_instruction,
|
||||
$.user_instruction,
|
||||
$.workdir_instruction,
|
||||
$.arg_instruction,
|
||||
$.onbuild_instruction,
|
||||
$.stopsignal_instruction,
|
||||
$.healthcheck_instruction,
|
||||
$.shell_instruction,
|
||||
$.maintainer_instruction,
|
||||
$.cross_build_instruction,
|
||||
),
|
||||
|
||||
from_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[fF][rR][oO][mM]/, "FROM"),
|
||||
optional($.param),
|
||||
$.image_spec,
|
||||
optional(seq(alias(/[aA][sS]/, "AS"), field("as", $.image_alias)))
|
||||
),
|
||||
|
||||
run_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[rR][uU][nN]/, "RUN"),
|
||||
repeat(
|
||||
choice(
|
||||
$.param,
|
||||
$.mount_param
|
||||
)
|
||||
),
|
||||
choice($.json_string_array, $.shell_command),
|
||||
repeat($.heredoc_block)
|
||||
),
|
||||
|
||||
cmd_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[cC][mM][dD]/, "CMD"),
|
||||
choice($.json_string_array, $.shell_command)
|
||||
),
|
||||
|
||||
label_instruction: ($) =>
|
||||
seq(alias(/[lL][aA][bB][eE][lL]/, "LABEL"), repeat1($.label_pair)),
|
||||
|
||||
expose_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[eE][xX][pP][oO][sS][eE]/, "EXPOSE"),
|
||||
repeat1(choice($.expose_port, $.expansion))
|
||||
),
|
||||
|
||||
env_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[eE][nN][vV]/, "ENV"),
|
||||
choice(repeat1($.env_pair), alias($._spaced_env_pair, $.env_pair))
|
||||
),
|
||||
|
||||
add_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[aA][dD][dD]/, "ADD"),
|
||||
repeat($.param),
|
||||
repeat1(
|
||||
seq(alias($.path_with_heredoc, $.path), $._non_newline_whitespace)
|
||||
),
|
||||
alias($.path_with_heredoc, $.path),
|
||||
repeat($.heredoc_block)
|
||||
),
|
||||
|
||||
copy_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[cC][oO][pP][yY]/, "COPY"),
|
||||
repeat($.param),
|
||||
repeat1(
|
||||
seq(alias($.path_with_heredoc, $.path), $._non_newline_whitespace)
|
||||
),
|
||||
alias($.path_with_heredoc, $.path),
|
||||
repeat($.heredoc_block)
|
||||
),
|
||||
|
||||
entrypoint_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[eE][nN][tT][rR][yY][pP][oO][iI][nN][tT]/, "ENTRYPOINT"),
|
||||
choice($.json_string_array, $.shell_command)
|
||||
),
|
||||
|
||||
volume_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[vV][oO][lL][uU][mM][eE]/, "VOLUME"),
|
||||
choice(
|
||||
$.json_string_array,
|
||||
seq($.path, repeat(seq($._non_newline_whitespace, $.path)))
|
||||
)
|
||||
),
|
||||
|
||||
user_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[uU][sS][eE][rR]/, "USER"),
|
||||
field("user", alias($._user_name_or_group, $.unquoted_string)),
|
||||
optional(
|
||||
seq(
|
||||
token.immediate(":"),
|
||||
field("group",
|
||||
alias($._immediate_user_name_or_group, $.unquoted_string))
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
_user_name_or_group: ($) =>
|
||||
seq(
|
||||
choice(/([a-zA-Z][-A-Za-z0-9_]*|[0-9]+)/, $.expansion),
|
||||
repeat($._immediate_user_name_or_group_fragment)
|
||||
),
|
||||
|
||||
// same as _user_name_or_group but sticks to previous token
|
||||
_immediate_user_name_or_group: ($) =>
|
||||
repeat1($._immediate_user_name_or_group_fragment),
|
||||
|
||||
_immediate_user_name_or_group_fragment: ($) =>
|
||||
choice(
|
||||
token.immediate(/([a-zA-Z][-a-zA-Z0-9_]*|[0-9]+)/),
|
||||
$._immediate_expansion
|
||||
),
|
||||
|
||||
workdir_instruction: ($) =>
|
||||
seq(alias(/[wW][oO][rR][kK][dD][iI][rR]/, "WORKDIR"), $.path),
|
||||
|
||||
arg_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[aA][rR][gG]/, "ARG"),
|
||||
field("name", alias(/[a-zA-Z0-9_]+/, $.unquoted_string)),
|
||||
optional(
|
||||
seq(
|
||||
token.immediate("="),
|
||||
field("default",
|
||||
choice(
|
||||
$.double_quoted_string,
|
||||
$.single_quoted_string,
|
||||
$.unquoted_string
|
||||
))
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
onbuild_instruction: ($) =>
|
||||
seq(alias(/[oO][nN][bB][uU][iI][lL][dD]/, "ONBUILD"), $._instruction),
|
||||
|
||||
stopsignal_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[sS][tT][oO][pP][sS][iI][gG][nN][aA][lL]/, "STOPSIGNAL"),
|
||||
$._stopsignal_value
|
||||
),
|
||||
|
||||
_stopsignal_value: ($) =>
|
||||
seq(
|
||||
choice(/[A-Z0-9]+/, $.expansion),
|
||||
repeat(choice(token.immediate(/[A-Z0-9]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
healthcheck_instruction: ($) =>
|
||||
seq(
|
||||
alias(/[hH][eE][aA][lL][tT][hH][cC][hH][eE][cC][kK]/, "HEALTHCHECK"),
|
||||
choice("NONE", seq(repeat($.param), $.cmd_instruction))
|
||||
),
|
||||
|
||||
shell_instruction: ($) =>
|
||||
seq(alias(/[sS][hH][eE][lL][lL]/, "SHELL"), $.json_string_array),
|
||||
|
||||
maintainer_instruction: () =>
|
||||
seq(
|
||||
alias(/[mM][aA][iI][nN][tT][aA][iI][nN][eE][rR]/, "MAINTAINER"),
|
||||
/.*/
|
||||
),
|
||||
|
||||
cross_build_instruction: () =>
|
||||
seq(
|
||||
alias(
|
||||
/[cC][rR][oO][sS][sS]_[bB][uU][iI][lL][dD][a-zA-Z_]*/,
|
||||
"CROSS_BUILD"
|
||||
),
|
||||
/.*/
|
||||
),
|
||||
|
||||
heredoc_block: ($) =>
|
||||
seq(
|
||||
// A heredoc block starts with a line break after the instruction it
|
||||
// belongs to. The herdoc_nl token is a special token that only matches
|
||||
// \n if there's at least one open heredoc to avoid conflicts.
|
||||
// We also alias this token to hide it from the output like all other
|
||||
// whitespace.
|
||||
alias($.heredoc_nl, "_heredoc_nl"),
|
||||
repeat(seq($.heredoc_line, "\n")),
|
||||
$.heredoc_end
|
||||
),
|
||||
|
||||
path: ($) =>
|
||||
seq(
|
||||
choice(
|
||||
/[^-\s\$<]/, // cannot start with a '-' to avoid conflicts with params
|
||||
/<[^<]/, // cannot start with a '<<' to avoid conflicts with heredocs (a single < is fine, though)
|
||||
$.expansion
|
||||
),
|
||||
repeat(choice(token.immediate(/[^\s\$]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
path_with_heredoc: ($) =>
|
||||
choice(
|
||||
$.heredoc_marker,
|
||||
seq(
|
||||
choice(
|
||||
/[^-\s\$<]/, // cannot start with a '-' to avoid conflicts with params
|
||||
/<[^-\s\$<]/,
|
||||
$.expansion
|
||||
),
|
||||
repeat(choice(token.immediate(/[^\s\$]+/), $._immediate_expansion))
|
||||
)
|
||||
),
|
||||
|
||||
expansion: $ =>
|
||||
seq("$", $._expansion_body),
|
||||
|
||||
// we have 2 rules b/c aliases don't work as expected on seq() directly
|
||||
_immediate_expansion: $ => alias($._imm_expansion, $.expansion),
|
||||
_imm_expansion: $ =>
|
||||
seq(token.immediate("$"), $._expansion_body),
|
||||
|
||||
_expansion_body: $ =>
|
||||
choice(
|
||||
$.variable,
|
||||
seq(
|
||||
token.immediate("{"),
|
||||
alias(token.immediate(/[^\}]+/), $.variable),
|
||||
token.immediate("}")
|
||||
)
|
||||
),
|
||||
|
||||
variable: () => token.immediate(/[a-zA-Z_][a-zA-Z0-9_]*/),
|
||||
|
||||
env_pair: ($) =>
|
||||
seq(
|
||||
field("name", $._env_key),
|
||||
token.immediate("="),
|
||||
optional(
|
||||
field("value",
|
||||
choice(
|
||||
$.double_quoted_string,
|
||||
$.single_quoted_string,
|
||||
$.unquoted_string
|
||||
))
|
||||
)
|
||||
),
|
||||
|
||||
_spaced_env_pair: ($) =>
|
||||
seq(
|
||||
field("name", $._env_key),
|
||||
token.immediate(/\s+/),
|
||||
field("value",
|
||||
choice(
|
||||
$.double_quoted_string,
|
||||
$.single_quoted_string,
|
||||
$.unquoted_string
|
||||
))
|
||||
),
|
||||
|
||||
_env_key: ($) =>
|
||||
alias(/[a-zA-Z_][a-zA-Z0-9_]*/, $.unquoted_string),
|
||||
|
||||
expose_port: () => seq(/\d+(-\d+)?/, optional(choice("/tcp", "/udp"))),
|
||||
|
||||
label_pair: ($) =>
|
||||
seq(
|
||||
field("key", choice(
|
||||
alias(/[-a-zA-Z0-9\._]+/, $.unquoted_string),
|
||||
$.double_quoted_string,
|
||||
$.single_quoted_string
|
||||
)),
|
||||
token.immediate("="),
|
||||
field("value",
|
||||
choice(
|
||||
$.double_quoted_string,
|
||||
$.single_quoted_string,
|
||||
$.unquoted_string
|
||||
))
|
||||
),
|
||||
|
||||
image_spec: ($) =>
|
||||
seq(
|
||||
field("name", $.image_name),
|
||||
seq(
|
||||
field("tag", optional($.image_tag)),
|
||||
field("digest", optional($.image_digest))
|
||||
)
|
||||
),
|
||||
|
||||
image_name: ($) =>
|
||||
seq(
|
||||
choice(/[^@:\s\$-]/, $.expansion),
|
||||
repeat(choice(token.immediate(/[^@:\s\$]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
image_tag: ($) =>
|
||||
seq(
|
||||
token.immediate(":"),
|
||||
repeat1(choice(token.immediate(/[^@\s\$]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
image_digest: ($) =>
|
||||
seq(
|
||||
token.immediate("@"),
|
||||
repeat1(choice(token.immediate(/[a-zA-Z0-9:]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
// Generic parsing of options passed right after an instruction name.
|
||||
param: () =>
|
||||
seq(
|
||||
"--",
|
||||
field("name", token.immediate(/[a-z][-a-z]*/)),
|
||||
token.immediate("="),
|
||||
field("value", token.immediate(/[^\s]+/))
|
||||
),
|
||||
|
||||
// Specific parsing of the --mount option e.g.
|
||||
//
|
||||
// --mount=type=cache,target=/root/.cache/go-build
|
||||
//
|
||||
mount_param: ($) => seq(
|
||||
"--",
|
||||
field("name", token.immediate("mount")),
|
||||
token.immediate("="),
|
||||
field(
|
||||
"value",
|
||||
seq(
|
||||
$.mount_param_param,
|
||||
repeat(
|
||||
seq(token.immediate(","), $.mount_param_param)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
mount_param_param: () => seq(
|
||||
token.immediate(/[^\s=,]+/),
|
||||
token.immediate("="),
|
||||
token.immediate(/[^\s=,]+/)
|
||||
),
|
||||
|
||||
image_alias: ($) => seq(
|
||||
choice(/[-a-zA-Z0-9_]+/, $.expansion),
|
||||
repeat(choice(token.immediate(/[-a-zA-Z0-9_]+/), $._immediate_expansion))
|
||||
),
|
||||
|
||||
shell_command: ($) =>
|
||||
seq(
|
||||
$.shell_fragment,
|
||||
repeat(
|
||||
seq(
|
||||
alias($.required_line_continuation, $.line_continuation),
|
||||
$.shell_fragment
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
shell_fragment: ($) => repeat1(
|
||||
choice(
|
||||
// A shell fragment is broken into the same tokens as other
|
||||
// constructs because the lexer prefers the longer tokens
|
||||
// when it has a choice. The example below shows the tokenization
|
||||
// of the --mount parameter.
|
||||
//
|
||||
// RUN --mount=foo=bar,baz=42 ls --all
|
||||
// ^^ ^ ^ ^ ^
|
||||
// ^^^^^ ^^^ ^^^ ^^^ ^^
|
||||
// |--------param-------|
|
||||
// |--shell_command--|
|
||||
//
|
||||
seq($.heredoc_marker, /[ \t]*/),
|
||||
/[,=-]/,
|
||||
/[^\\\[\n#\s,=-][^\\\n<]*/,
|
||||
/\\[^\n,=-]/,
|
||||
/<[^<]/,
|
||||
)
|
||||
),
|
||||
|
||||
line_continuation: () => /\\[ \t]*\n/,
|
||||
required_line_continuation: () => "\\\n",
|
||||
|
||||
json_string_array: ($) =>
|
||||
seq(
|
||||
"[",
|
||||
optional(
|
||||
seq($.json_string, repeat(seq(",", $.json_string)))
|
||||
),
|
||||
"]"
|
||||
),
|
||||
|
||||
// Note that JSON strings are different from the other double-quoted
|
||||
// strings. They don't support $-expansions.
|
||||
// Convenient reference: https://www.json.org/
|
||||
json_string: ($) => seq(
|
||||
'"',
|
||||
repeat(
|
||||
choice(
|
||||
token.immediate(/[^"\\]+/),
|
||||
alias($.json_escape_sequence, $.escape_sequence)
|
||||
)
|
||||
),
|
||||
'"'
|
||||
),
|
||||
|
||||
json_escape_sequence: () => token.immediate(
|
||||
/\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})/
|
||||
),
|
||||
|
||||
double_quoted_string: ($) =>
|
||||
seq(
|
||||
'"',
|
||||
repeat(
|
||||
choice(
|
||||
token.immediate(/[^"\n\\\$]+/),
|
||||
alias($.double_quoted_escape_sequence, $.escape_sequence),
|
||||
"\\",
|
||||
$._immediate_expansion
|
||||
)
|
||||
),
|
||||
'"'
|
||||
),
|
||||
|
||||
// same as double_quoted_string but without $-expansions:
|
||||
single_quoted_string: ($) =>
|
||||
seq(
|
||||
"'",
|
||||
repeat(
|
||||
choice(
|
||||
token.immediate(/[^'\n\\]+/),
|
||||
alias($.single_quoted_escape_sequence, $.escape_sequence),
|
||||
"\\",
|
||||
)
|
||||
),
|
||||
"'"
|
||||
),
|
||||
|
||||
unquoted_string: ($) =>
|
||||
repeat1(
|
||||
choice(
|
||||
token.immediate(/[^\s\n\"'\\\$]+/),
|
||||
token.immediate("\\ "),
|
||||
$._immediate_expansion
|
||||
)
|
||||
),
|
||||
|
||||
double_quoted_escape_sequence: () => token.immediate(
|
||||
choice(
|
||||
"\\\\",
|
||||
"\\\""
|
||||
)
|
||||
),
|
||||
|
||||
single_quoted_escape_sequence: () => token.immediate(
|
||||
choice(
|
||||
"\\\\",
|
||||
"\\'"
|
||||
)
|
||||
),
|
||||
|
||||
_non_newline_whitespace: () => token.immediate(/[\t ]+/),
|
||||
|
||||
comment: () => /#.*/,
|
||||
},
|
||||
});
|
||||
58
.config/nvim/treesitter/dockerfile/queries/highlights.scm
Normal file
58
.config/nvim/treesitter/dockerfile/queries/highlights.scm
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
[
|
||||
"FROM"
|
||||
"AS"
|
||||
"RUN"
|
||||
"CMD"
|
||||
"LABEL"
|
||||
"EXPOSE"
|
||||
"ENV"
|
||||
"ADD"
|
||||
"COPY"
|
||||
"ENTRYPOINT"
|
||||
"VOLUME"
|
||||
"USER"
|
||||
"WORKDIR"
|
||||
"ARG"
|
||||
"ONBUILD"
|
||||
"STOPSIGNAL"
|
||||
"HEALTHCHECK"
|
||||
"SHELL"
|
||||
"MAINTAINER"
|
||||
"CROSS_BUILD"
|
||||
(heredoc_marker)
|
||||
(heredoc_end)
|
||||
] @keyword
|
||||
|
||||
[
|
||||
":"
|
||||
"@"
|
||||
] @operator
|
||||
|
||||
(comment) @comment
|
||||
|
||||
|
||||
(image_spec
|
||||
(image_tag
|
||||
":" @punctuation.special)
|
||||
(image_digest
|
||||
"@" @punctuation.special))
|
||||
|
||||
[
|
||||
(double_quoted_string)
|
||||
(single_quoted_string)
|
||||
(json_string)
|
||||
(heredoc_block)
|
||||
] @string
|
||||
|
||||
(expansion
|
||||
[
|
||||
"$"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.special
|
||||
) @none
|
||||
|
||||
((variable) @constant
|
||||
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
|
||||
|
||||
|
||||
11
.config/nvim/treesitter/dockerfile/queries/injections.scm
Normal file
11
.config/nvim/treesitter/dockerfile/queries/injections.scm
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
||||
((shell_command) @injection.content
|
||||
(#set! injection.language "bash")
|
||||
(#set! injection.include-children))
|
||||
|
||||
((run_instruction
|
||||
(heredoc_block) @injection.content)
|
||||
(#set! injection.language "bash")
|
||||
(#set! injection.include-children))
|
||||
343
.config/nvim/treesitter/dockerfile/scanner.c
Normal file
343
.config/nvim/treesitter/dockerfile/scanner.c
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
// https://github.com/faergeek/tree-sitter-dockerfile/tree/make-language-injections-easier
|
||||
// https://github.com/camdencheek/tree-sitter-dockerfile
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2021 Camden Cheek
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#include "tree_sitter/parser.h"
|
||||
|
||||
#define MAX_HEREDOCS 10
|
||||
#define DEL_SPACE 512
|
||||
|
||||
typedef struct {
|
||||
bool in_heredoc;
|
||||
bool stripping_heredoc;
|
||||
unsigned heredoc_count;
|
||||
char *heredocs[MAX_HEREDOCS];
|
||||
} scanner_state;
|
||||
|
||||
enum TokenType {
|
||||
HEREDOC_MARKER,
|
||||
HEREDOC_LINE,
|
||||
HEREDOC_END,
|
||||
HEREDOC_NL,
|
||||
ERROR_SENTINEL,
|
||||
};
|
||||
|
||||
void *tree_sitter_dockerfile_external_scanner_create() {
|
||||
scanner_state *state = malloc(sizeof(scanner_state));
|
||||
memset(state, 0, sizeof(scanner_state));
|
||||
return state;
|
||||
}
|
||||
|
||||
void tree_sitter_dockerfile_external_scanner_destroy(void *payload) {
|
||||
if (!payload)
|
||||
return;
|
||||
|
||||
scanner_state *state = payload;
|
||||
for (unsigned i = 0; i < MAX_HEREDOCS; i++) {
|
||||
if (state->heredocs[i]) {
|
||||
free(state->heredocs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(state);
|
||||
}
|
||||
|
||||
unsigned tree_sitter_dockerfile_external_scanner_serialize(void *payload,
|
||||
char *buffer) {
|
||||
scanner_state *state = payload;
|
||||
|
||||
unsigned pos = 0;
|
||||
buffer[pos++] = state->in_heredoc;
|
||||
buffer[pos++] = state->stripping_heredoc;
|
||||
|
||||
for (unsigned i = 0; i < state->heredoc_count; i++) {
|
||||
// Add the ending null byte to the length since we'll have to copy it as
|
||||
// well.
|
||||
unsigned len = strlen(state->heredocs[i]) + 1;
|
||||
|
||||
// If we run out of space, just drop the heredocs that don't fit.
|
||||
// We need at least len + 1 bytes space since we'll copy len bytes below
|
||||
// and later add a null byte at the end.
|
||||
if (pos + len + 1 > TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&buffer[pos], state->heredocs[i], len);
|
||||
pos += len;
|
||||
}
|
||||
|
||||
// Add a null byte at the end to make it easy to detect.
|
||||
buffer[pos++] = 0;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void tree_sitter_dockerfile_external_scanner_deserialize(void *payload,
|
||||
const char *buffer,
|
||||
unsigned length) {
|
||||
scanner_state *state = payload;
|
||||
// Free all current heredocs to avoid leaking memory when we overwrite the
|
||||
// array later.
|
||||
for (unsigned i = 0; i < state->heredoc_count; i++) {
|
||||
free(state->heredocs[i]);
|
||||
state->heredocs[i] = NULL;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
state->in_heredoc = false;
|
||||
state->stripping_heredoc = false;
|
||||
state->heredoc_count = 0;
|
||||
} else {
|
||||
unsigned pos = 0;
|
||||
state->in_heredoc = buffer[pos++];
|
||||
state->stripping_heredoc = buffer[pos++];
|
||||
|
||||
unsigned heredoc_count = 0;
|
||||
for (unsigned i = 0; i < MAX_HEREDOCS; i++) {
|
||||
unsigned len = strlen(&buffer[pos]);
|
||||
|
||||
// We found the ending null byte which means that we're done.
|
||||
if (len == 0)
|
||||
break;
|
||||
|
||||
// Account for the ending null byte in strings (again).
|
||||
len++;
|
||||
char *heredoc = malloc(len);
|
||||
memcpy(heredoc, &buffer[pos], len);
|
||||
state->heredocs[i] = heredoc;
|
||||
heredoc_count++;
|
||||
|
||||
pos += len;
|
||||
}
|
||||
|
||||
state->heredoc_count = heredoc_count;
|
||||
}
|
||||
}
|
||||
|
||||
static void skip_whitespace(TSLexer *lexer) {
|
||||
while (lexer->lookahead != '\0' && lexer->lookahead != '\n' &&
|
||||
iswspace(lexer->lookahead))
|
||||
lexer->advance(lexer, true);
|
||||
}
|
||||
|
||||
static bool scan_marker(scanner_state *state, TSLexer *lexer) {
|
||||
skip_whitespace(lexer);
|
||||
|
||||
if (lexer->lookahead != '<')
|
||||
return false;
|
||||
lexer->advance(lexer, false);
|
||||
|
||||
if (lexer->lookahead != '<')
|
||||
return false;
|
||||
lexer->advance(lexer, false);
|
||||
|
||||
bool stripping = false;
|
||||
if (lexer->lookahead == '-') {
|
||||
stripping = true;
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
|
||||
int32_t quote = 0;
|
||||
if (lexer->lookahead == '"' || lexer->lookahead == '\'') {
|
||||
quote = lexer->lookahead;
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
|
||||
// Reserve a reasonable amount of space for the heredoc delimiter string.
|
||||
// Most heredocs (like EOF, EOT, EOS, FILE, etc.) are pretty short so we'll
|
||||
// usually only need a few bytes. We're also limited to less than 1024 bytes
|
||||
// by tree-sitter since our state has to fit in
|
||||
// TREE_SITTER_SERIALIZATION_BUFFER_SIZE.
|
||||
char delimiter[DEL_SPACE];
|
||||
|
||||
// We start recording the actual string at position 1 since we store whether
|
||||
// it's a stripping heredoc in the first position (with either a dash or a
|
||||
// space).
|
||||
unsigned del_idx = 1;
|
||||
|
||||
while (lexer->lookahead != '\0' &&
|
||||
(quote ? lexer->lookahead != quote : !iswspace(lexer->lookahead))) {
|
||||
if (lexer->lookahead == '\\') {
|
||||
lexer->advance(lexer, false);
|
||||
|
||||
if (lexer->lookahead == '\0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (del_idx > 0) {
|
||||
delimiter[del_idx++] = lexer->lookahead;
|
||||
}
|
||||
lexer->advance(lexer, false);
|
||||
|
||||
// If we run out of space, stop recording the delimiter but keep
|
||||
// advancing the lexer to ensure that we at least parse the marker
|
||||
// correctly. Reserve two bytes: one for the strip indicator and
|
||||
// one for the terminating null byte.
|
||||
if (del_idx >= DEL_SPACE - 2) {
|
||||
del_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (quote) {
|
||||
if (lexer->lookahead != quote) {
|
||||
return false;
|
||||
}
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
|
||||
if (del_idx == 0) {
|
||||
lexer->result_symbol = HEREDOC_MARKER;
|
||||
return true;
|
||||
}
|
||||
|
||||
delimiter[0] = stripping ? '-' : ' ';
|
||||
delimiter[del_idx] = '\0';
|
||||
|
||||
// We copy the delimiter string to the heap here since we can't store our
|
||||
// stack-allocated string in our state (which is stored on the heap).
|
||||
char *del_copy = malloc(del_idx + 1);
|
||||
memcpy(del_copy, delimiter, del_idx + 1);
|
||||
|
||||
if (state->heredoc_count == 0) {
|
||||
state->heredoc_count = 1;
|
||||
state->heredocs[0] = del_copy;
|
||||
state->stripping_heredoc = stripping;
|
||||
} else if (state->heredoc_count >= MAX_HEREDOCS) {
|
||||
free(del_copy);
|
||||
} else {
|
||||
state->heredocs[state->heredoc_count++] = del_copy;
|
||||
}
|
||||
|
||||
lexer->result_symbol = HEREDOC_MARKER;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool scan_content(scanner_state *state, TSLexer *lexer,
|
||||
const bool *valid_symbols) {
|
||||
if (state->heredoc_count == 0) {
|
||||
state->in_heredoc = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
state->in_heredoc = true;
|
||||
|
||||
if (state->stripping_heredoc) {
|
||||
skip_whitespace(lexer);
|
||||
}
|
||||
|
||||
if (valid_symbols[HEREDOC_END]) {
|
||||
unsigned delim_idx = 1;
|
||||
// Look for the current heredoc delimiter.
|
||||
while (state->heredocs[0][delim_idx] != '\0' &&
|
||||
lexer->lookahead != '\0' &&
|
||||
lexer->lookahead == state->heredocs[0][delim_idx]) {
|
||||
lexer->advance(lexer, false);
|
||||
delim_idx++;
|
||||
}
|
||||
|
||||
// Check if the entire string matched.
|
||||
if (state->heredocs[0][delim_idx] == '\0') {
|
||||
lexer->result_symbol = HEREDOC_END;
|
||||
|
||||
// Shift the first heredoc off the list.
|
||||
free(state->heredocs[0]);
|
||||
|
||||
for (unsigned i = 1; i < state->heredoc_count; i++) {
|
||||
state->heredocs[i - 1] = state->heredocs[i];
|
||||
}
|
||||
state->heredocs[state->heredoc_count - 1] = NULL;
|
||||
state->heredoc_count--;
|
||||
|
||||
if (state->heredoc_count > 0) {
|
||||
state->stripping_heredoc = state->heredocs[0][0] == '-';
|
||||
} else {
|
||||
state->in_heredoc = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_symbols[HEREDOC_LINE])
|
||||
return false;
|
||||
|
||||
lexer->result_symbol = HEREDOC_LINE;
|
||||
|
||||
for (;;) {
|
||||
switch (lexer->lookahead) {
|
||||
case '\0':
|
||||
if (lexer->eof(lexer)) {
|
||||
state->in_heredoc = false;
|
||||
return true;
|
||||
}
|
||||
lexer->advance(lexer, false);
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
return true;
|
||||
|
||||
default:
|
||||
lexer->advance(lexer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool tree_sitter_dockerfile_external_scanner_scan(void *payload, TSLexer *lexer,
|
||||
const bool *valid_symbols) {
|
||||
scanner_state *state = payload;
|
||||
|
||||
if (valid_symbols[ERROR_SENTINEL]) {
|
||||
if (state->in_heredoc) {
|
||||
return scan_content(state, lexer, valid_symbols);
|
||||
} else {
|
||||
return scan_marker(state, lexer);
|
||||
}
|
||||
}
|
||||
|
||||
// HEREDOC_NL only matches a linebreak if there are open heredocs. This is
|
||||
// necessary to avoid a conflict in the grammar since a normal line break
|
||||
// could either be the start of a heredoc or the end of an instruction.
|
||||
if (valid_symbols[HEREDOC_NL]) {
|
||||
if (state->heredoc_count > 0 && lexer->lookahead == '\n') {
|
||||
lexer->result_symbol = HEREDOC_NL;
|
||||
lexer->advance(lexer, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_symbols[HEREDOC_MARKER]) {
|
||||
return scan_marker(state, lexer);
|
||||
}
|
||||
|
||||
if (valid_symbols[HEREDOC_LINE] || valid_symbols[HEREDOC_END]) {
|
||||
return scan_content(state, lexer, valid_symbols);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue