diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index 061588da1a..da70d358a9 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -57,7 +57,7 @@ EOF functionaltest-lua) BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON" FUNCTIONALTEST=functionaltest-lua - DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF -DUSE_BUNDLED_TS_PARSERS=OFF" + DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF" ;; *) ;; diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index d2047c5b3d..2c19aa6e6b 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -208,6 +208,12 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz) set(TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 ) +set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.12.tar.gz) +set(TREESITTER_LUA_SHA256 b6d7c6d04e9101a2e589d25f1d61668301e776c0b8defa6eae8dd86272e9e7c3) + +set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/v0.1.0.tar.gz) +set(TREESITTER_VIM_SHA256 ea64fa211ccc7197669017b55911fdb56e8d4b6de96ba25c32b9586ec1c4f4c5) + set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.7.tar.gz) set(TREESITTER_SHA256 b355e968ec2d0241bbd96748e00a9038f83968f85d822ecb9940cbe4c42e182e) diff --git a/cmake.deps/cmake/BuildTreesitterParsers.cmake b/cmake.deps/cmake/BuildTreesitterParsers.cmake index 1ff86e89b9..dd3f4eb308 100644 --- a/cmake.deps/cmake/BuildTreesitterParsers.cmake +++ b/cmake.deps/cmake/BuildTreesitterParsers.cmake @@ -1,29 +1,25 @@ -ExternalProject_Add(treesitter-c -PREFIX ${DEPS_BUILD_DIR} -URL ${TREESITTER_C_URL} -DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/treesitter-c -DOWNLOAD_COMMAND ${CMAKE_COMMAND} - -DPREFIX=${DEPS_BUILD_DIR} - -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/treesitter-c - -DURL=${TREESITTER_C_URL} - -DEXPECTED_SHA256=${TREESITTER_C_SHA256} - -DTARGET=treesitter-c - -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake -PATCH_COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterParserCMakeLists.txt - ${DEPS_BUILD_DIR}/src/treesitter-c/CMakeLists.txt -CMAKE_ARGS - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_GENERATOR=${CMAKE_GENERATOR} - -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} - ${BUILD_TYPE_STRING} - -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} - -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_ALT_SEP} - # Pass toolchain - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - -DPARSERLANG=c +function(BuildTSParser LANG TS_URL TS_SHA256 TS_CMAKE_FILE) + set(NAME treesitter-${LANG}) + ExternalProject_Add(${NAME} + PREFIX ${DEPS_BUILD_DIR} + URL ${TREESITTER_C_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/${NAME} + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/${NAME} + -DURL=${TS_URL} + -DEXPECTED_SHA256=${TS_SHA256} + -DTARGET=${NAME} + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${TS_CMAKE_FILE} + ${DEPS_BUILD_DIR}/src/${NAME}/CMakeLists.txt + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + -DPARSERLANG=${LANG}) +endfunction() -BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} -INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE} -LIST_SEPARATOR |) +BuildTSParser(c ${TREESITTER_C_URL} ${TREESITTER_C_SHA256} TreesitterParserCMakeLists.txt) +BuildTSParser(lua ${TREESITTER_LUA_URL} ${TREESITTER_LUA_SHA256} TreesitterParserCMakeLists.txt) +BuildTSParser(vim ${TREESITTER_VIM_URL} ${TREESITTER_VIM_SHA256} TreesitterParserCMakeLists.txt) diff --git a/cmake.deps/cmake/TreesitterParserCMakeLists.txt b/cmake.deps/cmake/TreesitterParserCMakeLists.txt index 54bc35fb8a..9ec7a23864 100644 --- a/cmake.deps/cmake/TreesitterParserCMakeLists.txt +++ b/cmake.deps/cmake/TreesitterParserCMakeLists.txt @@ -1,10 +1,11 @@ cmake_minimum_required(VERSION 3.10) -# some parsers have c++ scanner, problem? -project(parser C) # CXX +project(parser C) + +file(GLOB source_files src/*.c) add_library(parser MODULE - src/parser.c + ${source_files} ) set_target_properties( parser diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index e0a0b34d28..b42b1de54b 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -123,7 +123,7 @@ foreach(PROG ${RUNTIME_PROGRAMS}) endforeach() globrecurse_wrapper(RUNTIME_FILES ${CMAKE_CURRENT_SOURCE_DIR} - *.vim *.lua *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) + *.vim *.lua *.scm *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} DIRECTORY) diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 2c6c9e4ed8..8d5e494601 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -375,9 +375,9 @@ get_captures_at_position({bufnr}, {row}, {col}) Gets a list of captures for a given cursor position Parameters: ~ - {bufnr} (number) The buffer number - {row} (number) The position row - {col} (number) The position column + {bufnr} (number) Buffer number (0 for current buffer) + {row} (number) Position row + {col} (number) Position column Return: ~ (table) A table of captures @@ -398,12 +398,14 @@ get_parser({bufnr}, {lang}, {opts}) *get_parser()* callback Parameters: ~ - {bufnr} The buffer the parser should be tied to - {lang} The filetype of this parser - {opts} Options object to pass to the created language tree + {bufnr} (number|nil) Buffer the parser should be tied to: (default + current buffer) + {lang} (string) |nil Filetype of this parser (default: buffer + filetype) + {opts} (table|nil) Options to pass to the created language tree Return: ~ - The parser + (table) Parser object get_string_parser({str}, {lang}, {opts}) *get_string_parser()* Gets a string parser @@ -417,8 +419,8 @@ is_ancestor({dest}, {source}) *is_ancestor()* Determines whether a node is the ancestor of another Parameters: ~ - {dest} (table) the possible ancestor - {source} (table) the possible descendant node + {dest} (table) Possible ancestor + {source} (table) Possible descendant node Return: ~ (boolean) True if dest is an ancestor of source @@ -427,20 +429,57 @@ is_in_node_range({node}, {line}, {col}) *is_in_node_range()* Determines whether (line, col) position is in node range Parameters: ~ - {node} Node defining the range - {line} A line (0-based) - {col} A column (0-based) + {node} (table) Node defining the range + {line} (number) Line (0-based) + {col} (number) Column (0-based) + + Return: ~ + (boolean) True if the position is in node range node_contains({node}, {range}) *node_contains()* Determines if a node contains a range Parameters: ~ - {node} (table) The node - {range} (table) The range + {node} (table) + {range} (table) Return: ~ (boolean) True if the node contains the range +start({bufnr}, {lang}, {opts}) *start()* + Start treesitter highlighting for a buffer + + Can be used in an ftplugin or FileType autocommand + + Note: By default, disables regex syntax highlighting, which may be + required for some plugins. In this case, add `{ syntax = true }`. + + Example: +> + + vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex', + callback = function(args) + vim.treesitter.start(args.buf, 'latex', { syntax = true }) + end + }) +< + + Parameters: ~ + {bufnr} (number|nil) Buffer to be highlighted (default: current + buffer) + {lang} (string|nil) Language of the parser (default: buffer + filetype) + {opts} (table|nil) Optional keyword arguments: + • `syntax` boolean Run regex syntax highlighting (default + false) + +stop({bufnr}) *stop()* + Stop treesitter highlighting for a buffer + + Parameters: ~ + {bufnr} (number|nil) Buffer to stop highlighting (default: current + buffer) + ============================================================================== Lua module: vim.treesitter.language *treesitter-language* diff --git a/runtime/ftplugin/lua.lua b/runtime/ftplugin/lua.lua new file mode 100644 index 0000000000..415cf28f9a --- /dev/null +++ b/runtime/ftplugin/lua.lua @@ -0,0 +1,3 @@ +if vim.g.ts_highlight_lua then + vim.treesitter.start() +end diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 6431162799..9c43811e03 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -32,9 +32,11 @@ setmetatable(M, { --- --- It is not recommended to use this, use vim.treesitter.get_parser() instead. --- ----@param bufnr The buffer the parser will be tied to ----@param lang The language of the parser ----@param opts Options to pass to the created language tree +---@param bufnr string Buffer the parser will be tied to (0 for current buffer) +---@param lang string Language of the parser +---@param opts table|nil Options to pass to the created language tree +--- +---@returns table Created parser object function M._create_parser(bufnr, lang, opts) language.require_language(lang) if bufnr == 0 then @@ -79,11 +81,11 @@ end --- If needed this will create the parser. --- Unconditionally attach the provided callback --- ----@param bufnr The buffer the parser should be tied to ----@param lang The filetype of this parser ----@param opts Options object to pass to the created language tree +---@param bufnr number|nil Buffer the parser should be tied to: (default current buffer) +---@param lang string |nil Filetype of this parser (default: buffer filetype) +---@param opts table|nil Options to pass to the created language tree --- ----@returns The parser +---@returns table Parser object function M.get_parser(bufnr, lang, opts) opts = opts or {} @@ -120,8 +122,8 @@ end --- Determines whether a node is the ancestor of another --- ----@param dest table the possible ancestor ----@param source table the possible descendant node +---@param dest table Possible ancestor +---@param source table Possible descendant node --- ---@returns (boolean) True if dest is an ancestor of source function M.is_ancestor(dest, source) @@ -156,9 +158,11 @@ end ---Determines whether (line, col) position is in node range --- ----@param node Node defining the range ----@param line A line (0-based) ----@param col A column (0-based) +---@param node table Node defining the range +---@param line number Line (0-based) +---@param col number Column (0-based) +--- +---@returns (boolean) True if the position is in node range function M.is_in_node_range(node, line, col) local start_line, start_col, end_line, end_col = M.get_node_range(node) if line >= start_line and line <= end_line then @@ -177,8 +181,8 @@ function M.is_in_node_range(node, line, col) end ---Determines if a node contains a range ----@param node table The node ----@param range table The range +---@param node table +---@param range table --- ---@returns (boolean) True if the node contains the range function M.node_contains(node, range) @@ -190,9 +194,9 @@ function M.node_contains(node, range) end ---Gets a list of captures for a given cursor position ----@param bufnr number The buffer number ----@param row number The position row ----@param col number The position column +---@param bufnr number Buffer number (0 for current buffer) +---@param row number Position row +---@param col number Position column --- ---@returns (table) A table of captures function M.get_captures_at_position(bufnr, row, col) @@ -241,4 +245,52 @@ function M.get_captures_at_position(bufnr, row, col) return matches end +--- Start treesitter highlighting for a buffer +--- +--- Can be used in an ftplugin or FileType autocommand +--- +--- Note: By default, disables regex syntax highlighting, which may be required for some plugins. +--- In this case, add `{ syntax = true }`. +--- +--- Example: +--- +---
+--- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
+--- callback = function(args)
+--- vim.treesitter.start(args.buf, 'latex', { syntax = true })
+--- end
+--- })
+---
+---
+---@param bufnr number|nil Buffer to be highlighted (default: current buffer)
+---@param lang string|nil Language of the parser (default: buffer filetype)
+---@param opts table|nil Optional keyword arguments:
+--- - `syntax` boolean Run regex syntax highlighting (default false)
+function M.start(bufnr, lang, opts)
+ bufnr = bufnr or a.nvim_get_current_buf()
+
+ local parser = M.get_parser(bufnr, lang)
+
+ M.highlighter.new(parser)
+
+ vim.b[bufnr].ts_highlight = true
+
+ if opts and opts.syntax then
+ vim.bo[bufnr].syntax = 'on'
+ end
+end
+
+---Stop treesitter highlighting for a buffer
+---
+---@param bufnr number|nil Buffer to stop highlighting (default: current buffer)
+function M.stop(bufnr)
+ bufnr = bufnr or a.nvim_get_current_buf()
+
+ if M.highlighter.active[bufnr] then
+ M.highlighter.active[bufnr]:destroy()
+ end
+
+ vim.bo[bufnr].syntax = 'on'
+end
+
return M
diff --git a/runtime/queries/c/injections.scm b/runtime/queries/c/injections.scm
new file mode 100644
index 0000000000..7e9e73449d
--- /dev/null
+++ b/runtime/queries/c/injections.scm
@@ -0,0 +1,3 @@
+(preproc_arg) @c
+
+; (comment) @comment
diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm
new file mode 100644
index 0000000000..92baba0f39
--- /dev/null
+++ b/runtime/queries/lua/highlights.scm
@@ -0,0 +1,192 @@
+;; Keywords
+
+"return" @keyword.return
+
+[
+ "goto"
+ "in"
+ "local"
+] @keyword
+
+(label_statement) @label
+
+(break_statement) @keyword
+
+(do_statement
+[
+ "do"
+ "end"
+] @keyword)
+
+(while_statement
+[
+ "while"
+ "do"
+ "end"
+] @repeat)
+
+(repeat_statement
+[
+ "repeat"
+ "until"
+] @repeat)
+
+(if_statement
+[
+ "if"
+ "elseif"
+ "else"
+ "then"
+ "end"
+] @conditional)
+
+(elseif_statement
+[
+ "elseif"
+ "then"
+ "end"
+] @conditional)
+
+(else_statement
+[
+ "else"
+ "end"
+] @conditional)
+
+(for_statement
+[
+ "for"
+ "do"
+ "end"
+] @repeat)
+
+(function_declaration
+[
+ "function"
+ "end"
+] @keyword.function)
+
+(function_definition
+[
+ "function"
+ "end"
+] @keyword.function)
+
+;; Operators
+
+[
+ "and"
+ "not"
+ "or"
+] @keyword.operator
+
+[
+ "+"
+ "-"
+ "*"
+ "/"
+ "%"
+ "^"
+ "#"
+ "=="
+ "~="
+ "<="
+ ">="
+ "<"
+ ">"
+ "="
+ "&"
+ "~"
+ "|"
+ "<<"
+ ">>"
+ "//"
+ ".."
+] @operator
+
+;; Punctuations
+
+[
+ ";"
+ ":"
+ ","
+ "."
+] @punctuation.delimiter
+
+;; Brackets
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+;; Variables
+
+(identifier) @variable
+
+((identifier) @variable.builtin
+ (#eq? @variable.builtin "self"))
+
+;; Constants
+
+((identifier) @constant
+ (#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
+
+(vararg_expression) @constant
+
+(nil) @constant.builtin
+
+[
+ (false)
+ (true)
+] @boolean
+
+;; Tables
+
+(field name: (identifier) @field)
+
+(dot_index_expression field: (identifier) @field)
+
+(table_constructor
+[
+ "{"
+ "}"
+] @constructor)
+
+;; Functions
+
+(parameters (identifier) @parameter)
+
+(function_call name: (identifier) @function.call)
+(function_declaration name: (identifier) @function)
+
+(function_call name: (dot_index_expression field: (identifier) @function.call))
+(function_declaration name: (dot_index_expression field: (identifier) @function))
+
+(method_index_expression method: (identifier) @method)
+
+(function_call
+ (identifier) @function.builtin
+ (#any-of? @function.builtin
+ ;; built-in functions in Lua 5.1
+ "assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs"
+ "load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print"
+ "rawequal" "rawget" "rawset" "require" "select" "setfenv" "setmetatable"
+ "tonumber" "tostring" "type" "unpack" "xpcall"))
+
+;; Others
+
+(comment) @comment
+
+(hash_bang_line) @comment
+
+(number) @number
+
+(string) @string
+
+;; Error
+(ERROR) @error
diff --git a/runtime/queries/lua/injections.scm b/runtime/queries/lua/injections.scm
new file mode 100644
index 0000000000..0e67329139
--- /dev/null
+++ b/runtime/queries/lua/injections.scm
@@ -0,0 +1,22 @@
+((function_call
+ name: [
+ (identifier) @_cdef_identifier
+ (_ _ (identifier) @_cdef_identifier)
+ ]
+ arguments: (arguments (string content: _ @c)))
+ (#eq? @_cdef_identifier "cdef"))
+
+((function_call
+ name: (_) @_vimcmd_identifier
+ arguments: (arguments (string content: _ @vim)))
+ (#any-of? @_vimcmd_identifier "vim.cmd" "vim.api.nvim_command" "vim.api.nvim_exec" "vim.api.nvim_cmd"))
+
+; ((function_call
+; name: (_) @_vimcmd_identifier
+; arguments: (arguments (string content: _ @query) .))
+; (#eq? @_vimcmd_identifier "vim.treesitter.query.set_query"))
+
+; ;; highlight string as query if starts with `;; query`
+; ((string ("string_content") @query) (#lua-match? @query "^%s*;+%s?query"))
+
+; (comment) @comment
diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm
new file mode 100644
index 0000000000..c02e226b66
--- /dev/null
+++ b/runtime/queries/vim/highlights.scm
@@ -0,0 +1,245 @@
+(identifier) @variable
+((identifier) @constant
+ (#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
+
+;; Keywords
+
+[
+ "if"
+ "else"
+ "elseif"
+ "endif"
+] @conditional
+
+[
+ "try"
+ "catch"
+ "finally"
+ "endtry"
+ "throw"
+] @exception
+
+[
+ "for"
+ "endfor"
+ "in"
+ "while"
+ "endwhile"
+ "break"
+ "continue"
+] @repeat
+
+[
+ "function"
+ "endfunction"
+] @keyword.function
+
+;; Function related
+(function_declaration name: (_) @function)
+(call_expression function: (identifier) @function)
+(parameters (identifier) @parameter)
+(default_parameter (identifier) @parameter)
+
+[ (bang) (spread) (at) ] @punctuation.special
+
+[ (no_option) (inv_option) (default_option) (option_name) ] @variable.builtin
+[
+ (scope)
+ "a:"
+ "$"
+] @namespace
+
+;; Commands and user defined commands
+
+[
+ "let"
+ "unlet"
+ "const"
+ "call"
+ "execute"
+ "normal"
+ "set"
+ "setlocal"
+ "silent"
+ "echo"
+ "echomsg"
+ "autocmd"
+ "augroup"
+ "return"
+ "syntax"
+ "lua"
+ "ruby"
+ "perl"
+ "python"
+ "highlight"
+ "command"
+ "delcommand"
+ "comclear"
+ "colorscheme"
+ "startinsert"
+ "stopinsert"
+ "global"
+ "runtime"
+ "wincmd"
+ "cnext"
+ "cprevious"
+ "cNext"
+ "vertical"
+ "leftabove"
+ "aboveleft"
+ "rightbelow"
+ "belowright"
+ "topleft"
+ "botright"
+ (unknown_command_name)
+] @keyword
+(map_statement cmd: _ @keyword)
+(command_name) @function.macro
+
+;; Syntax command
+
+(syntax_statement (keyword) @string)
+(syntax_statement [
+ "enable"
+ "on"
+ "off"
+ "reset"
+ "case"
+ "spell"
+ "foldlevel"
+ "iskeyword"
+ "keyword"
+ "match"
+ "cluster"
+ "region"
+] @keyword)
+
+(syntax_argument name: _ @keyword)
+
+[
+ "