diff --git a/README.md b/README.md index 364523c..95213c5 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,16 @@ It uses neovim's treesitter api to provide color definition for languages that h The main goal is to provide a solid example on how to use this library. \ It can be used by those who need better syntax coloring in their neovim environment. -### Development of this plugin will continue when the treesitter api becomes more stable - ### Notice - Requires neovim v0.5 - Is usable in the current state only to test and experiment - Only js filetypes are supported ATM - The plugin is under heavy development, it might change a lot or break things. -- Even though its only to experiment while the neovim team is working on a better api, i still wish people to see what can be done with the treesitter API ### Installing -With plug: `Plug 'kyazdani42/highlight.lua'` +With plug: `Plug 'nvim-treesitter/highlight.lua'` You can install parsers running `:InstallTSParser LANGUAGE`. Only `javascript` is available at the moment. \ The command does not work on windows and might not work on every OS. It depends on `git` and `gcc`. diff --git a/lua/highlight.lua b/lua/highlight.lua index f074537..8273ccd 100644 --- a/lua/highlight.lua +++ b/lua/highlight.lua @@ -2,18 +2,15 @@ local api = vim.api local filetypes = require'lib/init' if not filetypes then - return { - attach = function() end, - detach = function() end - } + return { setup = function() end } end local parser_cmd = require 'lib/parser' local has_parser = parser_cmd.has_parser +local get_query = parser_cmd.get_query local install_parser = require 'lib/install'.install_parser -local init_ts_parser = require 'lib/runner'.init_ts_parser local BUF_HANDLES = {} local function on_detach(buf) @@ -25,27 +22,13 @@ local function on_detach(buf) end end -local function attach_to_buf(buf, attributes) - api.nvim_command('setlocal syntax=off') -- reset the syntax and use only our api - api.nvim_buf_clear_namespace(buf, -1, 0, -1) - - local update_highlight = init_ts_parser(buf, attributes) - - local has_attached = vim.api.nvim_buf_attach(buf, 0, { - on_detach = on_detach, - on_lines = update_highlight - }) - - if has_attached then table.insert(BUF_HANDLES, buf) end -end - local function get_attributes(name) local ext = string.match(name or '', '%.[^%./]*$') or '' ext = string.sub(ext, 2, -1) return filetypes[ext] end -local function attach() +local function setup() local buf = api.nvim_win_get_buf(0) for i, handle in pairs(BUF_HANDLES) do @@ -54,11 +37,19 @@ local function attach() local attributes = get_attributes(api.nvim_buf_get_name(buf)) if attributes ~= nil and has_parser(attributes.parser_lang) then - attach_to_buf(buf, attributes) + api.nvim_command('setlocal syntax=off') -- reset the syntax and use only our api + api.nvim_buf_clear_namespace(buf, -1, 0, -1) + + local query = get_query(attributes.parser_lang) + if not query then return end + + vim.treesitter.TSHighlighter.new(query, buf, attributes.parser_lang) + local has_attached = vim.api.nvim_buf_attach(buf, 0, { on_detach = on_detach }) + if has_attached then table.insert(BUF_HANDLES, buf) end end end return { - attach = attach; + setup = setup; install_parser = install_parser; } diff --git a/lua/lib/init.lua b/lua/lib/init.lua index 0de5e40..ce30221 100644 --- a/lua/lib/init.lua +++ b/lua/lib/init.lua @@ -50,7 +50,7 @@ local colors = { } local filetypes = { - js = require 'syntax/javascript'.get_attributes(colors) + js = require 'syntax/javascript'(colors) } local function define_colors(group_colors, color_lang) diff --git a/lua/lib/parser.lua b/lua/lib/parser.lua index cee8a34..f92163c 100644 --- a/lua/lib/parser.lua +++ b/lua/lib/parser.lua @@ -2,6 +2,25 @@ local nvim = require 'lib/nvim' local err_msg = nvim.err_msg local get_runtime_file = nvim.get_runtime_file +local function read_scm(path) + local f = io.open(path, "rb") + if not f then return nil end + local content = f:read("*all") + f:close() + return content +end + +local function get_query(lang) + local file = get_runtime_file('lua/syntax/'..lang..'.scm', false)[1] + + if not file or not vim.loop.fs_access(file, 'R') then + err_msg('Did not find query for language `' .. lang ..'`') + return nil + end + + return read_scm(file) +end + local function has_parser(lang) local parser = get_runtime_file('parser/'..lang..'.so', false)[1] @@ -14,5 +33,6 @@ local function has_parser(lang) end return { - has_parser = has_parser + has_parser = has_parser; + get_query = get_query; } diff --git a/lua/lib/runner.lua b/lua/lib/runner.lua deleted file mode 100644 index 0cf9c02..0000000 --- a/lua/lib/runner.lua +++ /dev/null @@ -1,110 +0,0 @@ -local luv = vim.loop - -local function get_highlighter(buf, definitions, color_lang) - return function(node) - local node_type = node:type() - local row1, col1, row2, col2 = node:range() - - local highlight_group = definitions[node_type] - - if highlight_group == nil then - print(node_type) - return - elseif highlight_group == '' then - return - end - - highlight_group = color_lang .. highlight_group - - local line = row1 - local col_start - local col_end - - while line <= row2 do - - if line == row1 and line == row2 then - col_start = col1 - col_end = col2 - elseif line == row1 then - col_start = col1 - col_end = -1 - elseif line == row2 then - col_start = 0 - col_end = col2 - else - col_start = 0 - col_end = 0 - end - - vim.api.nvim_buf_add_highlight(buf, -1, highlight_group, line, col_start, col_end) - line = line + 1 - end - end -end - -local function get_tree_runner(highlight) - - local function run_tree(node) - highlight(node) - - local children = node:child_count() - if children == 0 then return end - - local i = 0 - while i < children do - run_tree(node:child(i)) - i = i + 1 - end - end - - return run_tree -end - -local function colorize_buffer(parser, run_tree) - local tree = parser:parse() - local root = tree:root() - run_tree(root) -end - -local function set_timeout(timeout, callback) - local timer = luv.new_timer() - timer:start(timeout, 0, function () - timer:stop() - timer:close() - callback() - end) - return timer -end - -local TIMEOUT = 50 - -local function get_update_highlight(parser, run_tree) - local running = false - - return function(_, buf) - if running then return end - - running = true - local tree = parser:parse() - local root = tree:root() - - local timer = set_timeout(TIMEOUT, vim.schedule_wrap(function() - vim.api.nvim_buf_clear_namespace(buf, -1, 0, -1) - run_tree(root) - running = false - end)) - end -end - -local function init_ts_parser(buf, attributes) - local parser = vim.treesitter.get_parser(buf, attributes.parser_lang) - - local highlighter = get_highlighter(buf, attributes.definitions, attributes.color_lang) - local run_tree = get_tree_runner(highlighter) - - colorize_buffer(parser, run_tree) - - return get_update_highlight(parser, run_tree) -end - -return { init_ts_parser = init_ts_parser } diff --git a/lua/syntax/javascript.lua b/lua/syntax/javascript.lua index c2fec06..b38b363 100644 --- a/lua/syntax/javascript.lua +++ b/lua/syntax/javascript.lua @@ -1,183 +1,5 @@ -local PARSER_LANG = 'javascript' -local COLOR_LANG = 'Javascript' - -local definitions = { - ['('] = 'Paren', - [')'] = 'Paren', - ['['] = 'Paren', - [']'] = 'Paren', - - ['{'] = 'Bracket', - ['}'] = 'Bracket', - ['${'] = 'Bracket', - - ['...'] = 'Operator', - ['='] = 'Operator', - ['+'] = 'Operator', - ['++'] = 'Operator', - ['-'] = 'Operator', - ['--'] = 'Operator', - ['*'] = 'Operator', - ['&'] = 'Operator', - ['+='] = 'Operator', - ['%'] = 'Operator', - ['~'] = 'Operator', - ['>>>'] = 'Operator', - ['>>'] = 'Operator', - ['/='] = 'Operator', - ['^'] = 'Operator', - ['-='] = 'Operator', - ['*='] = 'Operator', - ['<<<'] = 'Operator', - ['<<'] = 'Operator', - ['^='] = 'Operator', - ['%='] = 'Operator', - ['|'] = 'Operator', - ['|='] = 'Operator', - ['&='] = 'Operator', - ['<'] = 'Operator', - ['>'] = 'Operator', - ['<='] = 'Operator', - ['>='] = 'Operator', - ['=='] = 'Operator', - ['!='] = 'Operator', - ['==='] = 'Operator', - ['!=='] = 'Operator', - ['||'] = 'Operator', - ['&&'] = 'Operator', - - [';'] = 'Punc', - ['?'] = 'Punc', - [':'] = 'Punc', - ['.'] = 'Punc', - ['!'] = 'Punc', - [','] = 'Punc', - - ["'"] = 'Quote', - ['"'] = 'Quote', - ['`'] = 'Quote', - - const = 'Var', - let = 'Var', - var = 'Var', - - regex = 'Regex', - regex_flags = 'RegexFlag', - - ['true'] = 'Bool', - ['false'] = 'Bool', - null = 'Null', - number = 'Number', - - ['if'] = 'Logic', - ['else'] = 'Logic', - ['break'] = 'Logic', - ['do'] = 'Logic', - ['while'] = 'Logic', - ['for'] = 'Logic', - finally = 'Logic', - ['in'] = 'Logic', - try = 'Logic', - catch = 'Logic', - continue = 'Logic', - throw = 'Logic', - async = 'Logic', - await = 'Logic', - switch = 'Logic', - case = 'Logic', - default = 'Logic', - yield = 'Logic', - ['return'] = 'Logic', - comment = 'Comment', - - ['function'] = 'Function', - typeof = 'Function', - instanceof = 'Function', - delete = 'Function', - this = 'Function', - void = 'Function', - - class = 'Class', - extends = 'Class', - ['=>'] = 'Arrow', - new = 'New', - string = 'String', - call_expression = 'FunctionCall', - arguments = 'Argument', - identifier = 'Identifier', - shorthand_property_identifier = 'ObjProperty', - escape_sequence = 'EscapeSeq', - property_identifier = 'Properties', - formal_parameters = 'Parameters', - ERROR = 'Error', - - -- TODO: not sure how to organize these - -- Seems quite a lot of information we have here - ['/'] = '', - program = '', - pair = '', - object = '', - undefined = '', - class_body = '', - catch_clause = '', - regex_pattern = '', - try_statement = '', - throw_statement = '', - return_statement = '', - if_statement = '', - object_pattern = '', - statement_block = '', - array = '', - variable_declaration = '', - do_statement = '', - switch_statement = '', - update_expression = '', - labeled_statement = '', - yield_expression = '', - computed_property_name = '', - empty_statement = '', - continue_statement = '', - switch_body = '', - switch_default = '', - break_statement = '', - generator_function = '', - statement_identifier = '', - augmented_assignment_expression = '', - for_statement = '', - function_declaration = '', - unary_expression = '', - subscript_expression = '', - finally_clause = '', - sequence_expression = '', - assignment_pattern = '', - for_in_statement = '', - switch_case = '', - array_pattern = '', - class_heritage = '', - arrow_function = '', - new_expression = '', - template_string = '', - await_expression = '', - binary_expression = '', - class_declaration = '', - spread_element = '', - member_expression = '', - ternary_expression = '', - variable_declarator = '', - lexical_declaration = '', - expression_statement = '', - assignment_expression = '', - template_substitution = '', - parenthesized_expression = '', -} - -function setup_groups(colors) +local function setup_groups(colors) return { - -- TODO: remove these later on - -- keep them for debugging purposes - Test1 = { bg = colors.dark_yellow }, - Test2 = { bg = colors.yellow }, - Function = { fg = colors.cyan, gui = 'italic' }, Comment = { fg = colors.comment_grey, gui = 'italic' }, Null = { fg = colors.dark_yellow }, @@ -190,7 +12,7 @@ function setup_groups(colors) Class = { fg = colors.purple }, Properties = { fg = colors.blue }, Bracket = { fg = colors.cyan }, - Logic = { fg = colors.cyan, gui = 'italic' }, + Keyword = { fg = colors.cyan, gui = 'italic' }, Var = { fg = colors.purple }, Quote = { fg = colors.cyan }, Paren = { fg = colors.fg }, @@ -207,14 +29,14 @@ function setup_groups(colors) } end -return { - get_attributes = function(colors) - return { - definitions = definitions, - highlight_groups = setup_groups(colors), - parser_lang = PARSER_LANG, - color_lang = COLOR_LANG - } - end -} +local PARSER_LANG = 'javascript' +local COLOR_LANG = 'Js' + +return function(colors) + return { + highlight_groups = setup_groups(colors), + parser_lang = PARSER_LANG, + color_lang = COLOR_LANG + } +end diff --git a/lua/syntax/javascript.scm b/lua/syntax/javascript.scm new file mode 100644 index 0000000..5adb050 --- /dev/null +++ b/lua/syntax/javascript.scm @@ -0,0 +1,175 @@ +; Special identifiers +;-------------------- + +((identifier) @constant + (match? @constant "^[A-Z][A-Z_]+$")) + +((shorthand_property_identifier) @constant + (match? @constant "^[A-Z][A-Z_]+$")) + +((identifier) @constructor + (match? @constructor "^[A-Z]")) + +((identifier) @variable.builtin + (match? @variable.builtin "^(arguments|module|console|window|document)$") + (is-not? local)) + +((identifier) @function.builtin + (eq? @function.builtin "require") + (is-not? local)) + +; Function and method definitions +;-------------------------------- + +(function + name: (identifier) @function) +(function_declaration + name: (identifier) @function) +(method_definition + name: (property_identifier) @function.method) + +(pair + key: (property_identifier) @function.method + value: (function)) +(pair + key: (property_identifier) @function.method + value: (arrow_function)) + +(assignment_expression + left: (member_expression + property: (property_identifier) @function.method) + right: (arrow_function)) +(assignment_expression + left: (member_expression + property: (property_identifier) @function.method) + right: (function)) + +(variable_declarator + name: (identifier) @function + value: (arrow_function)) +(variable_declarator + name: (identifier) @function + value: (function)) + +(assignment_expression + left: (identifier) @function + right: (arrow_function)) +(assignment_expression + left: (identifier) @function + right: (function)) + +; Function and method calls +;-------------------------- + +(call_expression + function: (identifier) @function) + +(call_expression + function: (member_expression + property: (property_identifier) @function.method)) + +; Variables +;---------- + +(formal_parameters (identifier) @variable.parameter) + +(identifier) @variable + +; Properties +;----------- + +(property_identifier) @property + +; Literals +;--------- + +(this) @variable.builtin +(super) @variable.builtin + +(true) @constant.builtin +(false) @constant.builtin +(comment) @comment +(string) @string +(regex) @string.special +(template_string) @string +(number) @number + +; Punctuation +;------------ + +(template_substitution + "${" @punctuation.special + "}" @punctuation.special) @embedded + +";" @punctuation.delimiter +"." @punctuation.delimiter +"," @punctuation.delimiter + +"--" @operator +"-" @operator +"-=" @operator +"&&" @operator +"+" @operator +"++" @operator +"+=" @operator +"<" @operator +"<<" @operator +"=" @operator +"==" @operator +"===" @operator +"=>" @operator +">" @operator +">>" @operator +"||" @operator + +"(" @punctuation.bracket +")" @punctuation.bracket +"[" @punctuation.bracket +"]" @punctuation.bracket +"{" @punctuation.bracket +"}" @punctuation.bracket + +; Keywords +;---------- +"const" @JsVar +"let" @JsVar +"var" @JsVar + +"as" @JsKeyword +"async" @JsKeyword +"await" @JsKeyword +"break" @JsKeyword +"case" @JsKeyword +"catch" @JsKeyword +"class" @JsKeyword +"continue" @JsKeyword +"debugger" @JsKeyword +"default" @JsKeyword +"delete" @JsKeyword +"do" @JsKeyword +"else" @JsKeyword +"export" @JsKeyword +"extends" @JsKeyword +"finally" @JsKeyword +"for" @JsKeyword +"from" @JsKeyword +"function" @JsKeyword +"get" @JsKeyword +"if" @JsKeyword +"import" @JsKeyword +"in" @JsKeyword +"instanceof" @JsKeyword +"new" @JsKeyword +"of" @JsKeyword +"return" @JsKeyword +"set" @JsKeyword +"static" @JsKeyword +"switch" @JsKeyword +"target" @JsKeyword +"throw" @JsKeyword +"try" @JsKeyword +"typeof" @JsKeyword +"void" @JsKeyword +"while" @JsKeyword +"with" @JsKeyword +"yield" @JsKeyword diff --git a/plugin/highlight.vim b/plugin/highlight.vim index 74e9309..866bb2f 100644 --- a/plugin/highlight.vim +++ b/plugin/highlight.vim @@ -5,7 +5,7 @@ endif let s:save_cpo = &cpo set cpo&vim -au BufWinEnter * lua require 'highlight'.attach() +au BufWinEnter * lua require 'highlight'.setup() function! InstallParser(lang) call luaeval("require 'highlight'.install_parser(_A)", a:lang)