diff options
-rw-r--r-- | runtime/autoload/health/provider.vim | 2 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 20 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 10 | ||||
-rw-r--r-- | runtime/plugin/matchit.vim | 2 | ||||
-rw-r--r-- | src/nvim/lua/treesitter.c | 127 | ||||
-rw-r--r-- | src/nvim/testdir/shared.vim | 3 | ||||
-rw-r--r-- | src/nvim/testdir/test_windows_home.vim | 5 | ||||
-rw-r--r-- | third-party/cmake/BuildTreesitter.cmake | 76 | ||||
-rw-r--r-- | third-party/cmake/TreesitterCMakeLists.txt | 21 |
9 files changed, 172 insertions, 94 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index e8e38f581f..94fd7cf505 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -749,7 +749,7 @@ function! s:check_perl() abort call health#report_warn( \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s', \ current_cpan, latest_cpan), - \ ['Run in shell: cpanm Neovim::Ext']) + \ ['Run in shell: cpanm -n Neovim::Ext']) else call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan) endif diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index aaf13d1640..58cd535e98 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -74,6 +74,9 @@ Tree methods *lua-treesitter-tree* tstree:root() *tstree:root()* Return the root node of this tree. +tstree:copy() *tstree:copy()* + Returns a copy of the `tstree`. + Node methods *lua-treesitter-node* @@ -161,11 +164,28 @@ Tree-sitter queries are supported, with some limitations. Currently, the only supported match predicate is `eq?` (both comparing a capture against a string and two captures against each other). +A `query` consists of one or more patterns. A `pattern` is defined over node +types in the syntax tree. A `match` corresponds to specific elements of the +syntax tree which match a pattern. Patterns may optionally define captures +and predicates. A `capture` allows you to associate names with a specific +node in a pattern. A `predicate` adds arbitrary metadata and conditional data +to a match. + vim.treesitter.parse_query({lang}, {query}) *vim.treesitter.parse_query()* Parse {query} as a string. (If the query is in a file, the caller should read the contents into a string before calling). + Returns a `Query` (see |lua-treesitter-query|) object which can be used to + search nodes in the syntax tree for the patterns defined in {query} + using `iter_*` methods below. Exposes `info` and `captures` with + additional information about the {query}. + - `captures` contains the list of unique capture names defined in + {query}. + -` info.captures` also points to `captures`. + - `info.patterns` contains information about predicates. + + query:iter_captures({node}, {bufnr}, {start_row}, {end_row}) *query:iter_captures()* Iterate over all captures from all matches inside {node}. diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 0de3388356..19ef148afc 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -18,11 +18,13 @@ Parser.__index = Parser -- @returns If the tree changed with this call, the changed ranges function Parser:parse() if self.valid then - return self.tree + return self._tree_immutable end local changes - self.tree, changes = self._parser:parse(self:input_source()) + self._tree, changes = self._parser:parse(self._tree, self:input_source()) + + self._tree_immutable = self._tree:copy() self.valid = true @@ -32,7 +34,7 @@ function Parser:parse() end end - return self.tree, changes + return self._tree_immutable, changes end function Parser:input_source() @@ -45,7 +47,7 @@ function Parser:_on_bytes(bufnr, changed_tick, new_row, new_col, new_byte) local old_end_col = old_col + ((old_row == 0) and start_col or 0) local new_end_col = new_col + ((new_row == 0) and start_col or 0) - self._parser:edit(start_byte,start_byte+old_byte,start_byte+new_byte, + self._tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte, start_row, start_col, start_row+old_row, old_end_col, start_row+new_row, new_end_col) diff --git a/runtime/plugin/matchit.vim b/runtime/plugin/matchit.vim index 63be644062..d3583229fc 100644 --- a/runtime/plugin/matchit.vim +++ b/runtime/plugin/matchit.vim @@ -1,4 +1,4 @@ " Nvim: load the matchit plugin by default. -if stridx(&packpath, $VIMRUNTIME) >= 0 +if !exists("g:loaded_matchit") && stridx(&packpath, $VIMRUNTIME) >= 0 packadd matchit endif diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c53e208b00..0515bbfe53 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -23,11 +23,6 @@ #include "nvim/buffer.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} TSLua_parser; - -typedef struct { TSQueryCursor *cursor; int predicated_match; } TSLua_cursor; @@ -40,8 +35,6 @@ static struct luaL_Reg parser_meta[] = { { "__gc", parser_gc }, { "__tostring", parser_tostring }, { "parse", parser_parse }, - { "edit", parser_edit }, - { "tree", parser_tree }, { "set_included_ranges", parser_set_ranges }, { "included_ranges", parser_get_ranges }, { NULL, NULL } @@ -51,6 +44,8 @@ static struct luaL_Reg tree_meta[] = { { "__gc", tree_gc }, { "__tostring", tree_tostring }, { "root", tree_root }, + { "edit", tree_edit }, + { "copy", tree_copy }, { NULL, NULL } }; @@ -247,39 +242,32 @@ int tslua_push_parser(lua_State *L) return luaL_error(L, "no such language: %s", lang_name); } - TSParser *parser = ts_parser_new(); + TSParser **parser = lua_newuserdata(L, sizeof(TSParser *)); + *parser = ts_parser_new(); - if (!ts_parser_set_language(parser, lang)) { - ts_parser_delete(parser); + if (!ts_parser_set_language(*parser, lang)) { + ts_parser_delete(*parser); return luaL_error(L, "Failed to load language : %s", lang_name); } - TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] - p->parser = parser; - p->tree = NULL; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } -static TSLua_parser *parser_check(lua_State *L) +static TSParser ** parser_check(lua_State *L, uint16_t index) { - return luaL_checkudata(L, 1, "treesitter_parser"); + return luaL_checkudata(L, index, "treesitter_parser"); } static int parser_gc(lua_State *L) { - TSLua_parser *p = parser_check(L); + TSParser **p = parser_check(L, 1); if (!p) { return 0; } - ts_parser_delete(p->parser); - if (p->tree) { - ts_tree_delete(p->tree); - } - + ts_parser_delete(*p); return 0; } @@ -344,11 +332,17 @@ static void push_ranges(lua_State *L, static int parser_parse(lua_State *L) { - TSLua_parser *p = parser_check(L); - if (!p) { + TSParser **p = parser_check(L, 1); + if (!p || !(*p)) { return 0; } + TSTree *old_tree = NULL; + if (!lua_isnil(L, 2)) { + TSTree **tmp = tree_check(L, 2); + old_tree = tmp ? *tmp : NULL; + } + TSTree *new_tree = NULL; size_t len; const char *str; @@ -358,14 +352,14 @@ static int parser_parse(lua_State *L) // This switch is necessary because of the behavior of lua_isstring, that // consider numbers as strings... - switch (lua_type(L, 2)) { + switch (lua_type(L, 3)) { case LUA_TSTRING: - str = lua_tolstring(L, 2, &len); - new_tree = ts_parser_parse_string(p->parser, p->tree, str, len); + str = lua_tolstring(L, 3, &len); + new_tree = ts_parser_parse_string(*p, old_tree, str, len); break; case LUA_TNUMBER: - bufnr = lua_tointeger(L, 2); + bufnr = lua_tointeger(L, 3); buf = handle_get_buffer(bufnr); if (!buf) { @@ -373,7 +367,7 @@ static int parser_parse(lua_State *L) } input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 }; - new_tree = ts_parser_parse(p->parser, p->tree, input); + new_tree = ts_parser_parse(*p, old_tree, input); break; @@ -387,46 +381,42 @@ static int parser_parse(lua_State *L) return luaL_error(L, "An error occured when parsing."); } + // The new tree will be pushed to the stack, without copy, owwership is now to + // the lua GC. + // Old tree is still owned by the lua GC. uint32_t n_ranges = 0; - TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, - &n_ranges) : NULL; - if (p->tree) { - ts_tree_delete(p->tree); - } - p->tree = new_tree; + TSRange *changed = old_tree ? ts_tree_get_changed_ranges( + old_tree, new_tree, &n_ranges) : NULL; - tslua_push_tree(L, p->tree); + tslua_push_tree(L, new_tree, false); // [tree] - push_ranges(L, changed, n_ranges); + push_ranges(L, changed, n_ranges); // [tree, ranges] xfree(changed); return 2; } -static int parser_tree(lua_State *L) +static int tree_copy(lua_State *L) { - TSLua_parser *p = parser_check(L); - if (!p) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } - tslua_push_tree(L, p->tree); + tslua_push_tree(L, *tree, true); // [tree] + return 1; } -static int parser_edit(lua_State *L) +static int tree_edit(lua_State *L) { if (lua_gettop(L) < 10) { - lua_pushstring(L, "not enough args to parser:edit()"); + lua_pushstring(L, "not enough args to tree:edit()"); return lua_error(L); } - TSLua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (!p->tree) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } @@ -440,7 +430,7 @@ static int parser_edit(lua_State *L) TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, start_point, old_end_point, new_end_point }; - ts_tree_edit(p->tree, &edit); + ts_tree_edit(*tree, &edit); return 0; } @@ -453,8 +443,8 @@ static int parser_set_ranges(lua_State *L) "not enough args to parser:set_included_ranges()"); } - TSLua_parser *p = parser_check(L); - if (!p || !p->tree) { + TSParser **p = parser_check(L, 1); + if (!p) { return 0; } @@ -490,7 +480,7 @@ static int parser_set_ranges(lua_State *L) } // This memcpies ranges, thus we can free it afterwards - ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + ts_parser_set_included_ranges(*p, ranges, tbl_len); xfree(ranges); return 0; @@ -498,16 +488,15 @@ static int parser_set_ranges(lua_State *L) static int parser_get_ranges(lua_State *L) { - TSLua_parser *p = parser_check(L); - if (!p || !p->parser) { + TSParser **p = parser_check(L, 1); + if (!p) { return 0; } unsigned int len; - const TSRange *ranges = ts_parser_included_ranges(p->parser, &len); + const TSRange *ranges = ts_parser_included_ranges(*p, &len); push_ranges(L, ranges, len); - return 1; } @@ -517,14 +506,20 @@ static int parser_get_ranges(lua_State *L) /// push tree interface on lua stack. /// /// This makes a copy of the tree, so ownership of the argument is unaffected. -void tslua_push_tree(lua_State *L, TSTree *tree) +void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) { if (tree == NULL) { lua_pushnil(L); return; } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = ts_tree_copy(tree); + + if (do_copy) { + *ud = ts_tree_copy(tree); + } else { + *ud = tree; + } + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -537,20 +532,20 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_setfenv(L, -2); // [udata] } -static TSTree *tree_check(lua_State *L) +static TSTree **tree_check(lua_State *L, uint16_t index) { - TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); - return *ud; + TSTree **ud = luaL_checkudata(L, index, "treesitter_tree"); + return ud; } static int tree_gc(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - ts_tree_delete(tree); + ts_tree_delete(*tree); return 0; } @@ -562,11 +557,11 @@ static int tree_tostring(lua_State *L) static int tree_root(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - TSNode root = ts_tree_root_node(tree); + TSNode root = ts_tree_root_node(*tree); push_node(L, root, 1); return 1; } diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 4f1ddcd7b9..440da67412 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -291,6 +291,9 @@ func GetVimCommandClean() let cmd = substitute(cmd, '-u NONE', '--clean', '') let cmd = substitute(cmd, '--headless', '', '') + " Force using utf-8, Vim may pick up something else from the environment. + " let cmd ..= ' --cmd "set enc=utf8" ' + " Optionally run Vim under valgrind " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim index 2e311b9aa5..3c2db01444 100644 --- a/src/nvim/testdir/test_windows_home.vim +++ b/src/nvim/testdir/test_windows_home.vim @@ -1,8 +1,7 @@ " Test for $HOME on Windows. -if !has('win32') - finish -endif +source check.vim +CheckMSWindows let s:env = {} diff --git a/third-party/cmake/BuildTreesitter.cmake b/third-party/cmake/BuildTreesitter.cmake index 3212d6ea08..a55b2e36e8 100644 --- a/third-party/cmake/BuildTreesitter.cmake +++ b/third-party/cmake/BuildTreesitter.cmake @@ -1,22 +1,60 @@ -set(TS_CFLAGS "-O3 -Wall -Wextra") +include(CMakeParseArguments) -ExternalProject_Add(tree-sitter - PREFIX ${DEPS_BUILD_DIR} - URL ${TREESITTER_URL} - DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/tree-sitter - INSTALL_DIR ${DEPS_INSTALL_DIR} - DOWNLOAD_COMMAND ${CMAKE_COMMAND} - -DPREFIX=${DEPS_BUILD_DIR} - -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/tree-sitter - -DURL=${TREESITTER_URL} - -DEXPECTED_SHA256=${TREESITTER_SHA256} - -DTARGET=tree-sitter - -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake - BUILD_IN_SOURCE 1 - PATCH_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} CFLAGS=${TS_CFLAGS} - INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} install) +# BuildTreeSitter(TARGET targetname CONFIGURE_COMMAND ... BUILD_COMMAND ... INSTALL_COMMAND ...) +function(BuildTreeSitter) + cmake_parse_arguments(_treesitter + "BUILD_IN_SOURCE" + "TARGET" + "CONFIGURE_COMMAND;BUILD_COMMAND;INSTALL_COMMAND" + ${ARGN}) + + if(NOT _treesitter_CONFIGURE_COMMAND AND NOT _treesitter_BUILD_COMMAND + AND NOT _treesitter_INSTALL_COMMAND) + message(FATAL_ERROR "Must pass at least one of CONFIGURE_COMMAND, BUILD_COMMAND, INSTALL_COMMAND") + endif() + if(NOT _treesitter_TARGET) + set(_treesitter_TARGET "tree-sitter") + endif() + + ExternalProject_Add(tree-sitter + PREFIX ${DEPS_BUILD_DIR} + URL ${TREESITTER_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/tree-sitter + INSTALL_DIR ${DEPS_INSTALL_DIR} + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/tree-sitter + -DURL=${TREESITTER_URL} + -DEXPECTED_SHA256=${TREESITTER_SHA256} + -DTARGET=tree-sitter + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + BUILD_IN_SOURCE ${_treesitter_BUILD_IN_SOURCE} + PATCH_COMMAND "" + CONFIGURE_COMMAND "${_treesitter_CONFIGURE_COMMAND}" + BUILD_COMMAND "${_treesitter_BUILD_COMMAND}" + INSTALL_COMMAND "${_treesitter_INSTALL_COMMAND}") +endfunction() + +if(MSVC) + BuildTreeSitter(BUILD_IN_SOURCE + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt + ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE} + ) +else() + set(TS_CFLAGS "-O3 -Wall -Wextra") + BuildTreeSitter(BUILD_IN_SOURCE + CONFIGURE_COMMAND "" + BUILD_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} CFLAGS=${TS_CFLAGS} + INSTALL_COMMAND ${MAKE_PRG} CC=${DEPS_C_COMPILER} PREFIX=${DEPS_INSTALL_DIR} install) +endif() list(APPEND THIRD_PARTY_DEPS tree-sitter) diff --git a/third-party/cmake/TreesitterCMakeLists.txt b/third-party/cmake/TreesitterCMakeLists.txt new file mode 100644 index 0000000000..9e3ba3eeda --- /dev/null +++ b/third-party/cmake/TreesitterCMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8.12) +project(tree-sitter LANGUAGES C) + +file(GLOB SRC_FILES ${PROJECT_SOURCE_DIR}/lib/src/*.c) +foreach(sfile ${SRC_FILES}) + get_filename_component(f ${sfile} NAME) + if(${f} MATCHES "lib.c$") + list(REMOVE_ITEM SRC_FILES ${sfile}) + endif() +endforeach() +include_directories(${PROJECT_SOURCE_DIR}/lib/include) +add_library(tree-sitter ${SRC_FILES}) + +install(FILES + lib/include/tree_sitter/api.h + lib/include/tree_sitter/parser.h + DESTINATION include/tree_sitter) + +include(GNUInstallDirs) +install(TARGETS tree-sitter + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) |