├── scratch ├── lsp_definition.lua ├── example.c ├── text_object_test.lua ├── comment_levels.lua ├── cdef_test.lua ├── why_broke.lua ├── random.lua ├── simplest_test_fila.lua ├── multiline.lua ├── from_specs.lua ├── field.lua ├── test_file.lua ├── send_to_nlsp.lua ├── class.lua ├── wrapper.lua ├── output.txt ├── module_example.lua ├── lua_grammar_checklist.txt ├── docgen_output.txt ├── known_broken.lua ├── emmy_lua_grammar.lua ├── gh_issue_005.lua └── gen_howto.lua ├── bindings ├── python │ ├── tree_sitter_lua │ │ ├── py.typed │ │ ├── __init__.pyi │ │ ├── binding.c │ │ └── __init__.py │ └── tests │ │ └── test_binding.py ├── node │ ├── index.js │ ├── binding_test.js │ ├── index.d.ts │ └── binding.cc ├── c │ ├── tree-sitter-lua.pc.in │ └── tree-sitter-lua.h ├── swift │ ├── TreeSitterLua │ │ └── lua.h │ └── TreeSitterLuaTests │ │ └── TreeSitterLuaTests.swift ├── go │ ├── binding.go │ └── binding_test.go └── rust │ ├── build.rs │ └── lib.rs ├── example ├── tag.lua ├── simple.lua ├── config.lua ├── parameter.lua ├── return.lua ├── field.lua ├── function.lua ├── class.lua ├── see.lua ├── eval.lua └── brief.lua ├── .gitignore ├── .luarc.json ├── .gitattributes ├── go.mod ├── lua ├── nlsp │ ├── log.lua │ ├── structures.lua │ ├── ts │ │ ├── query.lua │ │ └── init.lua │ ├── utils.lua │ ├── init.lua │ ├── external │ │ └── luacheck.lua │ ├── state.lua │ ├── methods.lua │ └── rpc.lua ├── docgen │ ├── utils.lua │ ├── log.lua │ ├── init.lua │ ├── transformers.lua │ └── help.lua ├── ts_lua │ └── init.lua └── tests │ ├── docgen │ ├── config_spec.lua │ ├── tag_spec.lua │ └── brief_spec.lua │ └── nlsp │ └── text_document_sync_spec.lua ├── .stylua.toml ├── query └── lua │ ├── variables.scm │ ├── module_returns.scm │ ├── _test.scm │ ├── documentation.scm │ └── locals.scm ├── tests └── init.lua ├── plugin └── ts_lua.lua ├── queries └── lua │ ├── locals.scm │ ├── refactoring.scm │ ├── injections.scm │ ├── textobjects.scm │ └── highlights.scm ├── test └── corpus │ ├── simple_modules.txt │ ├── expressions.txt │ ├── comments.txt │ ├── strings.txt │ ├── blocks.txt │ ├── functions.txt │ ├── gh_issue_005.txt │ └── statements.txt ├── stream └── clips.md ├── .luacheckrc ├── .editorconfig ├── tree-sitter.json ├── Cargo.toml ├── binding.gyp ├── .github └── workflows │ ├── lint.yml │ └── ci.yml ├── pyproject.toml ├── Makefile ├── package.json ├── Package.swift ├── src ├── tree_sitter │ ├── alloc.h │ ├── parser.h │ └── array.h └── scanner.c ├── docker └── Dockerfile ├── setup.py ├── todo.scm ├── README.md └── HOWTO.md /scratch/lsp_definition.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_lua/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/tag.lua: -------------------------------------------------------------------------------- 1 | ---@tag your_module 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | parser/ 4 | -------------------------------------------------------------------------------- /example/simple.lua: -------------------------------------------------------------------------------- 1 | local x = 5 2 | 3 | print(x) 4 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspace.checkThirdParty": false 3 | } -------------------------------------------------------------------------------- /scratch/example.c: -------------------------------------------------------------------------------- 1 | 2 | int main() { 3 | return 5; 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/parser.c binary 2 | src/grammar.json binary 3 | src/tree_sitter/ binary 4 | -------------------------------------------------------------------------------- /scratch/text_object_test.lua: -------------------------------------------------------------------------------- 1 | local x = function() 2 | print "hello" 3 | return 5 4 | end 5 | -------------------------------------------------------------------------------- /example/config.lua: -------------------------------------------------------------------------------- 1 | ---@config { ['function_order'] = 'ascending', ['class_order'] = 'descending' } 2 | -------------------------------------------------------------------------------- /scratch/comment_levels.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | --hello world ] ] 3 | --]] 4 | 5 | local x = [[ wow ] cool ]] 6 | -------------------------------------------------------------------------------- /scratch/cdef_test.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | 3 | ffi.cdef [[ 4 | int test_thing(void) 5 | ]] 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tree-sitter/tree-sitter-lua 2 | 3 | go 1.23 4 | 5 | require github.com/tree-sitter/go-tree-sitter v0.23.1 6 | -------------------------------------------------------------------------------- /scratch/why_broke.lua: -------------------------------------------------------------------------------- 1 | local x = {} 2 | 3 | --- This function has documentation 4 | x.hello = function() 5 | return 5 6 | end 7 | 8 | return x 9 | -------------------------------------------------------------------------------- /lua/nlsp/log.lua: -------------------------------------------------------------------------------- 1 | return require("plenary.log").new { 2 | plugin = "nlsp", 3 | level = (vim.loop.os_getenv "USER" == "tj" and "trace") or "info", 4 | } 5 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferDouble" 6 | no_call_parentheses = true 7 | -------------------------------------------------------------------------------- /scratch/random.lua: -------------------------------------------------------------------------------- 1 | 2 | function HelloWorld(a, b) 3 | return a + b 4 | end 5 | 6 | if hello "yup" then 7 | print { "wow" } 8 | 9 | print; a 10 | end 11 | 12 | -------------------------------------------------------------------------------- /scratch/simplest_test_fila.lua: -------------------------------------------------------------------------------- 1 | --- Description of X 2 | ---@param a string: Hello X 3 | local X = function(a) 4 | return a 5 | end 6 | 7 | return { 8 | X = X, 9 | } 10 | -------------------------------------------------------------------------------- /scratch/multiline.lua: -------------------------------------------------------------------------------- 1 | -- This is driving me crazy 2 | 3 | --- Function 4 | ---@param a number: line 1 5 | ---line 1.1 6 | ---@param b number: line 2 7 | ---line 2.1 8 | function M.hello(a) 9 | return a 10 | end 11 | -------------------------------------------------------------------------------- /lua/docgen/utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | 3 | utils.read = function(f) 4 | local fp = assert(io.open(f)) 5 | local contents = fp:read "*all" 6 | fp:close() 7 | 8 | return contents 9 | end 10 | 11 | return utils 12 | -------------------------------------------------------------------------------- /bindings/node/index.js: -------------------------------------------------------------------------------- 1 | const root = require("path").join(__dirname, "..", ".."); 2 | 3 | module.exports = require("node-gyp-build")(root); 4 | 5 | try { 6 | module.exports.nodeTypeInfo = require("../../src/node-types.json"); 7 | } catch (_) {} 8 | -------------------------------------------------------------------------------- /query/lua/variables.scm: -------------------------------------------------------------------------------- 1 | (variable_declaration 2 | (variable_declarator 3 | (field_expression (identifier) (property_identifier) @expr) @var) @x) @y 4 | 5 | ; (local_variable_declaration 6 | ; (variable_declarator) @decl 7 | ; ) @local 8 | -------------------------------------------------------------------------------- /tests/init.lua: -------------------------------------------------------------------------------- 1 | vim.opt.rtp:append { ".", "../plenary.nvim" } 2 | 3 | vim.cmd [[runtime! plugin/plenary.vim]] 4 | vim.cmd [[runtime! plugin/ts_lua.lua]] 5 | 6 | -- Set the parser to this lua parser 7 | vim.treesitter.language.add("lua", { 8 | path = "./parser/lua.so", 9 | }) 10 | -------------------------------------------------------------------------------- /example/parameter.lua: -------------------------------------------------------------------------------- 1 | local math = {} 2 | 3 | --- Will return the bigger number 4 | ---@param a number: first number 5 | ---@param b number: second number 6 | math.max = function(a, b) 7 | if a > b then 8 | return a 9 | end 10 | return b 11 | end 12 | 13 | return math 14 | -------------------------------------------------------------------------------- /scratch/from_specs.lua: -------------------------------------------------------------------------------- 1 | local x = {} 2 | 3 | --- This function has documentation 4 | ---@param abc string: Docs for abc 5 | ---@param def string: Other docs for def 6 | ---@param bxy string: Final docs 7 | function x.hello(abc, def, bxy) 8 | return abc .. def .. bxy 9 | end 10 | 11 | return x 12 | -------------------------------------------------------------------------------- /scratch/field.lua: -------------------------------------------------------------------------------- 1 | local x = {} 2 | 3 | --- This function has documentation 4 | ---@param t table: Some table 5 | ---@field name string: name 6 | function x.hello(t) 7 | return t.name 8 | end 9 | 10 | --- Whats the node of this snippet 11 | x.bye = function() 12 | return 5 13 | end 14 | 15 | return x 16 | -------------------------------------------------------------------------------- /scratch/test_file.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- Example of my_func 4 | ---@param y string: Y description 5 | M.my_func = function(y) end 6 | 7 | --- This is a description of the function 8 | ---@param x table: X description 9 | ---@return nil 10 | function M.other_func(x) 11 | print(x) 12 | end 13 | 14 | return M 15 | -------------------------------------------------------------------------------- /bindings/c/tree-sitter-lua.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@PREFIX@ 2 | libdir=@LIBDIR@ 3 | includedir=@INCLUDEDIR@ 4 | 5 | Name: tree-sitter-lua 6 | Description: Lua grammar for tree-sitter 7 | URL: @URL@ 8 | Version: @VERSION@ 9 | Requires: @REQUIRES@ 10 | Libs: -L${libdir} @ADDITIONAL_LIBS@ -ltree-sitter-lua 11 | Cflags: -I${includedir} 12 | -------------------------------------------------------------------------------- /bindings/node/binding_test.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const assert = require("node:assert"); 4 | const { test } = require("node:test"); 5 | 6 | test("can load grammar", () => { 7 | const parser = new (require("tree-sitter"))(); 8 | assert.doesNotThrow(() => parser.setLanguage(require("."))); 9 | }); 10 | -------------------------------------------------------------------------------- /example/return.lua: -------------------------------------------------------------------------------- 1 | local math = {} 2 | 3 | --- Will return the bigger number 4 | ---@param a number: first number 5 | ---@param b number: second number 6 | ---@return number: bigger number 7 | function math.max = function(a, b) 8 | if a > b then 9 | return a 10 | end 11 | return b 12 | end 13 | 14 | return math 15 | -------------------------------------------------------------------------------- /bindings/c/tree-sitter-lua.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_LUA_H_ 2 | #define TREE_SITTER_LUA_H_ 3 | 4 | typedef struct TSLanguage TSLanguage; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | const TSLanguage *tree_sitter_lua(void); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // TREE_SITTER_LUA_H_ 17 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_lua/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | 3 | # NOTE: uncomment these to include any queries that this grammar contains: 4 | 5 | # HIGHLIGHTS_QUERY: Final[str] 6 | # INJECTIONS_QUERY: Final[str] 7 | # LOCALS_QUERY: Final[str] 8 | # TAGS_QUERY: Final[str] 9 | 10 | def language() -> object: ... 11 | -------------------------------------------------------------------------------- /lua/nlsp/structures.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Is this file useful at all?... 2 | local structures = {} 3 | 4 | structures.TextDocumentItem = {} 5 | 6 | structures.TextDocumentItem.new = function(filename, text) 7 | return { 8 | uri = vim.uri_from_fname(filename), 9 | text = text, 10 | } 11 | end 12 | 13 | return structures 14 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterLua/lua.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_LUA_H_ 2 | #define TREE_SITTER_LUA_H_ 3 | 4 | typedef struct TSLanguage TSLanguage; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | const TSLanguage *tree_sitter_lua(void); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // TREE_SITTER_LUA_H_ 17 | -------------------------------------------------------------------------------- /example/field.lua: -------------------------------------------------------------------------------- 1 | local x = {} 2 | 3 | --- This function has documentation 4 | ---@param t table: some input table 5 | ---@field k1 number: first key of input table 6 | ---@field key function: second key of input table 7 | ---@field key3 table: third key of input table 8 | function x.hello(t) 9 | return 0 10 | end 11 | 12 | return x 13 | -------------------------------------------------------------------------------- /lua/ts_lua/init.lua: -------------------------------------------------------------------------------- 1 | local plenary_debug = require "plenary.debug_utils" 2 | 3 | -- local Path = require "plenary.path" 4 | -- local Iter = require "plenary.iterators" 5 | 6 | local ts_lua_dir = vim.fn.fnamemodify(plenary_debug.sourced_filepath(), ":h:h:h") 7 | 8 | local M = {} 9 | 10 | M.plugin_dir = ts_lua_dir 11 | 12 | return M 13 | -------------------------------------------------------------------------------- /query/lua/module_returns.scm: -------------------------------------------------------------------------------- 1 | ( 2 | (program 3 | (variable_declaration 4 | (variable_declarator (identifier) @variable)) 5 | 6 | (module_return_statement 7 | (tableconstructor 8 | (fieldlist 9 | (field 10 | (identifier) @exported 11 | (identifier) @defined))))) 12 | 13 | (#eq? @defined @variable) 14 | ) 15 | -------------------------------------------------------------------------------- /scratch/send_to_nlsp.lua: -------------------------------------------------------------------------------- 1 | local Job = require "plenary.job" 2 | 3 | local rpc = require "nlsp.rpc" 4 | 5 | local j = Job:new { 6 | command = "nvim", 7 | args = { "--headless", "-c", 'lua require("nlsp").start()' }, 8 | } 9 | 10 | j:start() 11 | 12 | rpc.send_message({ 13 | method = "initialize", 14 | params = { 1, 2, 3 }, 15 | }, j.stdin) 16 | -------------------------------------------------------------------------------- /bindings/python/tests/test_binding.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import tree_sitter, tree_sitter_lua 4 | 5 | 6 | class TestLanguage(TestCase): 7 | def test_can_load_grammar(self): 8 | try: 9 | tree_sitter.Language(tree_sitter_lua.language()) 10 | except Exception: 11 | self.fail("Error loading Lua grammar") 12 | -------------------------------------------------------------------------------- /bindings/go/binding.go: -------------------------------------------------------------------------------- 1 | package tree_sitter_lua 2 | 3 | // #cgo CFLAGS: -std=c11 -fPIC 4 | // #include "../../src/parser.c" 5 | // // NOTE: if your language has an external scanner, add it here. 6 | import "C" 7 | 8 | import "unsafe" 9 | 10 | // Get the tree-sitter Language for this grammar. 11 | func Language() unsafe.Pointer { 12 | return unsafe.Pointer(C.tree_sitter_lua()) 13 | } 14 | -------------------------------------------------------------------------------- /query/lua/_test.scm: -------------------------------------------------------------------------------- 1 | ( 2 | [ 3 | (variable_declaration 4 | documentation: (emmy_documentation) @func 5 | name: (variable_declarator (identifier) @name)) @doc 6 | 7 | (function_statement 8 | documentation: (emmy_documentation) @func 9 | name: (function_name (identifier) @name)) @doc 10 | ] 11 | 12 | (module_return_statement (identifier) @exported) 13 | (#eq? @exported @name)) 14 | -------------------------------------------------------------------------------- /plugin/ts_lua.lua: -------------------------------------------------------------------------------- 1 | if vim.g.ts_lua_skip_queries then 2 | return 3 | end 4 | 5 | local root = require("ts_lua").plugin_dir 6 | vim.treesitter.language.add("lua", { 7 | path = root .. "/parser/lua.so", 8 | }) 9 | 10 | for _, file in ipairs(vim.fn.glob(root .. "/queries/lua/*", false, true)) do 11 | vim.treesitter.query.set("lua", vim.fn.fnamemodify(file, ":t:r"), table.concat(vim.fn.readfile(file), "\n")) 12 | end 13 | -------------------------------------------------------------------------------- /bindings/go/binding_test.go: -------------------------------------------------------------------------------- 1 | package tree_sitter_lua_test 2 | 3 | import ( 4 | "testing" 5 | 6 | tree_sitter "github.com/tree-sitter/go-tree-sitter" 7 | tree_sitter_lua "github.com/tree-sitter/tree-sitter-lua/bindings/go" 8 | ) 9 | 10 | func TestCanLoadGrammar(t *testing.T) { 11 | language := tree_sitter.NewLanguage(tree_sitter_lua.Language()) 12 | if language == nil { 13 | t.Errorf("Error loading Lua grammar") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterLuaTests/TreeSitterLuaTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import SwiftTreeSitter 3 | import TreeSitterLua 4 | 5 | final class TreeSitterLuaTests: XCTestCase { 6 | func testCanLoadGrammar() throws { 7 | let parser = Parser() 8 | let language = Language(language: tree_sitter_lua()) 9 | XCTAssertNoThrow(try parser.setLanguage(language), 10 | "Error loading Lua grammar") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /queries/lua/locals.scm: -------------------------------------------------------------------------------- 1 | ;; Variable and field declarations 2 | ((variable_declarator 3 | (identifier) @definition.var)) 4 | 5 | ;; Parameters 6 | (parameter_list (identifier) @definition.parameter) 7 | 8 | 9 | ;; Scopes 10 | [ 11 | (program) 12 | (function_statement) 13 | (function_start) 14 | (if_statement) 15 | (for_statement) 16 | (repeat_statement) 17 | (while_statement) 18 | (do_statement)] @scope 19 | 20 | ;; References 21 | [(identifier)] @reference 22 | -------------------------------------------------------------------------------- /lua/nlsp/ts/query.lua: -------------------------------------------------------------------------------- 1 | local debug_utils = require "plenary.debug_utils" 2 | local Path = require "plenary.path" 3 | 4 | local M = {} 5 | 6 | local QUERY_PATH = Path:new(vim.fn.fnamemodify(debug_utils.sourced_filepath(), ":p:h:h:h:h"), "query", "lua") 7 | 8 | function M.get(name, lang) 9 | lang = lang or "lua" 10 | 11 | local filepath = QUERY_PATH / (name .. ".scm") 12 | 13 | return vim.treesitter.query.parse(lang, filepath:read()) 14 | end 15 | 16 | --[[ 17 | print(vim.inspect(M.get('locals'))) 18 | --]] 19 | 20 | return M 21 | -------------------------------------------------------------------------------- /test/corpus/simple_modules.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Return Number 4 | ================== 5 | 6 | return 1 7 | 8 | --- 9 | 10 | (program 11 | (module_return_statement (number))) 12 | 13 | ================== 14 | Return String 15 | ================== 16 | 17 | return "hello" 18 | 19 | --- 20 | 21 | (program 22 | (module_return_statement (string))) 23 | 24 | ================== 25 | Return variable 26 | ================== 27 | 28 | return foobar 29 | 30 | --- 31 | 32 | (program 33 | (module_return_statement (identifier))) 34 | -------------------------------------------------------------------------------- /stream/clips.md: -------------------------------------------------------------------------------- 1 | 2 | # Vim: 3 | 4 | Intermediate vim movement: https://clips.twitch.tv/ToughViscousHorseThisIsSparta 5 | Why Neovim? https://clips.twitch.tv/SoftVainWaffleOSfrog 6 | 7 | # Memes: 8 | 9 | First Neovim Help Desk & Prime Joke: https://clips.twitch.tv/SecretiveFragileMarjoramSpicyBoy 10 | 25 dollars a month: https://clips.twitch.tv/ExquisiteRichSardineLitFam 11 | Muniter laugh: https://clips.twitch.tv/EntertainingSavageFriesPicoMause 12 | 13 | # Laughs: 14 | 15 | Lean in & say hello: https://clips.twitch.tv/BusyPerfectAsteriskCharlietheUnicorn 16 | -------------------------------------------------------------------------------- /queries/lua/refactoring.scm: -------------------------------------------------------------------------------- 1 | 2 | ;; Grabs all the local variable declarations. This is useful for scope 3 | ;; variable passing. Which variables do we need to pass to the extracted 4 | ;; function? 5 | (variable_declaration 6 | (local) 7 | (variable_declarator 8 | (identifier) @definition.local_var)) 9 | 10 | 11 | ;; grabs all the arguments that are passed into the function. Needed for 12 | ;; function extraction, 106 13 | (parameter_list (identifier) @definition.function_argument) 14 | 15 | (function) @definition.scope 16 | (function_statement) @definition.scope 17 | -------------------------------------------------------------------------------- /example/function.lua: -------------------------------------------------------------------------------- 1 | local m = {} 2 | 3 | --- We will not generate documentation for this function 4 | local some_func = function() 5 | return 5 6 | end 7 | 8 | --- We will not generate documentation for this function 9 | --- because it has `__` as prefix. This is the one exception 10 | m.__hidden = function() 11 | return 5 12 | end 13 | 14 | --- The documentation for this function will be generated. 15 | --- The markdown renderer will be used again.
16 | --- With the same set of features 17 | m.actual_func = function() 18 | return 5 19 | end 20 | 21 | return m 22 | -------------------------------------------------------------------------------- /example/class.lua: -------------------------------------------------------------------------------- 1 | local m = {} 2 | 3 | ---@class passwd @The passwd c struct 4 | ---@field pw_name string: username 5 | ---@field pw_passwd string: user password 6 | ---@field pw_uid number: user id 7 | ---@field pw_gid number: groupd id 8 | ---@field pw_gecos string: user information 9 | ---@field pw_dir string: user home directory 10 | ---@field pw_shell string: user default shell 11 | 12 | --- Get user by id 13 | ---@param id number: user id 14 | ---@return passwd: returns a password table 15 | function m.get_user(id) 16 | return ffi.C.getpwuid(id) 17 | end 18 | 19 | return m 20 | -------------------------------------------------------------------------------- /query/lua/documentation.scm: -------------------------------------------------------------------------------- 1 | (variable_declaration 2 | documentation: (emmy_documentation) @func 3 | name: (variable_declarator (identifier) @name)) @doc 4 | 5 | (function_statement 6 | documentation: (emmy_documentation) @func 7 | name: (function_name (identifier) @name)) @doc 8 | 9 | ; (module_return_statement (identifier) @exported) 10 | 11 | ; Get the briefs for the module 12 | ((documentation_brief) @brief) 13 | 14 | ((documentation_tag) @tag) 15 | 16 | ((documentation_config) @config) 17 | 18 | ((documentation_class) @class) 19 | 20 | ((documentation_command) @command) 21 | -------------------------------------------------------------------------------- /bindings/node/index.d.ts: -------------------------------------------------------------------------------- 1 | type BaseNode = { 2 | type: string; 3 | named: boolean; 4 | }; 5 | 6 | type ChildNode = { 7 | multiple: boolean; 8 | required: boolean; 9 | types: BaseNode[]; 10 | }; 11 | 12 | type NodeInfo = 13 | | (BaseNode & { 14 | subtypes: BaseNode[]; 15 | }) 16 | | (BaseNode & { 17 | fields: { [name: string]: ChildNode }; 18 | children: ChildNode[]; 19 | }); 20 | 21 | type Language = { 22 | name: string; 23 | language: unknown; 24 | nodeTypeInfo: NodeInfo[]; 25 | }; 26 | 27 | declare const language: Language; 28 | export = language; 29 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- Rerun tests only if their modification time changed. 2 | cache = true 3 | 4 | std = luajit 5 | codes = true 6 | 7 | self = false 8 | 9 | -- Glorious list of warnings: https://luacheck.readthedocs.io/en/stable/warnings.html 10 | ignore = { 11 | "212", -- Unused argument, In the case of callback function, _arg_name is easier to understand than _, so this option is set to off. 12 | "122", -- Indirectly setting a readonly global 13 | "331" 14 | } 15 | 16 | globals = { 17 | "_", 18 | } 19 | 20 | -- Global objects defined by the C code 21 | read_globals = { 22 | "vim", 23 | } 24 | -------------------------------------------------------------------------------- /queries/lua/injections.scm: -------------------------------------------------------------------------------- 1 | ((function_call 2 | prefix: ( 3 | (identifier) @_prefix_1 4 | (identifier) @_prefix_2) 5 | 6 | args: (string_argument) @c) 7 | 8 | (#eq? @_prefix_2 "cdef") 9 | (#offset! @c 0 2 0 -2)) 10 | 11 | 12 | (comment) @comment 13 | 14 | ((function_call 15 | prefix: ( 16 | (identifier) @_prefix_1 17 | (identifier) @_prefix_2) 18 | args: (string_argument) @vim) 19 | 20 | (#eq? @_prefix_1 "vim") 21 | (#eq? @_prefix_2 "cmd") 22 | (#offset! @vim 0 2 0 -2)) 23 | 24 | (documentation_command usage: (_) @vim) 25 | -------------------------------------------------------------------------------- /example/see.lua: -------------------------------------------------------------------------------- 1 | local math = {} 2 | 3 | --- Will return the smaller number 4 | ---@param a number: first number 5 | ---@param b number: second number 6 | ---@return number: smaller number 7 | ---@see math.max 8 | function math.min(a, b) 9 | if a < b then 10 | return a 11 | end 12 | return b 13 | end 14 | 15 | --- Will return the bigger number 16 | ---@param a number: first number 17 | ---@param b number: second number 18 | ---@return number: bigger number 19 | ---@see math.min 20 | function math.max(a, b) 21 | if a > b then 22 | return a 23 | end 24 | return b 25 | end 26 | 27 | return math 28 | -------------------------------------------------------------------------------- /test/corpus/expressions.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Can handle binary expressions with funcitons 4 | ================== 5 | 6 | local x = vim.x() or vim.y() 7 | 8 | --- 9 | 10 | (program 11 | (variable_declaration 12 | (local) 13 | (variable_declarator 14 | (identifier)) 15 | (binary_operation 16 | (function_call 17 | (identifier) 18 | (identifier) 19 | (function_call_paren) 20 | (function_call_paren)) 21 | (function_call 22 | (identifier) 23 | (identifier) 24 | (function_call_paren) 25 | (function_call_paren))))) 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.{json,toml,yml,gyp}] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.{c,cc,h}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.rs] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [*.{py,pyi}] 23 | indent_style = space 24 | indent_size = 4 25 | 26 | [*.swift] 27 | indent_style = space 28 | indent_size = 4 29 | 30 | [*.go] 31 | indent_style = tab 32 | indent_size = 8 33 | 34 | [Makefile] 35 | indent_style = tab 36 | indent_size = 8 37 | 38 | [parser.c] 39 | indent_size = 2 40 | -------------------------------------------------------------------------------- /lua/nlsp/utils.lua: -------------------------------------------------------------------------------- 1 | local utils = {} 2 | 3 | local NIL = vim.NIL 4 | 5 | --@private 6 | --- Returns its argument, but converts `vim.NIL` to Lua `nil`. 7 | --@param v (any) Argument 8 | --@returns (any) 9 | function utils.convert_NIL(v) 10 | if v == NIL then 11 | return nil 12 | end 13 | return v 14 | end 15 | 16 | --@private 17 | --- Checks whether a given path exists and is a directory. 18 | --@param filename (string) path to check 19 | --@returns (bool) 20 | function utils.is_dir(filename) 21 | local stat = vim.loop.fs_stat(filename) 22 | return stat and stat.type == "directory" or false 23 | end 24 | 25 | return utils 26 | -------------------------------------------------------------------------------- /scratch/class.lua: -------------------------------------------------------------------------------- 1 | local TestJob = {} 2 | TestJob.__index = TestJob 3 | 4 | ---@class TestArray @Numeric table 5 | ---@field len int: size 6 | 7 | ---@class TestMap @Map-like table 8 | 9 | --- Some docs for that class 10 | --- 11 | ---@class TestJob: Job @this is some documentation 12 | ---@field cmd string: external command 13 | ---@field args table: arguments for command 14 | function TestJob:new(o) 15 | local obj = o or {} 16 | return setmetatable(obj, self) 17 | end 18 | 19 | --- Start a job 20 | ---@param timeout number: set timeout, default 1000 21 | function TestJob:start(timeout) 22 | return timeout 23 | end 24 | 25 | return TestJob 26 | -------------------------------------------------------------------------------- /bindings/node/binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | extern "C" TSLanguage *tree_sitter_lua(); 6 | 7 | // "tree-sitter", "language" hashed with BLAKE2 8 | const napi_type_tag LANGUAGE_TYPE_TAG = { 9 | 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 10 | }; 11 | 12 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 13 | exports["name"] = Napi::String::New(env, "lua"); 14 | auto language = Napi::External::New(env, tree_sitter_lua()); 15 | language.TypeTag(&LANGUAGE_TYPE_TAG); 16 | exports["language"] = language; 17 | return exports; 18 | } 19 | 20 | NODE_API_MODULE(tree_sitter_lua_binding, Init) 21 | -------------------------------------------------------------------------------- /tree-sitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "grammars": [ 3 | { 4 | "name": "nlua", 5 | "camelcase": "Nlua", 6 | "scope": "source.nlua", 7 | "path": ".", 8 | "file-types": [ 9 | "lua" 10 | ], 11 | "highlights": [ 12 | "queries/highlights.scm" 13 | ] 14 | } 15 | ], 16 | "metadata": { 17 | "version": "1.0.0", 18 | "license": "ISC", 19 | "description": "", 20 | "links": { 21 | "repository": "https://github.com/tree-sitter/tree-sitter-nlua" 22 | } 23 | }, 24 | "bindings": { 25 | "c": true, 26 | "go": true, 27 | "node": true, 28 | "python": true, 29 | "rust": true, 30 | "swift": true 31 | } 32 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree-sitter-lua" 3 | description = "lua grammar for the tree-sitter parsing library" 4 | version = "0.0.1" 5 | keywords = ["incremental", "parsing", "lua"] 6 | categories = ["parsing", "text-editors"] 7 | repository = "https://github.com/tree-sitter/tree-sitter-javascript" 8 | edition = "2018" 9 | license = "MIT" 10 | 11 | build = "bindings/rust/build.rs" 12 | include = [ 13 | "bindings/rust/*", 14 | "grammar.js", 15 | "queries/*", 16 | "src/*", 17 | ] 18 | 19 | [lib] 20 | path = "bindings/rust/lib.rs" 21 | 22 | [dependencies] 23 | tree-sitter-language = "0.1.0" 24 | 25 | [build-dependencies] 26 | cc = "1.0" 27 | 28 | [dev-dependencies] 29 | tree-sitter = "0.23" -------------------------------------------------------------------------------- /lua/tests/docgen/config_spec.lua: -------------------------------------------------------------------------------- 1 | local docgen = require "docgen" 2 | 3 | local eq = assert.are.same 4 | 5 | describe("config", function() 6 | describe("transform", function() 7 | it("should interpret config strings", function() 8 | local nodes = docgen.get_nodes [[---@config { ['function_order'] = "ascending" }]] 9 | eq({ config = { 10 | ["function_order"] = "ascending", 11 | } }, nodes) 12 | end) 13 | 14 | it("should interpret config functions", function() 15 | local nodes = docgen.get_nodes [[---@config { ['function_order'] = function(tbl) table.sort(tbl) end }]] 16 | eq("function", type(nodes.config.function_order)) 17 | end) 18 | end) 19 | end) 20 | -------------------------------------------------------------------------------- /example/eval.lua: -------------------------------------------------------------------------------- 1 | local m = {} 2 | 3 | --- The documentation for this function will be generated. 4 | --- The markdown renderer will be used again.
5 | --- With the same set of features 6 | ---@eval { ['description'] = require('your_module').__format_keys() } 7 | m.actual_func = function() 8 | return 5 9 | end 10 | 11 | local static_values = { 12 | "a", 13 | "b", 14 | "c", 15 | "d", 16 | } 17 | 18 | m.__format_keys = function() 19 | -- we want to do formatting 20 | local table = { "
", "Static Values: ~" }
21 | 
22 |   for _, v in ipairs(static_values) do
23 |     table.insert(table, "    " .. v)
24 |   end
25 | 
26 |   table.insert(table, "
") 27 | return table 28 | end 29 | 30 | return m 31 | -------------------------------------------------------------------------------- /test/corpus/comments.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Can do simple comments 4 | ================== 5 | 6 | -- hello world 7 | 8 | --- 9 | 10 | (program (comment)) 11 | 12 | ================== 13 | Can do EOL comments 14 | ================== 15 | 16 | local x = 1 -- hello world 17 | 18 | --- 19 | 20 | (program 21 | (variable_declaration (local) (variable_declarator (identifier)) (number)) 22 | (comment)) 23 | 24 | ================== 25 | Can do simple comments 26 | ================== 27 | 28 | --[[ 29 | hello world 30 | more 31 | --]] 32 | 33 | --- 34 | 35 | (program (comment)) 36 | 37 | ================== 38 | Can do weirder comments 39 | ================== 40 | 41 | --[==[ 42 | hello world 43 | more 44 | --]==] 45 | 46 | --- 47 | 48 | (program (comment)) 49 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_lua_binding", 5 | "dependencies": [ 6 | " 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | TSLanguage *tree_sitter_lua(void); 6 | 7 | static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { 8 | return PyCapsule_New(tree_sitter_lua(), "tree_sitter.Language", NULL); 9 | } 10 | 11 | static PyMethodDef methods[] = { 12 | {"language", _binding_language, METH_NOARGS, 13 | "Get the tree-sitter language for this grammar."}, 14 | {NULL, NULL, 0, NULL} 15 | }; 16 | 17 | static struct PyModuleDef module = { 18 | .m_base = PyModuleDef_HEAD_INIT, 19 | .m_name = "_binding", 20 | .m_doc = NULL, 21 | .m_size = -1, 22 | .m_methods = methods 23 | }; 24 | 25 | PyMODINIT_FUNC PyInit__binding(void) { 26 | return PyModule_Create(&module); 27 | } 28 | -------------------------------------------------------------------------------- /scratch/wrapper.lua: -------------------------------------------------------------------------------- 1 | local wrap = function(str, limit, indent, indent1) 2 | indent = indent or "" 3 | indent1 = indent1 or indent 4 | limit = limit or 79 5 | local here = 1 - #indent1 6 | return indent1 7 | .. str:gsub("(%s+)()(%S+)()", function(sp, st, word, fi) 8 | local delta = 0 9 | word:gsub("@([@%a])", function(c) 10 | if c == "@" then 11 | delta = delta + 1 12 | elseif c == "x" then 13 | delta = delta + 5 14 | else 15 | delta = delta + 2 16 | end 17 | end) 18 | here = here + delta 19 | if fi - here > limit then 20 | here = st - #indent + delta 21 | return "\n" .. indent .. word 22 | end 23 | end) 24 | end 25 | 26 | print(wrap("hello world this is a longer string that I want to split", 20, "~~", " ")) 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "tree-sitter-lua" 7 | description = "Lua grammar for tree-sitter" 8 | version = "0.0.1" 9 | keywords = ["incremental", "parsing", "tree-sitter", "lua"] 10 | classifiers = [ 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: MIT License", 13 | "Topic :: Software Development :: Compilers", 14 | "Topic :: Text Processing :: Linguistic", 15 | "Typing :: Typed" 16 | ] 17 | requires-python = ">=3.9" 18 | license.text = "MIT" 19 | readme = "README.md" 20 | 21 | [project.urls] 22 | Homepage = "https://github.com/tree-sitter/tree-sitter-lua" 23 | 24 | [project.optional-dependencies] 25 | core = ["tree-sitter~=0.22"] 26 | 27 | [tool.cibuildwheel] 28 | build = "cp39-*" 29 | build-frontend = "build" 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ts := tree-sitter 2 | 3 | generate: 4 | ${ts} generate 5 | 6 | test: generate 7 | make test_ts 8 | make test_docgen 9 | 10 | test_ts: generate 11 | ${ts} test 12 | 13 | update: generate 14 | ${ts} test --update 15 | 16 | 17 | test_docgen: generate 18 | nvim \ 19 | --headless \ 20 | --noplugin \ 21 | -u tests/init.lua \ 22 | -c "PlenaryBustedDirectory lua/tests/ {minimal_init = 'tests/init.lua'}" 23 | 24 | build_parser: generate 25 | mkdir -p build 26 | cc -o ./build/parser.so -I./src src/parser.c src/scanner.c -shared -Os -fPIC 27 | mkdir -p parser 28 | cp ./build/parser.so ./parser/lua.so || exit 0 29 | 30 | gen_howto: 31 | nvim --headless --noplugin -u tests/init.lua -c "luafile ./scratch/gen_howto.lua" -c 'qa' 32 | 33 | lualint: 34 | luacheck lua/docgen 35 | 36 | dist: 37 | mkdir -p parser 38 | cc -o ./parser/lua.so -I./src src/parser.c src/scanner.c -shared -Os -fPIC 39 | 40 | wasm: build_parser 41 | ${ts} build-wasm 42 | 43 | web: wasm 44 | ${ts} web-ui 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-sitter-nlua", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "bindings/node", 6 | "types": "bindings/node", 7 | "scripts": { 8 | "install": "node-gyp-build", 9 | "prestart": "tree-sitter build --wasm", 10 | "start": "tree-sitter playground", 11 | "test": "node --test bindings/node/*_test.js" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "tree-sitter": "^0.20.0", 17 | "node-addon-api": "^8.0.0", 18 | "node-gyp-build": "^4.8.1" 19 | }, 20 | "peerDependencies": { 21 | "tree-sitter": "^0.21.1" 22 | }, 23 | "peerDependenciesMeta": { 24 | "tree_sitter": { 25 | "optional": true 26 | } 27 | }, 28 | "devDependencies": { 29 | "tree-sitter-cli": "^0.20.0", 30 | "prebuildify": "^6.0.1" 31 | }, 32 | "files": [ 33 | "grammar.js", 34 | "binding.gyp", 35 | "prebuilds/**", 36 | "bindings/node/*", 37 | "queries/*", 38 | "src/**", 39 | "*.wasm" 40 | ], 41 | "tree-sitter": [ 42 | { 43 | "scope": "source.lua", 44 | "injection-regex": "^lua$" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /example/brief.lua: -------------------------------------------------------------------------------- 1 | ---@brief [[ 2 | --- This will document a module and will be found at the top of each file. It uses an internal markdown renderer 3 | --- so you don't need to worry about formatting. It will wrap the lines into one paragraph and 4 | --- will make sure that the max line width is < 80. 5 | --- 6 | --- To start a new paragraph with a newline. 7 | --- 8 | --- To explicitly do a breakline do a `
` at the end.
9 | --- This is useful sometimes 10 | --- 11 | --- We also support itemize and enumerate 12 | --- - Item 1 13 | --- - Item 1.1 This item will be wrapped as well and the result will be as expected. This is really handy. 14 | --- - Item 1.1.1 15 | --- - Item 1.2 16 | --- - Item 2 17 | --- 18 | --- 1. Item 19 | --- 1.1. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna 20 | --- aliquyam erat, sed diam voluptua. 21 | --- 1.1.1. Item 22 | --- 1.2. Item 23 | --- 2. Item 24 | --- 25 | ---
26 | --- You can disable formatting with a
27 | --- pre block.
28 | --- This is useful if you want to draw a table or write some code
29 | --- 
30 | --- 31 | ---@brief ]] 32 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TreeSitterLua", 6 | products: [ 7 | .library(name: "TreeSitterLua", targets: ["TreeSitterLua"]), 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "TreeSitterLua", 15 | dependencies: [], 16 | path: ".", 17 | sources: [ 18 | "src/parser.c", 19 | // NOTE: if your language has an external scanner, add it here. 20 | ], 21 | resources: [ 22 | .copy("queries") 23 | ], 24 | publicHeadersPath: "bindings/swift", 25 | cSettings: [.headerSearchPath("src")] 26 | ), 27 | .testTarget( 28 | name: "TreeSitterLuaTests", 29 | dependencies: [ 30 | "SwiftTreeSitter", 31 | "TreeSitterLua", 32 | ], 33 | path: "bindings/swift/TreeSitterLuaTests" 34 | ) 35 | ], 36 | cLanguageStandard: .c11 37 | ) 38 | -------------------------------------------------------------------------------- /scratch/output.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | *docgen-test-module* 3 | 4 | Hello world. And then some more text And we can keep typing things here. and 5 | even more text that should be split in a way that definitely makes sense. 6 | 7 | M:even_cooler() *M:even_cooler()* 8 | --- Cooler function, with no params 9 | 10 | Return: ~ 11 | Docs generated at: Mon 30 Nov 2020 04:53:01 PM EST 12 | ---@return nil 13 | 14 | M.cool({longer_name}) *M.cool()* 15 | --- Cool function, not as cool as rocker tho 16 | 17 | Parameters: ~ 18 | {longer_name} (string) This is a string 19 | 20 | Return: ~ 21 | ---@return nil 22 | 23 | M.example({a}, {b}) *M.example()* 24 | --- Example function 25 | 26 | Parameters: ~ 27 | {a} (number) This is a number 28 | {b} (number) Also a number 29 | 30 | 31 | 32 | vim:tw=78:ts=8:ft=help:norl: 33 | -------------------------------------------------------------------------------- /src/tree_sitter/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ALLOC_H_ 2 | #define TREE_SITTER_ALLOC_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Allow clients to override allocation functions 13 | #ifdef TREE_SITTER_REUSE_ALLOCATOR 14 | 15 | extern void *(*ts_current_malloc)(size_t); 16 | extern void *(*ts_current_calloc)(size_t, size_t); 17 | extern void *(*ts_current_realloc)(void *, size_t); 18 | extern void (*ts_current_free)(void *); 19 | 20 | #ifndef ts_malloc 21 | #define ts_malloc ts_current_malloc 22 | #endif 23 | #ifndef ts_calloc 24 | #define ts_calloc ts_current_calloc 25 | #endif 26 | #ifndef ts_realloc 27 | #define ts_realloc ts_current_realloc 28 | #endif 29 | #ifndef ts_free 30 | #define ts_free ts_current_free 31 | #endif 32 | 33 | #else 34 | 35 | #ifndef ts_malloc 36 | #define ts_malloc malloc 37 | #endif 38 | #ifndef ts_calloc 39 | #define ts_calloc calloc 40 | #endif 41 | #ifndef ts_realloc 42 | #define ts_realloc realloc 43 | #endif 44 | #ifndef ts_free 45 | #define ts_free free 46 | #endif 47 | 48 | #endif 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif // TREE_SITTER_ALLOC_H_ 55 | -------------------------------------------------------------------------------- /query/lua/locals.scm: -------------------------------------------------------------------------------- 1 | ;;; DECLARATIONS AND SCOPES 2 | 3 | ;; Variable and field declarations 4 | ((variable_declarator 5 | (identifier) @definition.var)) 6 | 7 | ; ((variable_declarator 8 | ; (field_expression object:(*) @definition.associated (property_identifier) @definition.var))) 9 | 10 | ; ;; Parameters 11 | ; (parameter_list (identifier) @definition.parameter) 12 | 13 | ; ;; Loops 14 | ; ((for_statement 15 | ; (identifier) @definition.var)) 16 | 17 | ; ;; Function definitions 18 | ; ;; Functions definitions creates both a definition and a new scope 19 | ; ((function 20 | ; (function_name 21 | ; (function_name_field 22 | ; (identifier) @definition.associated 23 | ; (property_identifier) @definition.method))) @scope) 24 | 25 | ; ((function 26 | ; (function_name (identifier) @definition.function)) @scope) 27 | 28 | ; ((local_function 29 | ; (identifier) @definition.function) @scope) 30 | ; (function_definition) @scope 31 | 32 | ; (program) @scope 33 | ; ((if_statement) @scope) 34 | ; ((for_in_statement) @scope) 35 | ; ((repeat_statement) @scope) 36 | ; ((while_statement) @scope) 37 | 38 | ; ;;; REFERENCES 39 | ; ((identifier) @reference) 40 | ; ((property_identifier) @reference) 41 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge AS builder 2 | 3 | RUN apk add --no-cache git gcc g++ 4 | 5 | RUN mkdir -p ~/.local/share/nvim/site/pack/vendor/start 6 | RUN git clone --depth 1 https://github.com/nvim-lua/plenary.nvim /plenary.nvim 7 | RUN git clone --depth 1 https://github.com/tjdevries/tree-sitter-lua /tree-sitter-lua 8 | 9 | WORKDIR /tree-sitter-lua 10 | RUN mkdir -p parser 11 | RUN cc -o ./parser/lua.so -I./src src/parser.c src/scanner.c -shared -Os -lstdc++ -fPIC 12 | 13 | FROM alpine:edge AS app 14 | 15 | # I don't thin we work w/ 0.5.1 anymore 16 | # RUN apk add --no-cache neovim 17 | 18 | RUN apk add git build-base cmake automake autoconf libtool pkgconf coreutils curl unzip gettext-tiny-dev 19 | RUN git clone --depth 1 https://github.com/neovim/neovim /neovim 20 | WORKDIR /neovim 21 | RUN make -j4 22 | RUN make install 23 | 24 | 25 | # Copy all needed libraries from the builder 26 | WORKDIR /usr/lib 27 | COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/libstdc++.so.6 ./ 28 | 29 | WORKDIR /usr/share/nvim/runtime/pack 30 | RUN mkdir -p vendor/start 31 | WORKDIR /usr/share/nvim/runtime/pack/vendor/start 32 | COPY --from=builder /plenary.nvim plenary.nvim 33 | COPY --from=builder /tree-sitter-lua tree-sitter-lua 34 | RUN ln -s /tmp app 35 | WORKDIR /tmp 36 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_lua/__init__.py: -------------------------------------------------------------------------------- 1 | """Lua grammar for tree-sitter""" 2 | 3 | from importlib.resources import files as _files 4 | 5 | from ._binding import language 6 | 7 | 8 | def _get_query(name, file): 9 | query = _files(f"{__package__}.queries") / file 10 | globals()[name] = query.read_text() 11 | return globals()[name] 12 | 13 | 14 | def __getattr__(name): 15 | # NOTE: uncomment these to include any queries that this grammar contains: 16 | 17 | # if name == "HIGHLIGHTS_QUERY": 18 | # return _get_query("HIGHLIGHTS_QUERY", "highlights.scm") 19 | # if name == "INJECTIONS_QUERY": 20 | # return _get_query("INJECTIONS_QUERY", "injections.scm") 21 | # if name == "LOCALS_QUERY": 22 | # return _get_query("LOCALS_QUERY", "locals.scm") 23 | # if name == "TAGS_QUERY": 24 | # return _get_query("TAGS_QUERY", "tags.scm") 25 | 26 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 27 | 28 | 29 | __all__ = [ 30 | "language", 31 | # "HIGHLIGHTS_QUERY", 32 | # "INJECTIONS_QUERY", 33 | # "LOCALS_QUERY", 34 | # "TAGS_QUERY", 35 | ] 36 | 37 | 38 | def __dir__(): 39 | return sorted(__all__ + [ 40 | "__all__", "__builtins__", "__cached__", "__doc__", "__file__", 41 | "__loader__", "__name__", "__package__", "__path__", "__spec__", 42 | ]) 43 | -------------------------------------------------------------------------------- /scratch/module_example.lua: -------------------------------------------------------------------------------- 1 | -- TODO: Need to make a way to write a header section 2 | -- TODO: Need a way to document non-function values 3 | -- TODO: Need to parse return { x = y, z = foo }, etc. and transform 4 | -- TODO: Also need to add boilerplate stuff like modeline, etc. 5 | 6 | ---@brief [[ 7 | --- Hello world. 8 | --- And then some more text 9 | --- And we can keep typing things here. 10 | --- and even more text that should be split in a way that definitely makes sense. 11 | ---@brief ]] 12 | 13 | ---@tag docgen-test-module 14 | 15 | local M = {} 16 | 17 | --- Example function 18 | ---@param a number: This is a number 19 | ---@param b number: Also a number 20 | M.example = function(a, b) 21 | return a + b 22 | end 23 | 24 | --- Cool function, not as cool as rocker tho 25 | ---@param longer_name string: This is a string 26 | ---@return nil 27 | M.cool = function(longer_name, ...) 28 | print(longer_name, ...) 29 | end 30 | 31 | --- Cooler function, with no params 32 | ---@eval { ["return"] = 'Docs generated at: ' .. os.date() } 33 | ---@return nil 34 | function M:even_cooler() end 35 | 36 | M.not_documented = function() end 37 | 38 | -- TODO: Figure out how to exclude the not exported stuff. 39 | --local NotExported = {} 40 | 41 | local NotExported = {} 42 | 43 | --- Should not get exported 44 | ---@param wow string: Yup 45 | NotExported.not_exported = function(wow) 46 | print(wow) 47 | end 48 | 49 | return M 50 | -------------------------------------------------------------------------------- /queries/lua/textobjects.scm: -------------------------------------------------------------------------------- 1 | (function) @function.outer 2 | (function_statement) @function.outer 3 | 4 | ; TODO: Need to make this work, might want to change grammar 5 | ; to have entire body be one node, rather than fields. 6 | (function_body) @function.inner 7 | 8 | (for_statement) @loop.outer 9 | (while_statement) @loop.outer 10 | (repeat_statement) @loop.outer 11 | 12 | ; TODO: @conditional.inner 13 | (if_statement) @conditional.outer 14 | 15 | (function_call (function_arguments) @call.inner) 16 | (function_call) @call.outer 17 | 18 | (function_arguments (_) @parameter.inner) 19 | (parameter_list (_) @parameter.inner) 20 | 21 | (comment) @comment.outer 22 | 23 | (field) @element 24 | 25 | ;; TODO: It would be cool to figure out how to make variables good 26 | ; (variable_declaration) @variable 27 | 28 | ; ((function 29 | ; . (function_name) . (parameters) . (_) @_start 30 | ; (_) @_end .) 31 | ; (#make-range! "function.inner" @_start @_end)) 32 | ; ((local_function 33 | ; . (identifier) . (parameters) . (_) @_start 34 | ; (_) @_end .) 35 | ; (#make-range! "function.inner" @_start @_end)) 36 | ; ((function_definition 37 | ; . (parameters) . (_) @_start 38 | ; (_) @_end .) 39 | ; (#make-range! "function.inner" @_start @_end)) 40 | ; 41 | ; ((function 42 | ; . (function_name) . (parameters) . (_) @function.inner .)) 43 | ; ((local_function 44 | ; . (identifier) . (parameters) . (_) @function.inner .)) 45 | ; ((function_definition 46 | ; . (parameters) . (_) @function.inner .)) 47 | -------------------------------------------------------------------------------- /bindings/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src_dir = std::path::Path::new("src"); 3 | 4 | let mut c_config = cc::Build::new(); 5 | c_config.include(&src_dir); 6 | c_config 7 | .flag_if_supported("-Wno-unused-parameter") 8 | .flag_if_supported("-Wno-unused-but-set-variable") 9 | .flag_if_supported("-Wno-trigraphs"); 10 | #[cfg(target_env = "msvc")] 11 | c_config.flag("-utf-8"); 12 | 13 | let parser_path = src_dir.join("parser.c"); 14 | c_config.file(&parser_path); 15 | 16 | // If your language uses an external scanner written in C, 17 | // then include this block of code: 18 | 19 | /* 20 | let scanner_path = src_dir.join("scanner.c"); 21 | c_config.file(&scanner_path); 22 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 23 | */ 24 | 25 | c_config.compile("parser"); 26 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); 27 | 28 | // If your language uses an external scanner written in C++, 29 | // then include this block of code: 30 | 31 | /* 32 | let mut cpp_config = cc::Build::new(); 33 | cpp_config.cpp(true); 34 | cpp_config.include(&src_dir); 35 | cpp_config 36 | .flag_if_supported("-Wno-unused-parameter") 37 | .flag_if_supported("-Wno-unused-but-set-variable"); 38 | let scanner_path = src_dir.join("scanner.cc"); 39 | cpp_config.file(&scanner_path); 40 | cpp_config.compile("scanner"); 41 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 42 | */ 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tree-sitter: 7 | name: tree-sitter 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/install@v0.1 12 | with: 13 | crate: tree-sitter-cli 14 | version: latest 15 | - name: Generate grammar 16 | run: make generate 17 | - name: Run tests 18 | run: tree-sitter test 19 | 20 | docgen: 21 | name: X64-ubuntu 22 | runs-on: ubuntu-20.04 23 | steps: 24 | - uses: actions/checkout@v2 25 | - run: date +%F > todays-date 26 | - name: Restore cache for today's nightly. 27 | uses: actions/cache@v2 28 | with: 29 | path: _neovim 30 | key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} 31 | 32 | - name: Prepare 33 | run: | 34 | mkdir -p ~/.local/share/nvim/site/pack/vendor/start 35 | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim 36 | ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start 37 | - name: Build parser 38 | run: make dist 39 | - name: Run tests 40 | run: | 41 | curl -OL https://raw.githubusercontent.com/norcalli/bot-ci/master/scripts/github-actions-setup.sh 42 | source github-actions-setup.sh nightly-x64 43 | nvim --headless --noplugin \ 44 | -u tests/minimal_init.vim \ 45 | -c "PlenaryBustedDirectory lua/tests/ {minimal_init = 'tests/minimal_init.vim'}" 46 | -------------------------------------------------------------------------------- /scratch/lua_grammar_checklist.txt: -------------------------------------------------------------------------------- 1 | chunk ::= {stat [`;´]} [laststat [`;´]] 2 | 3 | block ::= chunk 4 | 5 | stat ::= 6 | [x] varlist `=´ explist | 7 | [x] functioncall | 8 | [x] do block end | 9 | [x] while exp do block end | 10 | [x] repeat block until exp | 11 | [x] if exp then block {elseif exp then block} [else block] end | 12 | [x] for identifier `=´ exp `,´ exp [`,´ exp] do block end | 13 | [x] for namelist in explist do block end | 14 | [x] function funcname funcbody | 15 | [x] local function identifier funcbody | 16 | [x] local namelist [`=´ explist] 17 | 18 | -- Done 19 | 20 | laststat ::= return [explist] | break 21 | 22 | var ::= 23 | var ::= prefixexp `.´ Name 24 | var ::= prefixexp `[´ exp `]´ 25 | 26 | args ::= 27 | functioncall ::= 28 | 29 | varlist ::= var {`,´ var} 30 | namelist ::= identifier {`,´ identifier} 31 | explist ::= {exp `,´} exp 32 | function ::= function funcbody 33 | funcbody ::= `(´ [parlist] `)´ block end 34 | parlist ::= namelist [`,´ `...´] | `...´ 35 | tableconstructor ::= `{´ [fieldlist] `}´ 36 | fieldlist ::= field {fieldsep field} [fieldsep] 37 | funcname ::= identifier {`.´ identifier} [`:´ identifier] 38 | exp ::= nil | false | true | Number | String | `...´ | function | 39 | prefixexp | tableconstructor | exp binop exp | unop exp 40 | prefixexp ::= var | functioncall | `(´ exp `)´ 41 | field ::= `[´ exp `]´ `=´ exp | identifier `=´ exp | exp 42 | fieldsep ::= `,´ | `;´ 43 | binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 44 | `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 45 | and | or 46 | 47 | unop ::= `-´ | not | `#´ 48 | -------------------------------------------------------------------------------- /test/corpus/strings.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Basic string 4 | ================== 5 | 6 | local x = "hello world" 7 | 8 | --- 9 | 10 | (program 11 | (variable_declaration 12 | (local) 13 | (variable_declarator (identifier)) 14 | (string))) 15 | 16 | ================== 17 | Basic string 18 | ================== 19 | 20 | local x = 'hello world' 21 | 22 | --- 23 | 24 | (program 25 | (variable_declaration 26 | (local) 27 | (variable_declarator (identifier)) 28 | (string))) 29 | 30 | ================== 31 | Contained string 32 | ================== 33 | 34 | local x = "foo 'bar baz" 35 | 36 | --- 37 | 38 | (program 39 | (variable_declaration 40 | (local) 41 | (variable_declarator (identifier)) 42 | (string))) 43 | 44 | ================== 45 | Bracket string 46 | ================== 47 | 48 | local x = [[ my string ]] 49 | 50 | --- 51 | 52 | (program 53 | (variable_declaration 54 | (local) 55 | (variable_declarator (identifier)) 56 | (string))) 57 | 58 | ================== 59 | Bracket string, single escape 60 | ================== 61 | 62 | local x = [[ my ] string ]] 63 | 64 | --- 65 | 66 | (program 67 | (variable_declaration 68 | (local) 69 | (variable_declarator (identifier)) 70 | (string))) 71 | 72 | ================== 73 | Bracket string 1 74 | ================== 75 | 76 | local x = [=[ my ] string ]=] 77 | 78 | --- 79 | 80 | (program 81 | (variable_declaration 82 | (local) 83 | (variable_declarator (identifier)) 84 | (string))) 85 | 86 | ================== 87 | Bracket string 2 88 | ================== 89 | 90 | local x = [==[ my ]=] string ]==] 91 | 92 | --- 93 | 94 | (program 95 | (variable_declaration 96 | (local) 97 | (variable_declarator (identifier)) 98 | (string))) 99 | -------------------------------------------------------------------------------- /lua/nlsp/init.lua: -------------------------------------------------------------------------------- 1 | local log = require "nlsp.log" 2 | local rpc = require "nlsp.rpc" 3 | 4 | local M = {} 5 | 6 | -- io.stderr:setvbuf("no") 7 | Shutdown = Shutdown or false 8 | 9 | local method_handlers = {} 10 | 11 | M.start = function() 12 | local ok, msg = pcall(function() 13 | log.info "We started" 14 | 15 | while not Shutdown do 16 | -- header 17 | local err, data = rpc.read_message() 18 | log.info("Message is:", err, data) 19 | 20 | -- if _G.Config.debugMode then 21 | -- reload_all() 22 | -- end 23 | 24 | if data == nil then 25 | if err == "eof" then 26 | return os.exit(1) 27 | end 28 | error(err) 29 | elseif data.method then 30 | -- request 31 | if not method_handlers[data.method] then 32 | log.info("confused by %t", data) 33 | err = string.format("%q: Not found/NYI", tostring(data.method)) 34 | if data.id then 35 | rpc.respondError(data.id, err, "MethodNotFound") 36 | else 37 | log.warning("%s", err) 38 | end 39 | else 40 | local ok 41 | ok, err = xpcall(function() 42 | method_handlers[data.method](data.params, data.id) 43 | end, debug.traceback) 44 | if not ok then 45 | if data.id then 46 | rpc.respondError(data.id, err, "InternalError") 47 | else 48 | log.warning("%s", tostring(err)) 49 | end 50 | end 51 | end 52 | elseif data.result then 53 | rpc.finish(data) 54 | elseif data.error then 55 | log("client error:%s", data.error.message) 56 | end 57 | end 58 | 59 | os.exit(0) 60 | end) 61 | 62 | if not ok then 63 | log.info("ERROR:", msg) 64 | end 65 | end 66 | 67 | return M 68 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import isdir, join 2 | from platform import system 3 | 4 | from setuptools import Extension, find_packages, setup 5 | from setuptools.command.build import build 6 | from wheel.bdist_wheel import bdist_wheel 7 | 8 | 9 | class Build(build): 10 | def run(self): 11 | if isdir("queries"): 12 | dest = join(self.build_lib, "tree_sitter_lua", "queries") 13 | self.copy_tree("queries", dest) 14 | super().run() 15 | 16 | 17 | class BdistWheel(bdist_wheel): 18 | def get_tag(self): 19 | python, abi, platform = super().get_tag() 20 | if python.startswith("cp"): 21 | python, abi = "cp39", "abi3" 22 | return python, abi, platform 23 | 24 | 25 | setup( 26 | packages=find_packages("bindings/python"), 27 | package_dir={"": "bindings/python"}, 28 | package_data={ 29 | "tree_sitter_lua": ["*.pyi", "py.typed"], 30 | "tree_sitter_lua.queries": ["*.scm"], 31 | }, 32 | ext_package="tree_sitter_lua", 33 | ext_modules=[ 34 | Extension( 35 | name="_binding", 36 | sources=[ 37 | "bindings/python/tree_sitter_lua/binding.c", 38 | "src/parser.c", 39 | # NOTE: if your language uses an external scanner, add it here. 40 | ], 41 | extra_compile_args=[ 42 | "-std=c11", 43 | "-fvisibility=hidden", 44 | ] if system() != "Windows" else [ 45 | "/std:c11", 46 | "/utf-8", 47 | ], 48 | define_macros=[ 49 | ("Py_LIMITED_API", "0x03090000"), 50 | ("PY_SSIZE_T_CLEAN", None), 51 | ("TREE_SITTER_HIDE_SYMBOLS", None), 52 | ], 53 | include_dirs=["src"], 54 | py_limited_api=True, 55 | ) 56 | ], 57 | cmdclass={ 58 | "build": Build, 59 | "bdist_wheel": BdistWheel 60 | }, 61 | zip_safe=False 62 | ) 63 | -------------------------------------------------------------------------------- /bindings/rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides Lua language support for the [tree-sitter][] parsing library. 2 | //! 3 | //! Typically, you will use the [LANGUAGE][] constant to add this language to a 4 | //! tree-sitter [Parser][], and then use the parser to parse some code: 5 | //! 6 | //! ``` 7 | //! let code = r#" 8 | //! "#; 9 | //! let mut parser = tree_sitter::Parser::new(); 10 | //! let language = tree_sitter_lua::LANGUAGE; 11 | //! parser 12 | //! .set_language(&language.into()) 13 | //! .expect("Error loading Lua parser"); 14 | //! let tree = parser.parse(code, None).unwrap(); 15 | //! assert!(!tree.root_node().has_error()); 16 | //! ``` 17 | //! 18 | //! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html 19 | //! [tree-sitter]: https://tree-sitter.github.io/ 20 | 21 | use tree_sitter_language::LanguageFn; 22 | 23 | extern "C" { 24 | fn tree_sitter_lua() -> *const (); 25 | } 26 | 27 | /// The tree-sitter [`LanguageFn`][LanguageFn] for this grammar. 28 | /// 29 | /// [LanguageFn]: https://docs.rs/tree-sitter-language/*/tree_sitter_language/struct.LanguageFn.html 30 | pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_lua) }; 31 | 32 | /// The content of the [`node-types.json`][] file for this grammar. 33 | /// 34 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types 35 | pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); 36 | 37 | // NOTE: uncomment these to include any queries that this grammar contains: 38 | 39 | // pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); 40 | // pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); 41 | // pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); 42 | // pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | #[test] 47 | fn test_can_load_grammar() { 48 | let mut parser = tree_sitter::Parser::new(); 49 | parser 50 | .set_language(&super::LANGUAGE.into()) 51 | .expect("Error loading Lua parser"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /todo.scm: -------------------------------------------------------------------------------- 1 | ;;; Local documentation 2 | ; --- A function description 3 | ; --@param p: param value 4 | ; --@param x: another value 5 | ; --@returns true 6 | ; local function cool_function(p, x) 7 | ; return true 8 | ; end 9 | (program 10 | (local_function 11 | (emmy_documentation 12 | (parameter_documentation 13 | name: (identifier) 14 | description: (parameter_description)) 15 | (parameter_documentation 16 | name: (identifier) 17 | description: (parameter_description)) 18 | 19 | (return_description)) 20 | 21 | (identifier) 22 | (parameters 23 | (identifier) 24 | (identifier)) 25 | 26 | (return_statement (true)))) 27 | ;;; Full documentation with assignment 28 | ; local x = {} 29 | ; 30 | ; --- hello world 31 | ; --@param y: add 1 32 | ; x.my_func = function(y) 33 | ; return y + 1 34 | ; end 35 | (program 36 | (local_variable_declaration 37 | variable: (variable_declarator (identifier)) (table)) 38 | 39 | (variable_declaration 40 | documentation: (emmy_documentation 41 | (parameter_documentation 42 | name: (identifier) 43 | description: (parameter_description))) 44 | 45 | variable: (variable_declarator 46 | (field_expression (identifier) (property_identifier))) 47 | 48 | expression: (function_definition 49 | (parameters (identifier)) 50 | (return_statement (binary_operation (identifier) (number)))) 51 | 52 | )) 53 | 54 | ;;; Full documentation with assignment bracket 55 | ; local x = {} 56 | ; 57 | ; --- hello world 58 | ; --@param y: add 1 59 | ; x["my_func"] = function(y) 60 | ; return y + 1 61 | ; end 62 | (program 63 | (variable_declaration 64 | (local) 65 | variable: (variable_declarator (identifier)) (table)) 66 | 67 | (variable_declaration 68 | documentation: (emmy_documentation 69 | (parameter_documentation 70 | name: (identifier) 71 | description: (parameter_description))) 72 | 73 | variable: (variable_declarator (identifier) (string)) 74 | 75 | expression: (function_definition 76 | (parameters (identifier)) 77 | (return_statement (binary_operation (identifier) (number)))) 78 | )) 79 | -------------------------------------------------------------------------------- /lua/nlsp/ts/init.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | local parsers = require "nvim-treesitter.parsers" 4 | local locals = require "nvim-treesitter.locals" 5 | 6 | local state = require "nlsp.state" 7 | local nquery = require "nlsp.ts.query" 8 | 9 | local M = {} 10 | 11 | --[[ 12 | interface Location { 13 | uri: DocumentUri; 14 | range: Range; 15 | } 16 | --]] 17 | function M.get_node_at_position(uri, position) 18 | local start_row = position.line 19 | local start_col = position.character 20 | 21 | local root = M.get_root(uri) 22 | return root:named_descendant_for_range(start_row, start_col, start_row, start_col) 23 | end 24 | 25 | function M.get_root(uri) 26 | local parser = assert(state.get_ts_parser(uri), "File must be open before getting position") 27 | 28 | if not parsers.has_parser "lua" then 29 | return 30 | end 31 | return parser:parse():root() 32 | end 33 | 34 | function M.get_definition_at_position(uri, position) 35 | local text = state.get_text_document_item(uri).text 36 | local root = M.get_root(uri) 37 | local query = nquery.get "locals" 38 | 39 | local start_row, _, end_row, _ = root:range() 40 | 41 | for k, v in query:iter_captures(root, text, start_row, end_row + 1) do 42 | print(k, v, vim.treesitter.get_node_text(v, text)) 43 | end 44 | 45 | -- vim.treesitter.parse_query({lang}, {query}) 46 | -- Parse {query} as a string. (If the query is in a file, the caller 47 | -- should read the contents into a string before calling). 48 | 49 | -- query:iter_captures({node}, {bufnr}, {start_row}, {end_row}) 50 | -- local definition_node = locals.find_definition(position_node, text) 51 | 52 | -- print(vim.inspect(definition_node), definition_node) 53 | -- print(definition_node:start()) 54 | -- print(definition_node:end_()) 55 | end 56 | 57 | function M.test() 58 | local location = vim.lsp.util.make_position_params().position 59 | 60 | state.textDocumentItem.open { 61 | uri = vim.uri_from_bufnr(0), 62 | text = table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), "\n"), 63 | } 64 | M.get_definition_at_position(vim.uri_from_bufnr(0), location) 65 | end 66 | 67 | --[[ 68 | lua RELOAD('nlsp'); require('nlsp.ts').test() 69 | --]] 70 | 71 | return M 72 | -------------------------------------------------------------------------------- /scratch/docgen_output.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | *docgen* 3 | 4 | Public API for all associated docgen procedures. 5 | 6 | docgen.foreach_node({contents}, {cb}, {query_name}) *docgen.foreach_node()* 7 | Run {cb} on each node from contents and query 8 | 9 | Parameters: ~ 10 | {contents} (string) Contents to pass to string parser 11 | {cb} (function) Function to call on captures with (id, node) 12 | {query_name} (string) Name of the query to search for 13 | 14 | 15 | docgen.get_ts_query({query_name}) *docgen.get_ts_query()* 16 | Get the query for a tree sitter query, loaded from query directory. 17 | 18 | Parameters: ~ 19 | {query_name} (string) The name of the query file (without .scm) 20 | 21 | 22 | docgen.get_parser() *docgen.get_parser()* 23 | Get the string parser for some contents 24 | 25 | 26 | 27 | ================================================================================ 28 | *docgen-help-formatter* 29 | 30 | All help formatting related utilties. Used to transform output from |docgen| 31 | into vim style documentation. Other documentation styles are possible, but have 32 | not yet been implemented. 33 | 34 | help.format({metadata}) *help.format()* 35 | Format an entire generated metadata from |docgen| 36 | 37 | Parameters: ~ 38 | {metadata} (table) The metadata from docgen 39 | 40 | 41 | 42 | ================================================================================ 43 | *docgen-transformers* 44 | 45 | Transforms generated tree from tree sitter -> metadata nodes that we can use 46 | for the project. Structure of a program is: (TODO) 47 | 48 | call_transformer() *call_transformer()* 49 | Takes any node and recursively transforms its children into the 50 | corresponding metadata required by |docgen|. 51 | 52 | 53 | 54 | vim:tw=78:ts=8:ft=help:norl: -------------------------------------------------------------------------------- /lua/nlsp/external/luacheck.lua: -------------------------------------------------------------------------------- 1 | -- alloyed/lua-lsp 2 | -- ty ty 3 | 4 | local popen_cmd = "sh -c 'cd %q; luacheck %q --filename %q --formatter plain --ranges --codes'" 5 | local message_match = "^([^:]+):(%d+):(%d+)%-(%d+): %(W(%d+)%) (.+)" 6 | local function try_luacheck(document) 7 | local diagnostics = {} 8 | local opts = {} 9 | if luacheck then 10 | local reports 11 | if Config._useNativeLuacheck == false then 12 | local tmp_path = "/tmp/check.lua" 13 | local tmp = assert(io.open(tmp_path, "w")) 14 | tmp:write(document.text) 15 | tmp:close() 16 | 17 | local _, ce = document.uri:find(Config.root, 1, true) 18 | local fname = document.uri:sub((ce or -1) + 2, -1):gsub("file://", "") 19 | local root = Config.root:gsub("file://", "") 20 | local issues = io.popen(popen_cmd:format(root, tmp_path, fname)) 21 | reports = { {} } 22 | for line in issues:lines() do 23 | local _, l, scol, ecol, code, msg = line:match(message_match) 24 | assert(tonumber(l), line) 25 | assert(tonumber(scol), line) 26 | assert(tonumber(ecol), line) 27 | table.insert(reports[1], { 28 | code = code, 29 | line = tonumber(l), 30 | column = tonumber(scol), 31 | end_column = tonumber(ecol), 32 | message = msg, 33 | }) 34 | end 35 | issues:close() 36 | else 37 | reports = luacheck.check_strings({ document.text }, { opts }) 38 | end 39 | 40 | for _, issue in ipairs(reports[1]) do 41 | -- FIXME: translate columns to characters 42 | table.insert(diagnostics, { 43 | code = issue.code, 44 | range = { 45 | start = { 46 | line = issue.line - 1, 47 | character = issue.column - 1, 48 | }, 49 | ["end"] = { 50 | line = issue.line - 1, 51 | character = issue.end_column, 52 | }, 53 | }, 54 | -- 1 == error, 2 == warning 55 | severity = issue.code:find "^0" and 1 or 2, 56 | source = "luacheck", 57 | message = issue.message or luacheck.get_message(issue), 58 | }) 59 | end 60 | end 61 | rpc.notify("textDocument/publishDiagnostics", { 62 | uri = document.uri, 63 | diagnostics = diagnostics, 64 | }) 65 | end 66 | -------------------------------------------------------------------------------- /lua/nlsp/state.lua: -------------------------------------------------------------------------------- 1 | local treesitter = vim.treesitter 2 | 3 | local storage = {} 4 | 5 | local state = {} 6 | 7 | state._clear = function() 8 | storage = {} 9 | 10 | --- Map 11 | storage.textDocumentItems = {} 12 | 13 | --- Map 14 | storage.parsers = {} 15 | end 16 | 17 | state._clear() 18 | 19 | --[[ TextDocumentItem 20 | interface TextDocumentItem { 21 | -- The text document's URI. 22 | uri: DocumentUri; 23 | 24 | -- The text document's language identifier. 25 | languageId: string; 26 | 27 | -- The version number of this document 28 | -- (it will increase after each change, including undo/redo). 29 | version: number; 30 | 31 | -- The content of the opened text document. 32 | text: string; 33 | } 34 | 35 | interface TextDocumentIdentifier { 36 | uri: DocumentUri; 37 | } 38 | 39 | interface VersionTextDocumentIdentifier extends TextDocumentIdentifier { 40 | version: number | null; 41 | } 42 | --]] 43 | 44 | state.textDocumentItem = {} 45 | 46 | state.textDocumentItem.open = function(textDocumentItem) 47 | assert(not storage.textDocumentItems[textDocumentItem.uri], "Should not have received an open for this before") 48 | storage.textDocumentItems[textDocumentItem.uri] = textDocumentItem 49 | 50 | -- TODO: Should probably do something with this... 51 | storage.parsers[textDocumentItem.uri] = treesitter.get_string_parser(textDocumentItem.text, "lua") 52 | end 53 | 54 | state.textDocument = {} 55 | 56 | state.textDocument.change = function(textDocument, contentChanges) 57 | assert(storage.textDocumentItems[textDocument.uri], "Should have already loaded this textDocument") 58 | 59 | assert(contentChanges, "Should have some changes to apply") 60 | assert(#contentChanges == 1, "Can only handle one change") 61 | 62 | local changes = contentChanges[1] 63 | assert(not changes.range) 64 | assert(not changes.rangeLength) 65 | 66 | state.textDocument.save(textDocument, changes.text) 67 | 68 | -- TODO: Should probably do something with this... 69 | end 70 | 71 | state.textDocument.save = function(textDocument, text) 72 | assert(storage.textDocumentItems[textDocument.uri], "Should have already loaded this textDocument") 73 | storage.textDocumentItems[textDocument.uri].text = text 74 | end 75 | 76 | state.textDocument.close = function(textDocument) 77 | assert(storage.textDocumentItems[textDocument.uri], "Should have already loaded this textDocument") 78 | storage.textDocumentItems[textDocument.uri] = nil 79 | end 80 | 81 | function state.get_text_document_item(uri) 82 | return storage.textDocumentItems[uri] 83 | end 84 | 85 | function state.get_ts_parser(uri) 86 | return storage.parsers[uri] 87 | end 88 | 89 | return state 90 | -------------------------------------------------------------------------------- /lua/tests/docgen/tag_spec.lua: -------------------------------------------------------------------------------- 1 | local docgen = require "docgen" 2 | local docgen_help = require "docgen.help" 3 | 4 | local eq = assert.are.same 5 | 6 | local dedent = require("plenary.strings").dedent 7 | 8 | local get_dedented_nodes = function(source) 9 | return docgen.get_nodes(dedent(source)) 10 | end 11 | 12 | describe("tag", function() 13 | local check_tag_string = function(input, tag_string) 14 | local nodes = get_dedented_nodes(input) 15 | 16 | eq(tag_string, nodes.tag) 17 | end 18 | 19 | it("should generate tag", function() 20 | check_tag_string( 21 | [=[ 22 | ---@tag hello 23 | ]=], 24 | "hello" 25 | ) 26 | end) 27 | 28 | it("should generate multiple tags", function() 29 | check_tag_string( 30 | [=[ 31 | ---@tag hello world 32 | ]=], 33 | "hello world" 34 | ) 35 | end) 36 | 37 | describe("help output", function() 38 | local function check_tag_output(input, output) 39 | local nodes = require("docgen").get_nodes(input) 40 | local result = docgen_help.format(nodes) 41 | result = result:gsub("================================================================================\n", "") 42 | 43 | eq(vim.trim(output), vim.trim(result)) 44 | end 45 | 46 | it("should add tag", function() 47 | check_tag_output( 48 | [=[ 49 | ---@tag hello 50 | ]=], 51 | [[HELLO *hello*]] 52 | ) 53 | end) 54 | 55 | it("should add multiple tags", function() 56 | check_tag_output( 57 | [=[ 58 | ---@tag hello world 59 | ]=], 60 | [[HELLO *hello* *world*]] 61 | ) 62 | end) 63 | 64 | it("should not depend on the amount of whitespace in annotation", function() 65 | check_tag_output( 66 | [=[ 67 | ---@tag hello world people 68 | ]=], 69 | [[HELLO *hello* *world* *people*]] 70 | ) 71 | end) 72 | 73 | it("works with module names as tags", function() 74 | check_tag_output( 75 | [=[ 76 | ---@tag telescope.command 77 | ]=], 78 | [[COMMAND *telescope.command*]] 79 | ) 80 | end) 81 | 82 | it("works with module names as tags and different name in config", function() 83 | check_tag_output( 84 | [=[ 85 | ---@tag telescope.nvim 86 | ---@config { ["name"] = "INTRODUCTION" } 87 | ]=], 88 | [[INTRODUCTION *telescope.nvim*]] 89 | ) 90 | end) 91 | end) 92 | end) 93 | -------------------------------------------------------------------------------- /lua/nlsp/methods.lua: -------------------------------------------------------------------------------- 1 | local log = require "nlsp.log" 2 | local rpc = require "nlsp.rpc" 3 | local state = require "nlsp.state" 4 | 5 | -- TODO: This shouldn't be called from here directly, it should be a layer that we call. 6 | local ts = require "nlsp.ts" 7 | 8 | local protocol = vim.lsp.protocol 9 | 10 | local Config = {} 11 | 12 | local methods = {} 13 | 14 | function methods.initialize(params, id) 15 | if Initialized then 16 | error "already initialized!" 17 | end 18 | 19 | Config.root = params.rootPath or params.rootUri 20 | 21 | log.info("Config.root = %q", Config.root) 22 | 23 | -- analyze.load_completerc(Config.root) 24 | -- analyze.load_luacheckrc(Config.root) 25 | 26 | --ClientCapabilities = params.capabilities 27 | Initialized = true 28 | 29 | -- hopefully this is modest enough 30 | return rpc.respond(id, nil, { 31 | capabilities = { 32 | -- completionProvider = { 33 | -- triggerCharacters = {".",":"}, 34 | -- resolveProvider = false 35 | -- }, 36 | definitionProvider = true, 37 | textDocumentSync = { 38 | openClose = true, 39 | 40 | -- Always send everything 41 | change = protocol.TextDocumentSyncKind.Full, 42 | 43 | -- Please send the whole text when you save 44 | -- because I'm too noob to do incremental stuff at the moment. 45 | save = { includeText = true }, 46 | }, 47 | hoverProvider = false, 48 | documentSymbolProvider = false, 49 | --referencesProvider = false, 50 | --documentHighlightProvider = false, 51 | --workspaceSymbolProvider = false, 52 | --codeActionProvider = false, 53 | --documentFormattingProvider = false, 54 | --documentRangeFormattingProvider = false, 55 | --renameProvider = false, 56 | }, 57 | }) 58 | end 59 | 60 | -- interface DidOpenTextDocumentParams { 61 | -- -- The document that was opened. 62 | -- textDocument: TextDocumentItem; 63 | -- } 64 | methods["textDocument/didOpen"] = function(params) 65 | state.textDocumentItem.open(params.textDocument) 66 | end 67 | 68 | methods["textDocument/didChange"] = function(params) 69 | state.textDocument.change(params.textDocument, params.contentChanges) 70 | end 71 | 72 | -- interface DidSaveTextDocumentParams { 73 | -- -- The document that was saved. 74 | -- textDocument: TextDocumentIdentifier; 75 | -- 76 | -- -- Optional the content when saved. 77 | -- -- Depends on the includeText value when the save notification was requested. 78 | -- text?: string; 79 | -- } 80 | methods["textDocument/didSave"] = function(params) 81 | state.textDocument.save(params.textDocument, params.text) 82 | end 83 | 84 | methods["textDocument/didClose"] = function(params) 85 | state.textDocument.close(params.textDocument) 86 | end 87 | 88 | -- interface TextDocumentPositionParams { 89 | -- textDocument: TextDocumentIdentifier 90 | -- position: Position 91 | -- } 92 | methods["textDocument/definition"] = function(params) 93 | local definition = ts.get_definiton_at_position(params.position, vim.uri_to_bufnr(params.textDocument.uri)) 94 | 95 | -- Send result 96 | rpc.respond(nil, { 97 | position = ts.node_to_position(definiton), 98 | }) 99 | end 100 | 101 | return methods 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tree-sitter-(n)lua 2 | 3 | TODO: Document how you can use `make build_parser` to automatically install 4 | this for lua. 5 | 6 | Tree sitter grammar for Lua built to be used inside of Neovim. 7 | 8 | Goal: Make a super great Lua grammar and tailor it to usage for Neovim. 9 | 10 | ## Docgen 11 | 12 | This grammar has a docgen module. 13 | 14 | [How to write emmy documentation](./HOWTO.md) 15 | 16 | ### How to generate documentation 17 | 18 | Write a lua script that looks like this: 19 | 20 | ```lua 21 | local docgen = require('docgen') 22 | 23 | local docs = {} 24 | 25 | docs.test = function() 26 | -- Filepaths that should generate docs 27 | local input_files = { 28 | "./lua/telescope/init.lua", 29 | "./lua/telescope/builtin/init.lua", 30 | "./lua/telescope/pickers/layout_strategies.lua", 31 | "./lua/telescope/actions/init.lua", 32 | "./lua/telescope/previewers/init.lua", 33 | } 34 | 35 | -- Maybe sort them that depends what you want and need 36 | table.sort(input_files, function(a, b) 37 | return #a < #b 38 | end) 39 | 40 | -- Output file 41 | local output_file = "./doc/telescope.txt" 42 | local output_file_handle = io.open(output_file, "w") 43 | 44 | for _, input_file in ipairs(input_files) do 45 | docgen.write(input_file, output_file_handle) 46 | end 47 | 48 | output_file_handle:write(" vim:tw=78:ts=8:ft=help:norl:\n") 49 | output_file_handle:close() 50 | vim.cmd [[checktime]] 51 | end 52 | 53 | docs.test() 54 | 55 | return docs 56 | ``` 57 | 58 | It is suggested to use a `minimal_init.vim`. Example: 59 | 60 | ```viml 61 | set rtp+=. 62 | set rtp+=../plenary.nvim/ 63 | set rtp+=../tree-sitter-lua/ 64 | 65 | runtime! plugin/plenary.vim 66 | ``` 67 | 68 | After that you can run docgen like this: 69 | 70 | ``` 71 | nvim --headless --noplugin -u scripts/minimal_init.vim -c "luafile ./scripts/gendocs.lua" -c 'qa' 72 | ``` 73 | 74 | ## Thoughts on LSP: 75 | 76 | OK, so here's what I'm thinking: 77 | 78 | 1. We have tree sitter for Lua (that we wrote) 79 | 2. Can use tree sitter + queries to get information about one file 80 | - This is like, what a variable is in the file, where it's defined, references, etc. 81 | 3. We can use "programming" to link up multiple files w/ our tree sitter info 82 | - Use package.path, package.searchers, etc. to find where requires lead to. 83 | 4. Now we have "project" level knowledge. 84 | 5. Can give smarts based on those things. 85 | 86 | ALSO! 87 | 88 | We can cheat :) :) :) 89 | 90 | Let's say we run our LSP in another neovim instance (just `nvim --headless -c 'some stuff'`) 91 | ... 92 | OK 93 | 94 | this means we can ask `vim` about itself (and other globals, and your package.path, etc.) 95 | 96 | 97 | Part 2 of cheating: 98 | 99 | we can re-use vim.lsp in our implementation 100 | 101 | ## Status 102 | 103 | - [ ] Grammar 104 | - [ ] 105 | 106 | 107 | 108 | ## How did I get this to be da one for nvim-treesitter 109 | 110 | 1. It helps if you made @vigoux the person he is today. 111 | - (Not a firm requirement, but it's very helpful) 112 | 2. Make a grammar.js and then be able to generate a parser.so 113 | 3. Write a bunch of tests for the language and provide somethjing the other parser doesnt. 114 | - Mine is mostly that it parses docs 115 | - It also parses more specifically many aspects of the language for smarter highlighting (hopefully) 116 | - I also like the names more 117 | 4. To test with nvim-treesitter, you need to make a `lua.so` (or whatever your filetype is) available somewhere in rtp in a folder called `parser`. 118 | - Then you need to write the `.scm` files, like highlight and all that good stuff. 119 | 5. ??? 120 | 1. Profit! 121 | -------------------------------------------------------------------------------- /scratch/known_broken.lua: -------------------------------------------------------------------------------- 1 | -- Fixed 2 | local x = vim.x() or vim.y() 3 | local another = x 4 | print(another) 5 | 6 | -- Fixed 7 | ---@brief [[ 8 | --- and still this is working 9 | --- "a string" and this works 10 | --- and another thing 11 | ---@brief ]] 12 | 13 | local cache = require "cmp.utils.cache" 14 | local keymap = require "cmp.utils.keymap" 15 | local misc = require "cmp.utils.misc" 16 | local api = require "cmp.utils.api" 17 | 18 | ---@class cmp.TestConfig 19 | ---@field public g cmp.ConfigSchema 20 | local config = {} 21 | 22 | ---@type cmp.Cache 23 | config.cache = cache.new() 24 | 25 | ---@type cmp.ConfigSchema 26 | config.global = require "cmp.config.default"() 27 | 28 | ---@type table 29 | config.buffers = {} 30 | 31 | ---@type table 32 | config.cmdline = {} 33 | 34 | ---Set configuration for global. 35 | ---@param c cmp.ConfigSchema 36 | config.set_global = function(c) 37 | config.global = misc.merge(c, config.global) 38 | config.global.revision = config.global.revision or 1 39 | config.global.revision = config.global.revision + 1 40 | end 41 | 42 | ---Set configuration for buffer 43 | ---@param c cmp.ConfigSchema 44 | ---@param bufnr number|nil 45 | config.set_buffer = function(c, bufnr) 46 | local revision = (config.buffers[bufnr] or {}).revision or 1 47 | config.buffers[bufnr] = c 48 | config.buffers[bufnr].revision = revision + 1 49 | end 50 | 51 | ---Set configuration for cmdline 52 | config.set_cmdline = function(c, type) 53 | local revision = (config.cmdline[type] or {}).revision or 1 54 | config.cmdline[type] = c 55 | config.cmdline[type].revision = revision + 1 56 | end 57 | 58 | ---@return cmp.ConfigSchema 59 | config.get = function() 60 | local global = config.global 61 | if api.is_cmdline_mode() then 62 | local type = vim.fn.getcmdtype() 63 | local cmdline = config.cmdline[type] or { revision = 1, sources = {} } 64 | return config.cache:ensure({ "get_cmdline", type, global.revision or 0, cmdline.revision or 0 }, function() 65 | return misc.merge(config.normalize(cmdline), config.normalize(global)) 66 | end) 67 | else 68 | local bufnr = vim.api.nvim_get_current_buf() 69 | local buffer = config.buffers[bufnr] or { revision = 1 } 70 | return config.cache:ensure({ "get_buffer", bufnr, global.revision or 0, buffer.revision or 0 }, function() 71 | return misc.merge(config.normalize(buffer), config.normalize(global)) 72 | end) 73 | end 74 | end 75 | 76 | ---Return cmp is enabled or not. 77 | config.enabled = function() 78 | local enabled = config.get().enabled 79 | if type(enabled) == "function" then 80 | enabled = enabled() 81 | end 82 | return enabled and api.is_suitable_mode() 83 | end 84 | 85 | ---Return source config 86 | ---@param name string 87 | ---@return cmp.SourceConfig 88 | config.get_source_config = function(name) 89 | local bufnr = vim.api.nvim_get_current_buf() 90 | local global = config.global 91 | local buffer = config.buffers[bufnr] or { revision = 1 } 92 | return config.cache:ensure( 93 | { "get_source_config", bufnr, global.revision or 0, buffer.revision or 0, name }, 94 | function() 95 | local c = config.get() 96 | for _, s in ipairs(c.sources) do 97 | if s.name == name then 98 | if type(s.opts) ~= "table" then 99 | s.opts = {} 100 | end 101 | return s 102 | end 103 | end 104 | return nil 105 | end 106 | ) 107 | end 108 | 109 | ---Normalize mapping key 110 | ---@param c cmp.ConfigSchema 111 | ---@return cmp.ConfigSchema 112 | config.normalize = function(c) 113 | if c.mapping then 114 | for k, v in pairs(c.mapping) do 115 | c.mapping[keymap.normalize(k)] = v 116 | end 117 | end 118 | return c 119 | end 120 | 121 | return config 122 | -------------------------------------------------------------------------------- /queries/lua/highlights.scm: -------------------------------------------------------------------------------- 1 | ;;; Highlighting for lua 2 | 3 | ;;; Builtins 4 | ;; Keywords 5 | 6 | [(if_start) 7 | (if_then) 8 | (if_elseif) 9 | (if_else) 10 | (if_end)] @keyword.conditional 11 | 12 | [(for_start) 13 | (for_in) 14 | (for_do) 15 | (for_end)] @keyword.repeat 16 | 17 | [(while_start) 18 | (while_do) 19 | (while_end)] @keyword.repeat 20 | 21 | [(repeat_start) 22 | (repeat_until)] @keyword.repeat 23 | 24 | (break_statement) @keyword.repeat 25 | 26 | [(return_statement) 27 | (module_return_statement)] @keyword.return 28 | 29 | [(do_start) 30 | (do_end)] @keyword 31 | 32 | ; [ 33 | ; "goto" 34 | ; ] @keyword 35 | 36 | ;; Operators 37 | 38 | ; TODO: I think I've made a bunch of these nodes. 39 | ; we might be able to just use those! 40 | 41 | [ 42 | "not" 43 | "and" 44 | "or"] @keyword.operator 45 | 46 | ["=" 47 | "~=" 48 | "==" 49 | "<=" 50 | ">=" 51 | "<" 52 | ">" 53 | "+" 54 | "-" 55 | "%" 56 | "/" 57 | "//" 58 | "*" 59 | "^" 60 | "&" 61 | "~" 62 | "|" 63 | ">>" 64 | "<<" 65 | ".." 66 | "#"] @operator 67 | 68 | 69 | 70 | ;; Punctuation 71 | ["," "."] @punctuation.delimiter 72 | 73 | ;; Brackets 74 | [(left_paren) 75 | (right_paren) 76 | "[" 77 | "]" 78 | "{" 79 | "}"] @punctuation.bracket 80 | 81 | ;; Variables 82 | (identifier) @variable 83 | ( 84 | (identifier) @variable.builtin 85 | (#match? @variable.builtin "self")) 86 | 87 | ; (preproc_call 88 | ; directive: (preproc_directive) @_u 89 | ; argument: (_) @constant 90 | ; (#eq? @_u "#undef")) 91 | 92 | ;; Constants 93 | (boolean) @boolean 94 | (nil) @constant.builtin 95 | (ellipsis) @constant ;; "..." 96 | (local) @keyword 97 | 98 | ;; Functions 99 | (function_call_paren) @function.bracket 100 | 101 | [ 102 | (function_start) 103 | (function_end)] @keyword.function 104 | 105 | (emmy_type) @type 106 | (emmy_literal) @string 107 | (emmy_parameter 108 | (identifier) @parameter 109 | description: (_)? @comment) @comment 110 | 111 | (emmy_class) @comment 112 | (emmy_field name: (_) @property) @comment 113 | (emmy_function_parameter 114 | name: (_) @parameter) 115 | 116 | (emmy_type_dictionary_value key: (identifier) @property) 117 | 118 | (emmy_note) @comment 119 | (emmy_see) @comment 120 | 121 | ; TODO: Make the container so we can still highlight the beginning of the line 122 | ; (emmy_eval_container) @comment 123 | ; (_emmy_eval_container) @comment 124 | 125 | (emmy_return) @comment 126 | 127 | ; TODO: returns 128 | 129 | (emmy_header) @comment 130 | (emmy_ignore) @comment 131 | (documentation_brief) @comment 132 | 133 | (documentation_command) @comment 134 | 135 | (function_call 136 | [ 137 | ((identifier)+ @identifier . (identifier) @function.call . (function_call_paren)) 138 | ((identifier) @function.call.lua . (function_call_paren))]) 139 | 140 | (function_call 141 | prefix: (identifier) @function.call.lua 142 | args: (string_argument) @string) 143 | 144 | (function_call 145 | prefix: (identifier) @function.call.lua 146 | args: (table_argument)) 147 | 148 | ; (function [(function_name) (identifier)] @function) 149 | ; (function ["function" "end"] @keyword.function) 150 | ; (local_function [(function_name) (identifier)] @function) 151 | ; (local_function ["function" "end"] @keyword.function) 152 | ; (function_definition ["function" "end"] @keyword.function) 153 | 154 | ; TODO: Do I have replacements for these. 155 | ; (property_identifier) @property 156 | ; (method) @method 157 | 158 | ; (function_call (identifier) @function . (arguments)) 159 | ; (function_call (field (property_identifier) @function) . (arguments)) 160 | 161 | ;; Parameters 162 | ; (parameters (identifier) @parameter) 163 | 164 | ;; Nodes 165 | ; (table ["{" "}"] @constructor) 166 | (comment) @comment 167 | (string) @string 168 | (number) @number 169 | ; (label_statement) @label 170 | 171 | ;; Error 172 | (ERROR) @error 173 | -------------------------------------------------------------------------------- /lua/tests/docgen/brief_spec.lua: -------------------------------------------------------------------------------- 1 | local docgen = require "docgen" 2 | local docgen_help = require "docgen.help" 3 | 4 | local eq = assert.are.same 5 | 6 | local dedent = require("plenary.strings").dedent 7 | 8 | local dedent_trim = function(x) 9 | return vim.trim(dedent(x)) 10 | end 11 | 12 | local help_block = function(x) 13 | return "================================================================================\n" 14 | .. dedent_trim((x:gsub("\n%s*$", ""))) 15 | end 16 | 17 | local get_dedented_nodes = function(source) 18 | return docgen.get_nodes(dedent(source)) 19 | end 20 | 21 | describe("brief", function() 22 | local check_brief_nodes = function(input, brief_node) 23 | local nodes = get_dedented_nodes(input) 24 | 25 | eq({ 26 | brief = brief_node, 27 | }, nodes) 28 | end 29 | 30 | it("should generate a brief", function() 31 | check_brief_nodes( 32 | [=[ 33 | ---@brief [[ 34 | --- Hello world 35 | ---@brief ]] 36 | ]=], 37 | { "Hello world" } 38 | ) 39 | end) 40 | 41 | it("should generate a multi-line brief", function() 42 | check_brief_nodes( 43 | [=[ 44 | ---@brief [[ 45 | --- Hello world 46 | --- Yup again 47 | ---@brief ]] 48 | ]=], 49 | { "Hello world", "Yup again" } 50 | ) 51 | end) 52 | 53 | it("keeps empty strings for empty lines", function() 54 | check_brief_nodes( 55 | [=[ 56 | ---@brief [[ 57 | --- Hello world 58 | --- 59 | --- Yup again 60 | ---@brief ]] 61 | ]=], 62 | { "Hello world", "", "Yup again" } 63 | ) 64 | end) 65 | 66 | it("should keep indents in the inner strings", function() 67 | check_brief_nodes( 68 | [=[ 69 | ---@brief [[ 70 | --- Hello world: 71 | --- - This is indented 72 | --- - And this is some more 73 | --- - Not as indented 74 | ---@brief ]] 75 | ]=], 76 | { 77 | "Hello world:", 78 | " - This is indented", 79 | " - And this is some more", 80 | " - Not as indented", 81 | } 82 | ) 83 | end) 84 | 85 | describe("help output", function() 86 | local function check_brief_output(input, output) 87 | local nodes = require("docgen").get_nodes(input) 88 | local result = docgen_help.format(nodes) 89 | 90 | eq(help_block(output), vim.trim(result)) 91 | end 92 | 93 | it("should not wrap lines, if
", function() 94 | check_brief_output( 95 | [=[ 96 | ---@brief [[ 97 | --- Hello world
98 | --- Yup again 99 | ---@brief ]] 100 | ]=], 101 | [[ 102 | Hello world 103 | Yup again 104 | ]] 105 | ) 106 | end) 107 | 108 | it("should wrap lines", function() 109 | check_brief_output( 110 | [=[ 111 | ---@brief [[ 112 | --- Hello world 113 | --- Yup again 114 | ---@brief ]] 115 | ]=], 116 | [[ 117 | Hello world Yup again 118 | ]] 119 | ) 120 | end) 121 | 122 | it("should keep empty lines", function() 123 | check_brief_output( 124 | [=[ 125 | ---@brief [[ 126 | --- Hello world 127 | --- 128 | --- Yup again 129 | ---@brief ]] 130 | ]=], 131 | [[ 132 | Hello world 133 | 134 | Yup again 135 | ]] 136 | ) 137 | end) 138 | 139 | it("should keep indenting working", function() 140 | check_brief_output( 141 | [=[ 142 | ---@brief [[ 143 | --- Hello world: 144 | --- - This is indented 145 | --- - And this is some more 146 | --- - Not as indented 147 | ---@brief ]] 148 | ]=], 149 | [[ 150 | Hello world: 151 | - This is indented 152 | - And this is some more 153 | - Not as indented 154 | ]] 155 | ) 156 | end) 157 | end) 158 | end) 159 | -------------------------------------------------------------------------------- /scratch/emmy_lua_grammar.lua: -------------------------------------------------------------------------------- 1 | local _ = [[ 2 | EmmyLua <- ({} '---' EmmyBody {} ShortComment) 3 | -> EmmyLua 4 | EmmySp <- (!'---@' !'---' Comment / %s / %nl)* 5 | EmmyComments <- (EmmyComment (%nl EmmyComMulti / %nl EmmyComSingle)*) 6 | EmmyComment <- EmmySp %s* {(!%nl .)*} 7 | EmmyComMulti <- EmmySp '---|' {} -> en {(!%nl .)*} 8 | EmmyComSingle <- EmmySp '---' !'@' %s* {} -> ' ' {(!%nl .)*} 9 | EmmyBody <- '@class' %s+ EmmyClass -> EmmyClass 10 | / '@type' %s+ EmmyType -> EmmyType 11 | / '@alias' %s+ EmmyAlias -> EmmyAlias 12 | / '@param' %s+ EmmyParam -> EmmyParam 13 | / '@return' %s+ EmmyReturn -> EmmyReturn 14 | / '@field' %s+ EmmyField -> EmmyField 15 | / '@generic' %s+ EmmyGeneric -> EmmyGeneric 16 | / '@vararg' %s+ EmmyVararg -> EmmyVararg 17 | / '@language' %s+ EmmyLanguage -> EmmyLanguage 18 | / '@see' %s+ EmmySee -> EmmySee 19 | / '@overload' %s+ EmmyOverLoad -> EmmyOverLoad 20 | / %s* EmmyComments -> EmmyComment 21 | / EmmyIncomplete 22 | EmmyName <- ({} {[a-zA-Z_] [a-zA-Z0-9_]*}) 23 | -> EmmyName 24 | MustEmmyName <- EmmyName / DirtyEmmyName 25 | DirtyEmmyName <- {} -> DirtyEmmyName 26 | EmmyLongName <- ({} {(!%nl .)+}) 27 | -> EmmyName 28 | EmmyIncomplete <- MustEmmyName 29 | -> EmmyIncomplete 30 | EmmyClass <- (MustEmmyName EmmyParentClass?) 31 | EmmyParentClass <- %s* {} ':' %s* MustEmmyName 32 | EmmyType <- EmmyTypeUnits EmmyTypeEnums 33 | EmmyTypeUnits <- {| 34 | EmmyTypeUnit? 35 | (%s* '|' %s* !String EmmyTypeUnit)* 36 | |} 37 | EmmyTypeEnums <- {| EmmyTypeEnum* |} 38 | EmmyTypeUnit <- EmmyFunctionType 39 | / EmmyTableType 40 | / EmmyArrayType 41 | / EmmyCommonType 42 | EmmyCommonType <- EmmyName 43 | -> EmmyCommonType 44 | EmmyTypeEnum <- %s* (%nl %s* '---')? '|'? EmmyEnum 45 | -> EmmyTypeEnum 46 | EmmyEnum <- %s* {'>'?} %s* String (EmmyEnumComment / (!%nl !'|' .)*) 47 | EmmyEnumComment <- %s* '#' %s* {(!%nl .)*} 48 | EmmyAlias <- MustEmmyName %s* EmmyType EmmyTypeEnum* 49 | EmmyParam <- MustEmmyName %s* EmmyType %s* EmmyOption %s* EmmyTypeEnum* 50 | EmmyOption <- Table? 51 | -> EmmyOption 52 | EmmyReturn <- {} %nil {} Table -> EmmyOption 53 | / {} EmmyType {} EmmyOption 54 | EmmyField <- (EmmyFieldAccess MustEmmyName %s* EmmyType) 55 | EmmyFieldAccess <- ({'public'} Cut %s*) 56 | / ({'protected'} Cut %s*) 57 | / ({'private'} Cut %s*) 58 | / {} -> 'public' 59 | EmmyGeneric <- EmmyGenericBlock 60 | (%s* ',' %s* EmmyGenericBlock)* 61 | EmmyGenericBlock<- (MustEmmyName %s* (':' %s* EmmyType)?) 62 | -> EmmyGenericBlock 63 | EmmyVararg <- EmmyType 64 | EmmyLanguage <- MustEmmyName 65 | EmmyArrayType <- ({} MustEmmyName -> EmmyCommonType {} '[' DirtyBR) 66 | -> EmmyArrayType 67 | / ({} PL EmmyCommonType DirtyPR '[' DirtyBR) 68 | -> EmmyArrayType 69 | EmmyTableType <- ({} 'table' Cut '<' %s* EmmyType %s* ',' %s* EmmyType %s* '>' {}) 70 | -> EmmyTableType 71 | EmmyFunctionType<- ({} 'fun' Cut %s* EmmyFunctionArgs %s* EmmyFunctionRtns {}) 72 | -> EmmyFunctionType 73 | EmmyFunctionArgs<- ('(' %s* EmmyFunctionArg %s* (',' %s* EmmyFunctionArg %s*)* DirtyPR) 74 | -> EmmyFunctionArgs 75 | / '(' %nil DirtyPR -> None 76 | / %nil 77 | EmmyFunctionRtns<- (':' %s* EmmyType (%s* ',' %s* EmmyType)*) 78 | -> EmmyFunctionRtns 79 | / %nil 80 | EmmyFunctionArg <- MustEmmyName %s* ':' %s* EmmyType 81 | EmmySee <- {} MustEmmyName %s* '#' %s* MustEmmyName {} 82 | EmmyOverLoad <- EmmyFunctionType 83 | ]] 84 | -------------------------------------------------------------------------------- /scratch/gh_issue_005.lua: -------------------------------------------------------------------------------- 1 | local m = {} 2 | 3 | --- Test Header 1 4 | function m.a01() 5 | return 0 6 | end 7 | 8 | --- Duh docs 9 | function m.asdf() 10 | return 5 11 | end 12 | 13 | --- Test Header 2 14 | function m.a02() 15 | return 0 16 | end 17 | 18 | --- Test Header 3 19 | function m.a03() 20 | return 0 21 | end 22 | 23 | --- Test Header 5 24 | function m.a05() 25 | return 0 26 | end 27 | 28 | --- Test Header 6 29 | function m.a06() 30 | return 0 31 | end 32 | 33 | --- Test Header 7 34 | function m.a07() 35 | return 0 36 | end 37 | 38 | --- Test Header 8 39 | function m.a08() 40 | return 0 41 | end 42 | 43 | --- Test Header 9 44 | function m.a09() 45 | return 0 46 | end 47 | 48 | --- Test Header 10 49 | function m.a10() 50 | return 0 51 | end 52 | 53 | --- Test Header 11 54 | function m.a11() 55 | return 0 56 | end 57 | 58 | --- Test Header 12 59 | function m.a12() 60 | return 0 61 | end 62 | 63 | --- Test Header 13 64 | function m.a13() 65 | return 0 66 | end 67 | 68 | --- Test Header 14 69 | function m.a14() 70 | return 0 71 | end 72 | 73 | --- Test Header 15 74 | function m.a15() 75 | return 0 76 | end 77 | 78 | --- Test Header 16 79 | function m.a16() 80 | return 0 81 | end 82 | 83 | --- Test Header 17 84 | function m.a17() 85 | return 0 86 | end 87 | 88 | --- Test Header 17 89 | function m.a17() 90 | return 0 91 | end 92 | --- Test Header 17 93 | function m.a17() 94 | return 0 95 | end 96 | --- Test Header 17 97 | function m.a17() 98 | return 0 99 | end 100 | --- Test Header 17 101 | function m.a17() 102 | return 0 103 | end 104 | --- Test Header 17 105 | function m.a17() 106 | return 0 107 | end 108 | --- Test Header 17 109 | function m.a17() 110 | return 0 111 | end 112 | --- Test Header 17 113 | function m.a17() 114 | return 0 115 | end 116 | --- Test Header 17 117 | function m.a17() 118 | return 0 119 | end 120 | --- Test Header 17 121 | function m.a17() 122 | return 0 123 | end 124 | --- Test Header 17 125 | function m.a17() 126 | return 0 127 | end 128 | --- Test Header 17 129 | function m.a17() 130 | return 0 131 | end 132 | --- Test Header 17 133 | function m.a17() 134 | return 0 135 | end 136 | --- Test Header 17 137 | function m.a17() 138 | return 0 139 | end 140 | --- Test Header 17 141 | function m.a17() 142 | return 0 143 | end 144 | --- Test Header 17 145 | function m.a17() 146 | return 0 147 | end 148 | --- Test Header 17 149 | function m.a17() 150 | return 0 151 | end 152 | --- Test Header 17 153 | function m.a17() 154 | return 0 155 | end 156 | --- Test Header 17 157 | function m.a17() 158 | return 0 159 | end 160 | --- Test Header 17 161 | function m.a17() 162 | return 0 163 | end 164 | --- Test Header 17 165 | function m.a17() 166 | return 0 167 | end 168 | --- Test Header 17 169 | function m.a17() 170 | return 0 171 | end 172 | --- Test Header 17 173 | function m.a17() 174 | return 0 175 | end 176 | --- Test Header 17 177 | function m.a17() 178 | return 0 179 | end 180 | --- Test Header 17 181 | function m.a17() 182 | return 0 183 | end 184 | --- Test Header 17 185 | function m.a17() 186 | return 0 187 | end 188 | --- Test Header 17 189 | function m.a17() 190 | return 0 191 | end 192 | --- Test Header 17 193 | function m.a17() 194 | return 0 195 | end 196 | --- Test Header 17 197 | function m.a17() 198 | return 0 199 | end 200 | --- Test Header 17 201 | function m.a17() 202 | return 0 203 | end 204 | --- Test Header 17 205 | function m.a17() 206 | return 0 207 | end 208 | --- Test Header 17 209 | function m.a17() 210 | return 0 211 | end 212 | --- Test Header 17 213 | function m.a17() 214 | return 0 215 | end 216 | --- Test Header 17 217 | function m.a17() 218 | return 0 219 | end 220 | --- Test Header 17 221 | function m.a17() 222 | return 0 223 | end 224 | --- Test Header 17 225 | function m.a17() 226 | return 0 227 | end 228 | --- Test Header 17 229 | function m.a17() 230 | return 0 231 | end 232 | --- Test Header 17 233 | function m.a17() 234 | return 0 235 | end 236 | --- Test Header 17 237 | function m.a17() 238 | return 0 239 | end 240 | --- Test Header 17 241 | function m.a17() 242 | return 0 243 | end 244 | --- Test Header 17 245 | function m.a17() 246 | return 0 247 | end 248 | --- Test Header 17 249 | function m.a17() 250 | return 0 251 | end 252 | --- Test Header 17 253 | function m.a17() 254 | return 0 255 | end 256 | 257 | return m 258 | -------------------------------------------------------------------------------- /lua/tests/nlsp/text_document_sync_spec.lua: -------------------------------------------------------------------------------- 1 | if true then 2 | return 3 | end 4 | 5 | local methods = require "nlsp.methods" 6 | local state = require "nlsp.state" 7 | -- local structures = require('nlsp.structures') 8 | 9 | local query = require "vim.treesitter.query" 10 | 11 | describe("text_document_sync", function() 12 | before_each(function() 13 | -- Clear the state between executions. 14 | state._clear() 15 | end) 16 | 17 | it("should support opening a textDocument", function() 18 | local uri = vim.uri_from_fname "/home/fake.lua" 19 | local item = { 20 | uri = uri, 21 | text = "local hello = 'world'", 22 | } 23 | 24 | methods["textDocument/didOpen"] { 25 | textDocument = item, 26 | } 27 | 28 | local saved_item = state.get_text_document_item(uri) 29 | assert.are.same(saved_item.text, item.text) 30 | end) 31 | 32 | it("should support changing a textDocument", function() 33 | local uri = vim.uri_from_fname "/home/fake.lua" 34 | local item = { 35 | uri = uri, 36 | text = "local hello = 'world'", 37 | } 38 | 39 | local new_text = "local hello = 'goodbye'" 40 | 41 | methods["textDocument/didOpen"] { 42 | textDocument = vim.deepcopy(item), 43 | } 44 | 45 | methods["textDocument/didChange"] { 46 | textDocument = { 47 | uri = uri, 48 | }, 49 | contentChanges = { 50 | { text = new_text }, 51 | }, 52 | } 53 | 54 | local saved_item = state.get_text_document_item(uri) 55 | assert.are.same(saved_item.text, new_text) 56 | assert.are_not.same(item.text, new_text) 57 | end) 58 | 59 | it("should not allow calling didChange before didOpen", function() 60 | local uri = vim.uri_from_fname "/home/fake.lua" 61 | 62 | local ok = pcall(methods["textDocument/didChange"], { 63 | textDocument = { 64 | uri = uri, 65 | }, 66 | contentChanges = { 67 | { text = "this should not matter" }, 68 | }, 69 | }) 70 | 71 | assert(not ok) 72 | end) 73 | 74 | it("should support saving", function() 75 | local uri = vim.uri_from_fname "/home/fake.lua" 76 | local item = { 77 | uri = uri, 78 | text = "local hello = 'world'", 79 | } 80 | 81 | local new_text = "local hello = 'goodbye'" 82 | 83 | methods["textDocument/didOpen"] { 84 | textDocument = vim.deepcopy(item), 85 | } 86 | 87 | methods["textDocument/didSave"] { 88 | textDocument = { 89 | uri = uri, 90 | }, 91 | text = new_text, 92 | } 93 | 94 | local saved_item = state.get_text_document_item(uri) 95 | assert.are.same(saved_item.text, new_text) 96 | assert.are_not.same(item.text, new_text) 97 | end) 98 | 99 | it("should not allow calling didSave before didOpen", function() 100 | local uri = vim.uri_from_fname "/home/fake.lua" 101 | 102 | local ok = pcall(methods["textDocument/didSave"], { 103 | textDocument = { 104 | uri = uri, 105 | }, 106 | text = "this should not matter", 107 | }) 108 | 109 | assert(not ok) 110 | end) 111 | 112 | it("should allow closing documents", function() 113 | local uri = vim.uri_from_fname "/home/fake.lua" 114 | local item = { 115 | uri = uri, 116 | text = "local hello = 'world'", 117 | } 118 | 119 | methods["textDocument/didOpen"] { 120 | textDocument = vim.deepcopy(item), 121 | } 122 | 123 | methods["textDocument/didClose"] { 124 | textDocument = { 125 | uri = uri, 126 | }, 127 | } 128 | 129 | local saved_item = state.get_text_document_item(uri) 130 | assert(saved_item == nil) 131 | end) 132 | 133 | describe("parser:parse()", function() 134 | it("should create a parser on open", function() 135 | local uri = vim.uri_from_fname "/home/fake.lua" 136 | local item = { 137 | uri = uri, 138 | text = "local hello = 'world'", 139 | } 140 | 141 | methods["textDocument/didOpen"] { 142 | textDocument = item, 143 | } 144 | 145 | local parser = state.get_ts_parser(uri) 146 | assert.are_not.same(parser, nil) 147 | 148 | parser = parser:parse()[1] 149 | local root = parser:root() 150 | assert.are.same(vim.treesitter.get_node_text(root, item.text), item.text) 151 | assert.are.same(root:type(), "program") 152 | end) 153 | end) 154 | end) 155 | -------------------------------------------------------------------------------- /lua/docgen/log.lua: -------------------------------------------------------------------------------- 1 | -- log.lua 2 | -- 3 | -- Inspired by rxi/log.lua 4 | -- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | 9 | -- User configuration section 10 | local default_config = { 11 | -- Name of the plugin. Prepended to log messages 12 | plugin = "docgen.nvim", 13 | 14 | -- Should print the output to neovim while running 15 | use_console = true, 16 | 17 | -- Should highlighting be used in console (using echohl) 18 | highlights = true, 19 | 20 | -- Should write to a file 21 | use_file = true, 22 | 23 | -- Any messages above this level will be logged. 24 | level = "info", 25 | 26 | -- Level configuration 27 | modes = { 28 | { name = "trace", hl = "Comment" }, 29 | { name = "debug", hl = "Comment" }, 30 | { name = "info", hl = "None" }, 31 | { name = "warn", hl = "WarningMsg" }, 32 | { name = "error", hl = "ErrorMsg" }, 33 | { name = "fatal", hl = "ErrorMsg" }, 34 | }, 35 | 36 | -- Can limit the number of decimals displayed for floats 37 | float_precision = 0.01, 38 | } 39 | 40 | -- {{{ NO NEED TO CHANGE 41 | local log = {} 42 | 43 | local unpack = unpack or table.unpack 44 | 45 | log.new = function(config, standalone) 46 | config = vim.tbl_deep_extend("force", default_config, config) 47 | 48 | local outfile = string.format("%s/%s.log", vim.api.nvim_call_function("stdpath", { "data" }), config.plugin) 49 | 50 | local obj 51 | if standalone then 52 | obj = log 53 | else 54 | obj = {} 55 | end 56 | 57 | local levels = {} 58 | for i, v in ipairs(config.modes) do 59 | levels[v.name] = i 60 | end 61 | 62 | local round = function(x, increment) 63 | increment = increment or 1 64 | x = x / increment 65 | return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment 66 | end 67 | 68 | local make_string = function(...) 69 | local t = {} 70 | for i = 1, select("#", ...) do 71 | local x = select(i, ...) 72 | 73 | if type(x) == "number" and config.float_precision then 74 | x = tostring(round(x, config.float_precision)) 75 | elseif type(x) == "table" then 76 | x = vim.inspect(x) 77 | else 78 | x = tostring(x) 79 | end 80 | 81 | t[#t + 1] = x 82 | end 83 | return table.concat(t, " ") 84 | end 85 | 86 | local log_at_level = function(level, level_config, message_maker, ...) 87 | -- Return early if we're below the config.level 88 | if level < levels[config.level] then 89 | return 90 | end 91 | local nameupper = level_config.name:upper() 92 | 93 | local msg = message_maker(...) 94 | local info = debug.getinfo(2, "Sl") 95 | local lineinfo = info.short_src .. ":" .. info.currentline 96 | 97 | -- Output to console 98 | if config.use_console then 99 | local console_string = string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg) 100 | 101 | if config.highlights and level_config.hl then 102 | vim.cmd(string.format("echohl %s", level_config.hl)) 103 | end 104 | 105 | local split_console = vim.split(console_string, "\n") 106 | for _, v in ipairs(split_console) do 107 | vim.cmd(string.format([[echom "[%s] %s"]], config.plugin, vim.fn.escape(v, '"'))) 108 | end 109 | 110 | if config.highlights and level_config.hl then 111 | vim.cmd "echohl NONE" 112 | end 113 | end 114 | 115 | -- Output to log file 116 | if config.use_file then 117 | local fp = io.open(outfile, "a") 118 | local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) 119 | fp:write(str) 120 | fp:close() 121 | end 122 | end 123 | 124 | for i, x in ipairs(config.modes) do 125 | obj[x.name] = function(...) 126 | return log_at_level(i, x, make_string, ...) 127 | end 128 | 129 | obj[("fmt_%s"):format(x.name)] = function() 130 | return log_at_level(i, x, function(...) 131 | local passed = { ... } 132 | local fmt = table.remove(passed, 1) 133 | local inspected = {} 134 | for _, v in ipairs(passed) do 135 | table.insert(inspected, vim.inspect(v)) 136 | end 137 | return string.format(fmt, unpack(inspected)) 138 | end) 139 | end 140 | end 141 | end 142 | 143 | log.new(default_config, true) 144 | -- }}} 145 | 146 | return log 147 | -------------------------------------------------------------------------------- /test/corpus/blocks.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Can do simple do block 4 | ================== 5 | 6 | do x = 1 end 7 | 8 | --- 9 | 10 | (program 11 | (do_statement 12 | (do_start) 13 | (variable_declaration 14 | (variable_declarator (identifier)) 15 | (number)) 16 | (do_end))) 17 | 18 | ================== 19 | Can have do blocks with more than one thing 20 | ================== 21 | 22 | do 23 | x = 1 24 | my_func() 25 | end 26 | 27 | --- 28 | 29 | (program 30 | (do_statement 31 | (do_start) 32 | (variable_declaration 33 | (variable_declarator (identifier)) 34 | (number)) 35 | (function_call 36 | (identifier) (function_call_paren) (function_call_paren)) 37 | (do_end))) 38 | 39 | ================== 40 | While blocks 41 | ================== 42 | 43 | while false do x = x + 1 end 44 | 45 | --- 46 | 47 | (program 48 | (while_statement 49 | (while_start) 50 | (boolean) 51 | (while_do) 52 | (variable_declaration 53 | (variable_declarator (identifier)) 54 | (binary_operation (identifier) (number))) 55 | (while_end))) 56 | 57 | ================== 58 | Repeat block 59 | ================== 60 | 61 | repeat x = x + 1 until true 62 | 63 | --- 64 | 65 | (program 66 | (repeat_statement 67 | (repeat_start) 68 | (variable_declaration 69 | (variable_declarator (identifier)) 70 | (binary_operation (identifier) (number))) 71 | (repeat_until) 72 | (boolean))) 73 | 74 | ================== 75 | If blocks 76 | ================== 77 | 78 | if x then return y end 79 | 80 | --- 81 | 82 | (program 83 | (if_statement 84 | (if_start) 85 | (identifier) 86 | (if_then) 87 | (return_statement (identifier)) 88 | (if_end))) 89 | 90 | ================== 91 | If elseif else blocks 92 | ================== 93 | 94 | if x == 0 then return y elseif x == nil then return 7 else return "str" end 95 | 96 | --- 97 | 98 | (program 99 | (if_statement 100 | (if_start) 101 | (binary_operation (identifier) (number)) 102 | (if_then) 103 | (return_statement (identifier)) 104 | (if_elseif) 105 | (binary_operation (identifier) (nil)) 106 | (if_then) 107 | (return_statement (number)) 108 | (if_else) 109 | (return_statement (string)) 110 | (if_end))) 111 | 112 | ================== 113 | For loop, identifier style 114 | ================== 115 | 116 | for x = 1, 10 do print(x) end 117 | 118 | --- 119 | 120 | (program 121 | (for_statement 122 | (for_start) 123 | (for_numeric 124 | var: (identifier) 125 | start: (number) 126 | finish: (number)) 127 | (for_do) 128 | (function_call 129 | prefix: (identifier) 130 | (function_call_paren) 131 | args: (function_arguments (identifier)) 132 | (function_call_paren)) 133 | (for_end))) 134 | 135 | ================== 136 | For loop, identifier style 137 | ================== 138 | 139 | for x = 1, 10, z do print(x) end 140 | 141 | --- 142 | 143 | (program 144 | (for_statement 145 | (for_start) 146 | (for_numeric 147 | var: (identifier) 148 | start: (number) 149 | finish: (number) 150 | step: (identifier)) 151 | (for_do) 152 | (function_call 153 | prefix: (identifier) 154 | (function_call_paren) 155 | args: (function_arguments (identifier)) 156 | (function_call_paren)) 157 | (for_end))) 158 | 159 | ================== 160 | For loop, identifier style 161 | ================== 162 | 163 | for k, v in ipairs(x) do print(k, v) end 164 | 165 | --- 166 | 167 | (program 168 | (for_statement 169 | (for_start) 170 | (for_generic 171 | identifier_list: (identifier_list (identifier) (identifier)) 172 | (for_in) 173 | expression_list: (function_call 174 | prefix: (identifier) 175 | (function_call_paren) 176 | args: (function_arguments (identifier)) 177 | (function_call_paren))) 178 | (for_do) 179 | (function_call 180 | prefix: (identifier) 181 | (function_call_paren) 182 | args: (function_arguments (identifier) (identifier)) 183 | (function_call_paren)) 184 | (for_end))) 185 | 186 | ================== 187 | Can handle for loops with no body 188 | ================== 189 | 190 | for _, x in ipairs(mylist) do 191 | end 192 | 193 | --- 194 | 195 | (program 196 | (for_statement 197 | (for_start) 198 | (for_generic 199 | (identifier_list 200 | (identifier) 201 | (identifier)) 202 | (for_in) 203 | (function_call 204 | (identifier) 205 | (function_call_paren) 206 | (function_arguments 207 | (identifier)) 208 | (function_call_paren))) 209 | (for_do) 210 | (for_end))) 211 | 212 | ================== 213 | Returns from if statement with no values 214 | ================== 215 | 216 | if not diagnostics then return end 217 | 218 | --- 219 | 220 | (program 221 | (if_statement 222 | (if_start) 223 | (unary_operation (identifier)) 224 | (if_then) 225 | (return_statement) 226 | (if_end))) 227 | -------------------------------------------------------------------------------- /test/corpus/functions.txt: -------------------------------------------------------------------------------- 1 | 2 | ================== 3 | Declare global function 4 | ================== 5 | 6 | function x() return 5 end 7 | 8 | --- 9 | 10 | (program 11 | (function_statement 12 | (function_start) 13 | name: (function_name (identifier)) 14 | (function_body_paren) 15 | (function_body_paren) 16 | (function_body (return_statement (number))) 17 | (function_end))) 18 | 19 | ================== 20 | Declare table function 21 | ================== 22 | 23 | function t.x() return 5 end 24 | 25 | --- 26 | 27 | (program 28 | (function_statement 29 | (function_start) 30 | name: (function_name (identifier) (table_dot) (identifier)) 31 | (function_body_paren) 32 | (function_body_paren) 33 | (function_body (return_statement (number))) 34 | (function_end))) 35 | 36 | ================== 37 | Declare table function 38 | ================== 39 | 40 | function t:x() return 5 end 41 | 42 | --- 43 | 44 | (program 45 | (function_statement 46 | (function_start) 47 | name: (function_name (identifier) (table_colon) (identifier)) 48 | (function_body_paren) 49 | (function_body_paren) 50 | (function_body (return_statement (number))) 51 | (function_end))) 52 | 53 | ================== 54 | Declare local function 55 | ================== 56 | 57 | local function f() print("hi"); return 5 end 58 | 59 | --- 60 | 61 | (program 62 | (function_statement 63 | (local) 64 | (function_start) 65 | name: (identifier) 66 | (function_body_paren) 67 | (function_body_paren) 68 | (function_body 69 | (function_call 70 | prefix: (identifier) 71 | (function_call_paren) 72 | args: (function_arguments (string)) 73 | (function_call_paren)) 74 | (return_statement (number))) 75 | (function_end))) 76 | 77 | ================== 78 | Declare local function, error 79 | ================== 80 | 81 | local function t.x() return 5 end 82 | 83 | --- 84 | 85 | (program 86 | (function_statement 87 | (local) 88 | (function_start) 89 | (identifier) 90 | (ERROR (UNEXPECTED 'x')) 91 | (function_body_paren) 92 | (function_body_paren) 93 | (function_body (return_statement (number))) 94 | (function_end))) 95 | 96 | ================== 97 | Declare function with an argument 98 | ================== 99 | 100 | function f(x) end 101 | 102 | --- 103 | 104 | (program 105 | (function_statement 106 | (function_start) 107 | (function_name (identifier)) 108 | (function_body_paren) 109 | (parameter_list (identifier)) 110 | (function_body_paren) 111 | (function_end))) 112 | 113 | ================== 114 | No trailing commas in function declaration 115 | ================== 116 | 117 | function f(x,) end 118 | 119 | --- 120 | 121 | (program 122 | (function_statement 123 | (function_start) 124 | (function_name (identifier)) 125 | (function_body_paren) 126 | (parameter_list (identifier)) 127 | (ERROR) 128 | (function_body_paren) 129 | (function_end))) 130 | 131 | ================== 132 | Declare function with two arguments 133 | ================== 134 | 135 | function f(wow, two_vars) end 136 | 137 | --- 138 | 139 | (program 140 | (function_statement 141 | (function_start) 142 | (function_name (identifier)) 143 | (function_body_paren) 144 | (parameter_list (identifier) (identifier)) 145 | (function_body_paren) 146 | (function_end))) 147 | 148 | ================== 149 | Declare function with ellipsis 150 | ================== 151 | 152 | function f(...) end 153 | 154 | --- 155 | 156 | (program 157 | (function_statement 158 | (function_start) 159 | (function_name (identifier)) 160 | (function_body_paren) 161 | (parameter_list (ellipsis)) 162 | (function_body_paren) 163 | (function_end))) 164 | 165 | ================== 166 | Declare function with one arg and ellipsis 167 | ================== 168 | 169 | function f(x, ...) end 170 | 171 | --- 172 | 173 | (program 174 | (function_statement 175 | (function_start) 176 | (function_name (identifier)) 177 | (function_body_paren) 178 | (parameter_list (identifier) (ellipsis)) 179 | (function_body_paren) 180 | (function_end))) 181 | 182 | ================== 183 | Declare function with multiple args and ellipsis 184 | ================== 185 | 186 | function f(x, y, z, ...) end 187 | 188 | --- 189 | 190 | (program 191 | (function_statement 192 | (function_start) 193 | (function_name (identifier)) 194 | (function_body_paren) 195 | (parameter_list (identifier) (identifier) (identifier) (ellipsis)) 196 | (function_body_paren) 197 | (function_end))) 198 | 199 | ================== 200 | Declare a function with documentation ahead of it 201 | ================== 202 | 203 | --- 204 | function f() end 205 | 206 | --- 207 | 208 | (program 209 | (function_statement 210 | (emmy_documentation 211 | (emmy_header)) 212 | (function_start) 213 | (function_name 214 | (identifier)) 215 | (function_body_paren) 216 | (function_body_paren) 217 | (function_end))) 218 | -------------------------------------------------------------------------------- /lua/docgen/init.lua: -------------------------------------------------------------------------------- 1 | vim.cmd [[runtime plugin/ts_lua.lua]] 2 | 3 | local call_transformer = require "docgen.transformers" 4 | local log = require "docgen.log" 5 | local utils = require "docgen.utils" 6 | 7 | local read = utils.read 8 | 9 | ---@brief [[ 10 | --- Public API for all associated docgen procedures. 11 | --- 12 | --- Supported tags: 13 | --- - @brief: 14 | --- - Usage: 15 | ---
 16 | --- ---@brief [[
 17 | --- --- You can put things you want to say about the project here.
 18 | --- --- It gets put at the top of the help file.
 19 | --- ---@brief ]]
 20 | 
 21 | ---@brief ]]
 22 | 
 23 | ---@tag docgen
 24 | 
 25 | -- Load up our build parser.
 26 | 
 27 | local docgen = {}
 28 | 
 29 | docgen.debug = false
 30 | 
 31 | function docgen._get_query_text(query_name)
 32 |   return read(vim.api.nvim_get_runtime_file(string.format("query/lua/%s.scm", query_name), false)[1])
 33 | end
 34 | 
 35 | --- Get the query for a tree sitter query, loaded from query directory.
 36 | ---@param query_name string: The name of the query file (without .scm)
 37 | function docgen.get_ts_query(query_name)
 38 |   return vim.treesitter.query.parse("lua", docgen._get_query_text(query_name))
 39 | end
 40 | 
 41 | --- Get the string parser for some contents
 42 | function docgen.get_parser(contents)
 43 |   return vim.treesitter.get_string_parser(contents, "lua")
 44 | end
 45 | 
 46 | --- Run {cb} on each node from contents and query
 47 | ---@param contents string: Contents to pass to string parser
 48 | ---@param query_name string: Name of the query to search for
 49 | ---@param cb function: Function to call on captures with (id, node)
 50 | function docgen.foreach_node(contents, query_name, cb)
 51 |   local parser = docgen.get_parser(contents)
 52 |   local query = docgen.get_ts_query(query_name)
 53 | 
 54 |   local tree = parser:parse()[1]
 55 | 
 56 |   for id, node in query:iter_captures(tree:root(), contents, 0, -1) do
 57 |     log.debug(id, node:type())
 58 | 
 59 |     cb(id, node)
 60 |   end
 61 | end
 62 | 
 63 | function docgen.transform_nodes(contents, query_name, toplevel_types, return_module)
 64 |   local t = {}
 65 | 
 66 |   t.return_module = return_module
 67 | 
 68 |   docgen.foreach_node(contents, query_name, function(id, node)
 69 |     if toplevel_types[node:type()] then
 70 |       local ok, result = pcall(call_transformer, t, contents, node, return_module)
 71 |       if not ok then
 72 |         print("ERROR:", id, node, result)
 73 |       end
 74 |     end
 75 |   end)
 76 | 
 77 |   return t
 78 | end
 79 | 
 80 | local function find_return_module(contents)
 81 |   local parser = docgen.get_parser(contents)
 82 |   -- Its better to just have one query here. That way i know as soon as i found the
 83 |   -- module return statement i am done. Its bad for performance that i am parsing the
 84 |   -- file twice now but its no performance critical thing, so nvm
 85 |   local query = vim.treesitter.query.parse("lua", "(module_return_statement (identifier) @exported)")
 86 |   local tree = parser:parse()[1]
 87 | 
 88 |   for _, node in query:iter_captures(tree:root(), contents, 0, -1) do -- luacheck: ignore
 89 |     return vim.treesitter.get_node_text(node, contents)
 90 |   end
 91 | end
 92 | 
 93 | function docgen.get_nodes(contents)
 94 |   local query_name = "documentation"
 95 |   local toplevel_types = {
 96 |     variable_declaration = true,
 97 |     function_statement = true,
 98 |     documentation_brief = true,
 99 |     documentation_tag = true,
100 |     documentation_config = true,
101 |     documentation_class = true,
102 |     documentation_command = true,
103 |   }
104 | 
105 |   -- Can be nil here. That way it still works if the file only has a brief.
106 |   -- I do the nil check in the transformer _function
107 |   local return_module = find_return_module(contents)
108 | 
109 |   return docgen.transform_nodes(contents, query_name, toplevel_types, return_module)
110 | end
111 | 
112 | function docgen.write(input_file, output_file_handle)
113 |   log.trace("Transforming:", input_file)
114 |   local contents = read(input_file)
115 |   local resulting_nodes = docgen.get_nodes(contents)
116 | 
117 |   if docgen.debug then
118 |     print("Resulting Nodes:", vim.inspect(resulting_nodes))
119 |   end
120 | 
121 |   local result = require("docgen.help").format(resulting_nodes)
122 |   output_file_handle:write(result)
123 | end
124 | 
125 | function docgen.test()
126 |   local input_dir = "./lua/docgen/"
127 |   local input_files = vim.fn.globpath(input_dir, "**/*.lua", false, true)
128 | 
129 |   -- Always put init.lua first, then you can do other stuff.
130 |   table.sort(input_files, function(a, b)
131 |     if string.find(a, "init.lua") then
132 |       return true
133 |     elseif string.find(b, "init.lua") then
134 |       return false
135 |     else
136 |       return a < b
137 |     end
138 |   end)
139 | 
140 |   local output_file = "./scratch/docgen_output.txt"
141 |   local output_file_handle = io.open(output_file, "w")
142 | 
143 |   for _, input_file in ipairs(input_files) do
144 |     docgen.write(input_file, output_file_handle)
145 |   end
146 | 
147 |   output_file_handle:write " vim:tw=78:ts=8:ft=help:norl:"
148 |   output_file_handle:close()
149 |   vim.cmd [[checktime]]
150 | end
151 | 
152 | --[[
153 |  ["M.example"] = {
154 |      name = "M.example",
155 |     description = "--- Example function",
156 |     parameters = {
157 |       a = {
158 |         description = { "This is a number" },
159 |         name = "a",
160 |         type = "number"
161 |       },
162 |       b = {
163 |         description = { "Also a number" },
164 |         name = "b",
165 |         type = "number"
166 |       }
167 |     }
168 |   }
169 | --]]
170 | 
171 | return docgen
172 | 


--------------------------------------------------------------------------------
/scratch/gen_howto.lua:
--------------------------------------------------------------------------------
  1 | local docgen = require "docgen"
  2 | local docgen_help = require "docgen.help"
  3 | local docgen_util = require "docgen.utils"
  4 | 
  5 | local dedent = require("plenary.strings").dedent
  6 | 
  7 | local is_empty = function(content)
  8 |   local lines = vim.split(content, "\n")
  9 |   for i = 2, #lines do
 10 |     if lines[i] ~= "" then
 11 |       return false
 12 |     end
 13 |   end
 14 |   return true
 15 | end
 16 | 
 17 | local docs = {}
 18 | 
 19 | docs.test = function()
 20 |   -- Filepaths that should generate docs
 21 |   local input_files = {
 22 |     {
 23 |       head = "## Brief",
 24 |       pre_desc = "Brief is used to describe a module. This is an example input:",
 25 |       input = "./example/brief.lua",
 26 |       post_desc = "",
 27 |     },
 28 |     {
 29 |       head = "## Tag",
 30 |       pre_desc = "Add a tag to your module. This is suggested:",
 31 |       input = "./example/tag.lua",
 32 |       post_desc = "",
 33 |     },
 34 |     {
 35 |       head = "## Config",
 36 |       pre_desc = "You can configure docgen on file basis. For example you can define how `functions` or `classes` are sorted.",
 37 |       input = "./example/config.lua",
 38 |       post_desc = dedent [[
 39 |         Available keys value pairs are:
 40 |         - `function_order`:
 41 |           - `file_order` (default)
 42 |           - `ascending`
 43 |           - `descending`
 44 |           - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
 45 |           - If you have a typo it will do `file_order` sorting
 46 |         - `class_order`:
 47 |           - `file_order` (default)
 48 |           - `ascending`
 49 |           - `descending`
 50 |           - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
 51 |           - If you have a typo it will do `file_order` sorting
 52 |         - `field_order`:
 53 |           - `file_order` (default)
 54 |           - `ascending`
 55 |           - `descending`
 56 |           - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
 57 |           - If you have a typo it will do `file_order` sorting]],
 58 |     },
 59 |     {
 60 |       head = "## Function Header",
 61 |       pre_desc = dedent [[
 62 |         You can describe your functions.
 63 | 
 64 |         Note: We will only generate documentation for functions that are exported with the module.]],
 65 |       input = "./example/function.lua",
 66 |       post_desc = "",
 67 |     },
 68 |     {
 69 |       head = "## Parameter",
 70 |       pre_desc = "You can specify parameters and document them with `---@param name type: desc`",
 71 |       input = "./example/parameter.lua",
 72 |       post_desc = "",
 73 |     },
 74 |     {
 75 |       head = "## Field",
 76 |       pre_desc = "Can be used to describe a parameter table.",
 77 |       input = "./example/field.lua",
 78 |       post_desc = "",
 79 |     },
 80 |     {
 81 |       head = "## Return",
 82 |       pre_desc = "You can specify a return parameter with `---@return type: desc`",
 83 |       input = "./example/return.lua",
 84 |       post_desc = "",
 85 |     },
 86 |     {
 87 |       head = "## See",
 88 |       pre_desc = "Reference something else.",
 89 |       input = "./example/see.lua",
 90 |       post_desc = "",
 91 |     },
 92 |     {
 93 |       head = "## Class",
 94 |       pre_desc = dedent [[
 95 |         You can define your own classes and types to give a better sense of the Input or Ouput of a function.
 96 |         Another good usecase for this are structs defined by ffi.
 97 | 
 98 |         This is a more complete (not functional) example where we define the documentation of the c struct
 99 |         `passwd` and return this struct with a function.]],
100 |       input = "./example/class.lua",
101 |       post_desc = "",
102 |     },
103 |     {
104 |       head = "## Eval",
105 |       pre_desc = dedent [[
106 |       You can evaluate arbitrary code. For example if you have a static table you can
107 |       do generate a table that will be part of the `description` output.
108 |       ]],
109 |       input = "./example/eval.lua",
110 |       -- TODO(conni2461):
111 |       -- Hard code eval because currently it doesn't create something.
112 |       -- Your module doesn't exist. We can tackle this when we can have multiline
113 |       -- eval
114 |       post_desc = dedent [[
115 | 
116 |         Output:
117 | 
118 |         ```
119 |         ================================================================================
120 |         m.actual_func()                                              *m.actual_func()*
121 |             The documentation for this function will be generated. The markdown
122 |             renderer will be used again.
123 |             With the same set of features.
124 | 
125 |             Static Values: ~
126 |                 a
127 |                 b
128 |                 c
129 |                 d
130 |         ```]],
131 |     },
132 |   }
133 | 
134 |   local output_file_handle = io.open("HOWTO.md", "w")
135 |   output_file_handle:write "# How to write emmy documentation\n\n"
136 |   for _, file in pairs(input_files) do
137 |     output_file_handle:write(file.head .. "\n\n")
138 |     if file.pre_desc ~= "" then
139 |       output_file_handle:write(file.pre_desc .. "\n\n")
140 |     end
141 | 
142 |     local content = docgen_util.read(file.input)
143 |     output_file_handle:write("```lua\n" .. content .. "\n```\n")
144 | 
145 |     local output = docgen_help.format(docgen.get_nodes(content))
146 |     if not is_empty(output) then
147 |       output_file_handle:write("\nOutput:\n\n```\n" .. output .. "\n```\n\n")
148 |     end
149 | 
150 |     if file.post_desc ~= "" then
151 |       output_file_handle:write(file.post_desc .. "\n\n")
152 |     end
153 |   end
154 |   output_file_handle:close()
155 | end
156 | 
157 | docs.test()
158 | 
159 | return docs
160 | 


--------------------------------------------------------------------------------
/src/scanner.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | #include 
  4 | 
  5 | enum TokenType {
  6 |   BLOCK_COMMENT_START,
  7 |   BLOCK_COMMENT_CONTENT,
  8 |   BLOCK_COMMENT_END,
  9 | 
 10 |   STRING_START,
 11 |   STRING_CONTENT,
 12 |   STRING_END,
 13 | };
 14 | 
 15 | static inline void consume(TSLexer *lexer) { lexer->advance(lexer, false); }
 16 | static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
 17 | 
 18 | static inline bool consume_char(char c, TSLexer *lexer) {
 19 |   if (lexer->lookahead != c) {
 20 |     return false;
 21 |   }
 22 | 
 23 |   consume(lexer);
 24 |   return true;
 25 | }
 26 | 
 27 | static inline uint8_t consume_and_count_char(char c, TSLexer *lexer) {
 28 |   uint8_t count = 0;
 29 |   while (lexer->lookahead == c) {
 30 |     ++count;
 31 |     consume(lexer);
 32 |   }
 33 |   return count;
 34 | }
 35 | 
 36 | static inline void skip_whitespaces(TSLexer *lexer) {
 37 |   while (iswspace(lexer->lookahead)) {
 38 |     skip(lexer);
 39 |   }
 40 | }
 41 | 
 42 | void *tree_sitter_lua_external_scanner_create() { return NULL; }
 43 | void tree_sitter_lua_external_scanner_destroy(void *payload) {}
 44 | 
 45 | char ending_char = 0;
 46 | uint8_t level_count = 0;
 47 | 
 48 | static inline void reset_state() {
 49 |   ending_char = 0;
 50 |   level_count = 0;
 51 | }
 52 | 
 53 | unsigned tree_sitter_lua_external_scanner_serialize(void *payload, char *buffer) {
 54 |   buffer[0] = ending_char;
 55 |   buffer[1] = level_count;
 56 |   return 2;
 57 | }
 58 | 
 59 | void tree_sitter_lua_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
 60 |   if (length == 0) return;
 61 |   ending_char = buffer[0];
 62 |   if (length == 1) return;
 63 |   level_count = buffer[1];
 64 | }
 65 | 
 66 | static bool scan_block_start(TSLexer *lexer) {
 67 |   if (consume_char('[', lexer)) {
 68 |     uint8_t level = consume_and_count_char('=', lexer);
 69 | 
 70 |     if (consume_char('[', lexer)) {
 71 |       level_count = level;
 72 |       return true;
 73 |     }
 74 |   }
 75 | 
 76 |   return false;
 77 | }
 78 | 
 79 | static bool scan_block_end(TSLexer *lexer) {
 80 |   if (consume_char(']', lexer)) {
 81 |     uint8_t level = consume_and_count_char('=', lexer);
 82 | 
 83 |     if (level_count == level && consume_char(']', lexer)) {
 84 |       return true;
 85 |     }
 86 |   }
 87 | 
 88 |   return false;
 89 | }
 90 | 
 91 | static bool scan_block_content(TSLexer *lexer) {
 92 |   while (lexer->lookahead != 0) {
 93 |     if (lexer->lookahead == ']') {
 94 |       lexer->mark_end(lexer);
 95 | 
 96 |       if (scan_block_end(lexer)) {
 97 |         return true;
 98 |       }
 99 |     } else {
100 |       consume(lexer);
101 |     }
102 |   }
103 | 
104 |   return false;
105 | }
106 | 
107 | static bool scan_comment_start(TSLexer *lexer) {
108 |   if (consume_char('-', lexer) && consume_char('-', lexer)) {
109 |     lexer->mark_end(lexer);
110 | 
111 |     if (scan_block_start(lexer)) {
112 |       lexer->mark_end(lexer);
113 |       lexer->result_symbol = BLOCK_COMMENT_START;
114 |       return true;
115 |     }
116 |   }
117 | 
118 |   return false;
119 | }
120 | 
121 | static bool scan_comment_content(TSLexer *lexer) {
122 |   if (ending_char == 0) { // block comment
123 |     if (scan_block_content(lexer)) {
124 |       lexer->result_symbol = BLOCK_COMMENT_CONTENT;
125 |       return true;
126 |     }
127 | 
128 |     return false;
129 |   }
130 | 
131 |   while (lexer->lookahead != 0) {
132 |     if (lexer->lookahead == ending_char) {
133 |       reset_state();
134 |       lexer->result_symbol = BLOCK_COMMENT_CONTENT;
135 |       return true;
136 |     }
137 | 
138 |     consume(lexer);
139 |   }
140 | 
141 |   return false;
142 | }
143 | 
144 | static bool scan_string_start(TSLexer *lexer) {
145 |   if (lexer->lookahead == '"' || lexer->lookahead == '\'') {
146 |     ending_char = lexer->lookahead;
147 |     consume(lexer);
148 |     return true;
149 |   }
150 | 
151 |   if (scan_block_start(lexer)) {
152 |     return true;
153 |   }
154 | 
155 |   return false;
156 | }
157 | 
158 | static bool scan_string_end(TSLexer *lexer) {
159 |   if (ending_char == 0) { // block string
160 |     return scan_block_end(lexer);
161 |   }
162 | 
163 |   if (consume_char(ending_char, lexer)) {
164 |     return true;
165 |   }
166 | 
167 |   return false;
168 | }
169 | 
170 | static bool scan_string_content(TSLexer *lexer) {
171 |   if (ending_char == 0) { // block string
172 |     return scan_block_content(lexer);
173 |   }
174 | 
175 |   while (lexer->lookahead != '\n' && lexer->lookahead != 0 && lexer->lookahead != ending_char) {
176 |     if (consume_char('\\', lexer) && consume_char('z', lexer)) {
177 |       while (iswspace(lexer->lookahead)) {
178 |         consume(lexer);
179 |       }
180 |       continue;
181 |     };
182 | 
183 |     if (lexer->lookahead == 0) {
184 |       return true;
185 |     }
186 | 
187 |     consume(lexer);
188 |   }
189 | 
190 |   return true;
191 | }
192 | 
193 | bool tree_sitter_lua_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
194 |   if (valid_symbols[STRING_END] && scan_string_end(lexer)) {
195 |     reset_state();
196 |     lexer->result_symbol = STRING_END;
197 |     return true;
198 |   }
199 | 
200 |   if (valid_symbols[STRING_CONTENT] && scan_string_content(lexer)) {
201 |     lexer->result_symbol = STRING_CONTENT;
202 |     return true;
203 |   }
204 | 
205 |   if (valid_symbols[BLOCK_COMMENT_END] && ending_char == 0 && scan_block_end(lexer)) {
206 |     reset_state();
207 |     lexer->result_symbol = BLOCK_COMMENT_END;
208 |     return true;
209 |   }
210 | 
211 |   if (valid_symbols[BLOCK_COMMENT_CONTENT] && scan_comment_content(lexer)) {
212 |     return true;
213 |   }
214 | 
215 |   skip_whitespaces(lexer);
216 | 
217 |   if (valid_symbols[STRING_START] && scan_string_start(lexer)) {
218 |     lexer->result_symbol = STRING_START;
219 |     return true;
220 |   }
221 | 
222 |   if (valid_symbols[BLOCK_COMMENT_START]) {
223 |     if (scan_comment_start(lexer)) {
224 |       return true;
225 |     }
226 |   }
227 | 
228 |   return false;
229 | }
230 | 


--------------------------------------------------------------------------------
/src/tree_sitter/parser.h:
--------------------------------------------------------------------------------
  1 | #ifndef TREE_SITTER_PARSER_H_
  2 | #define TREE_SITTER_PARSER_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include 
  9 | #include 
 10 | #include 
 11 | 
 12 | #define ts_builtin_sym_error ((TSSymbol)-1)
 13 | #define ts_builtin_sym_end 0
 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
 15 | 
 16 | #ifndef TREE_SITTER_API_H_
 17 | typedef uint16_t TSStateId;
 18 | typedef uint16_t TSSymbol;
 19 | typedef uint16_t TSFieldId;
 20 | typedef struct TSLanguage TSLanguage;
 21 | #endif
 22 | 
 23 | typedef struct {
 24 |   TSFieldId field_id;
 25 |   uint8_t child_index;
 26 |   bool inherited;
 27 | } TSFieldMapEntry;
 28 | 
 29 | typedef struct {
 30 |   uint16_t index;
 31 |   uint16_t length;
 32 | } TSFieldMapSlice;
 33 | 
 34 | typedef struct {
 35 |   bool visible;
 36 |   bool named;
 37 |   bool supertype;
 38 | } TSSymbolMetadata;
 39 | 
 40 | typedef struct TSLexer TSLexer;
 41 | 
 42 | struct TSLexer {
 43 |   int32_t lookahead;
 44 |   TSSymbol result_symbol;
 45 |   void (*advance)(TSLexer *, bool);
 46 |   void (*mark_end)(TSLexer *);
 47 |   uint32_t (*get_column)(TSLexer *);
 48 |   bool (*is_at_included_range_start)(const TSLexer *);
 49 |   bool (*eof)(const TSLexer *);
 50 |   void (*log)(const TSLexer *, const char *, ...);
 51 | };
 52 | 
 53 | typedef enum {
 54 |   TSParseActionTypeShift,
 55 |   TSParseActionTypeReduce,
 56 |   TSParseActionTypeAccept,
 57 |   TSParseActionTypeRecover,
 58 | } TSParseActionType;
 59 | 
 60 | typedef union {
 61 |   struct {
 62 |     uint8_t type;
 63 |     TSStateId state;
 64 |     bool extra;
 65 |     bool repetition;
 66 |   } shift;
 67 |   struct {
 68 |     uint8_t type;
 69 |     uint8_t child_count;
 70 |     TSSymbol symbol;
 71 |     int16_t dynamic_precedence;
 72 |     uint16_t production_id;
 73 |   } reduce;
 74 |   uint8_t type;
 75 | } TSParseAction;
 76 | 
 77 | typedef struct {
 78 |   uint16_t lex_state;
 79 |   uint16_t external_lex_state;
 80 | } TSLexMode;
 81 | 
 82 | typedef union {
 83 |   TSParseAction action;
 84 |   struct {
 85 |     uint8_t count;
 86 |     bool reusable;
 87 |   } entry;
 88 | } TSParseActionEntry;
 89 | 
 90 | typedef struct {
 91 |   int32_t start;
 92 |   int32_t end;
 93 | } TSCharacterRange;
 94 | 
 95 | struct TSLanguage {
 96 |   uint32_t version;
 97 |   uint32_t symbol_count;
 98 |   uint32_t alias_count;
 99 |   uint32_t token_count;
100 |   uint32_t external_token_count;
101 |   uint32_t state_count;
102 |   uint32_t large_state_count;
103 |   uint32_t production_id_count;
104 |   uint32_t field_count;
105 |   uint16_t max_alias_sequence_length;
106 |   const uint16_t *parse_table;
107 |   const uint16_t *small_parse_table;
108 |   const uint32_t *small_parse_table_map;
109 |   const TSParseActionEntry *parse_actions;
110 |   const char * const *symbol_names;
111 |   const char * const *field_names;
112 |   const TSFieldMapSlice *field_map_slices;
113 |   const TSFieldMapEntry *field_map_entries;
114 |   const TSSymbolMetadata *symbol_metadata;
115 |   const TSSymbol *public_symbol_map;
116 |   const uint16_t *alias_map;
117 |   const TSSymbol *alias_sequences;
118 |   const TSLexMode *lex_modes;
119 |   bool (*lex_fn)(TSLexer *, TSStateId);
120 |   bool (*keyword_lex_fn)(TSLexer *, TSStateId);
121 |   TSSymbol keyword_capture_token;
122 |   struct {
123 |     const bool *states;
124 |     const TSSymbol *symbol_map;
125 |     void *(*create)(void);
126 |     void (*destroy)(void *);
127 |     bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
128 |     unsigned (*serialize)(void *, char *);
129 |     void (*deserialize)(void *, const char *, unsigned);
130 |   } external_scanner;
131 |   const TSStateId *primary_state_ids;
132 | };
133 | 
134 | static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
135 |   uint32_t index = 0;
136 |   uint32_t size = len - index;
137 |   while (size > 1) {
138 |     uint32_t half_size = size / 2;
139 |     uint32_t mid_index = index + half_size;
140 |     TSCharacterRange *range = &ranges[mid_index];
141 |     if (lookahead >= range->start && lookahead <= range->end) {
142 |       return true;
143 |     } else if (lookahead > range->end) {
144 |       index = mid_index;
145 |     }
146 |     size -= half_size;
147 |   }
148 |   TSCharacterRange *range = &ranges[index];
149 |   return (lookahead >= range->start && lookahead <= range->end);
150 | }
151 | 
152 | /*
153 |  *  Lexer Macros
154 |  */
155 | 
156 | #ifdef _MSC_VER
157 | #define UNUSED __pragma(warning(suppress : 4101))
158 | #else
159 | #define UNUSED __attribute__((unused))
160 | #endif
161 | 
162 | #define START_LEXER()           \
163 |   bool result = false;          \
164 |   bool skip = false;            \
165 |   UNUSED                        \
166 |   bool eof = false;             \
167 |   int32_t lookahead;            \
168 |   goto start;                   \
169 |   next_state:                   \
170 |   lexer->advance(lexer, skip);  \
171 |   start:                        \
172 |   skip = false;                 \
173 |   lookahead = lexer->lookahead;
174 | 
175 | #define ADVANCE(state_value) \
176 |   {                          \
177 |     state = state_value;     \
178 |     goto next_state;         \
179 |   }
180 | 
181 | #define ADVANCE_MAP(...)                                              \
182 |   {                                                                   \
183 |     static const uint16_t map[] = { __VA_ARGS__ };                    \
184 |     for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) {  \
185 |       if (map[i] == lookahead) {                                      \
186 |         state = map[i + 1];                                           \
187 |         goto next_state;                                              \
188 |       }                                                               \
189 |     }                                                                 \
190 |   }
191 | 
192 | #define SKIP(state_value) \
193 |   {                       \
194 |     skip = true;          \
195 |     state = state_value;  \
196 |     goto next_state;      \
197 |   }
198 | 
199 | #define ACCEPT_TOKEN(symbol_value)     \
200 |   result = true;                       \
201 |   lexer->result_symbol = symbol_value; \
202 |   lexer->mark_end(lexer);
203 | 
204 | #define END_STATE() return result;
205 | 
206 | /*
207 |  *  Parse Table Macros
208 |  */
209 | 
210 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
211 | 
212 | #define STATE(id) id
213 | 
214 | #define ACTIONS(id) id
215 | 
216 | #define SHIFT(state_value)            \
217 |   {{                                  \
218 |     .shift = {                        \
219 |       .type = TSParseActionTypeShift, \
220 |       .state = (state_value)          \
221 |     }                                 \
222 |   }}
223 | 
224 | #define SHIFT_REPEAT(state_value)     \
225 |   {{                                  \
226 |     .shift = {                        \
227 |       .type = TSParseActionTypeShift, \
228 |       .state = (state_value),         \
229 |       .repetition = true              \
230 |     }                                 \
231 |   }}
232 | 
233 | #define SHIFT_EXTRA()                 \
234 |   {{                                  \
235 |     .shift = {                        \
236 |       .type = TSParseActionTypeShift, \
237 |       .extra = true                   \
238 |     }                                 \
239 |   }}
240 | 
241 | #define REDUCE(symbol_name, children, precedence, prod_id) \
242 |   {{                                                       \
243 |     .reduce = {                                            \
244 |       .type = TSParseActionTypeReduce,                     \
245 |       .symbol = symbol_name,                               \
246 |       .child_count = children,                             \
247 |       .dynamic_precedence = precedence,                    \
248 |       .production_id = prod_id                             \
249 |     },                                                     \
250 |   }}
251 | 
252 | #define RECOVER()                    \
253 |   {{                                 \
254 |     .type = TSParseActionTypeRecover \
255 |   }}
256 | 
257 | #define ACCEPT_INPUT()              \
258 |   {{                                \
259 |     .type = TSParseActionTypeAccept \
260 |   }}
261 | 
262 | #ifdef __cplusplus
263 | }
264 | #endif
265 | 
266 | #endif  // TREE_SITTER_PARSER_H_
267 | 


--------------------------------------------------------------------------------
/test/corpus/gh_issue_005.txt:
--------------------------------------------------------------------------------
  1 | ==================
  2 | Can do a large lua file
  3 | ==================
  4 | 
  5 | local m = {}
  6 | 
  7 | --- Test Header 1
  8 | function m.a01()
  9 |   return 0
 10 | end
 11 | 
 12 | return m
 13 | 
 14 | ---
 15 | 
 16 | (program
 17 |   (variable_declaration
 18 |     (local)
 19 |     (variable_declarator
 20 |       (identifier))
 21 |     (tableconstructor))
 22 |   (function_statement
 23 |     (emmy_documentation
 24 |       (emmy_header))
 25 |     (function_start)
 26 |     (function_name
 27 |       (identifier)
 28 |       (table_dot)
 29 |       (identifier))
 30 |     (function_body_paren)
 31 |     (function_body_paren)
 32 |     (function_body
 33 |       (return_statement
 34 |         (number)))
 35 |     (function_end))
 36 |   (module_return_statement
 37 |     (identifier)))
 38 | 
 39 | ==================
 40 | Can do a large lua file
 41 | ==================
 42 | 
 43 | local m = {}
 44 | 
 45 | --- Test Header 1
 46 | function m.a01()
 47 |   return 0
 48 | end
 49 | 
 50 | --- Test Header 2
 51 | function m.a02()
 52 |   return 0
 53 | end
 54 | 
 55 | --- Test Header 3
 56 | function m.a03()
 57 |   return 0
 58 | end
 59 | 
 60 | --- Test Header 4
 61 | function m.a04()
 62 |   return 0
 63 | end
 64 | 
 65 | --- Test Header 5
 66 | function m.a05()
 67 |   return 0
 68 | end
 69 | 
 70 | --- Test Header 6
 71 | function m.a06()
 72 |   return 0
 73 | end
 74 | 
 75 | --- Test Header 7
 76 | function m.a07()
 77 |   return 0
 78 | end
 79 | 
 80 | --- Test Header 8
 81 | function m.a08()
 82 |   return 0
 83 | end
 84 | 
 85 | --- Test Header 9
 86 | function m.a09()
 87 |   return 0
 88 | end
 89 | 
 90 | --- Test Header 10
 91 | function m.a10()
 92 |   return 0
 93 | end
 94 | 
 95 | --- Test Header 11
 96 | function m.a11()
 97 |   return 0
 98 | end
 99 | 
100 | --- Test Header 12
101 | function m.a12()
102 |   return 0
103 | end
104 | 
105 | --- Test Header 13
106 | function m.a13()
107 |   return 0
108 | end
109 | 
110 | --- Test Header 14
111 | function m.a14()
112 |   return 0
113 | end
114 | 
115 | --- Test Header 15
116 | function m.a15()
117 |   return 0
118 | end
119 | 
120 | --- Test Header 16
121 | function m.a16()
122 |   return 0
123 | end
124 | --- Test Header 17
125 | function m.a17()
126 |   return 0
127 | end
128 | 
129 | return m
130 | 
131 | ---
132 | 
133 | (program
134 |   (variable_declaration
135 |     (local)
136 |     (variable_declarator
137 |       (identifier))
138 |     (tableconstructor))
139 |   (function_statement
140 |     (emmy_documentation
141 |       (emmy_header))
142 |     (function_start)
143 |     (function_name
144 |       (identifier)
145 |       (table_dot)
146 |       (identifier))
147 |     (function_body_paren)
148 |     (function_body_paren)
149 |     (function_body
150 |       (return_statement
151 |         (number)))
152 |     (function_end))
153 |   (function_statement
154 |     (emmy_documentation
155 |       (emmy_header))
156 |     (function_start)
157 |     (function_name
158 |       (identifier)
159 |       (table_dot)
160 |       (identifier))
161 |     (function_body_paren)
162 |     (function_body_paren)
163 |     (function_body
164 |       (return_statement
165 |         (number)))
166 |     (function_end))
167 |   (function_statement
168 |     (emmy_documentation
169 |       (emmy_header))
170 |     (function_start)
171 |     (function_name
172 |       (identifier)
173 |       (table_dot)
174 |       (identifier))
175 |     (function_body_paren)
176 |     (function_body_paren)
177 |     (function_body
178 |       (return_statement
179 |         (number)))
180 |     (function_end))
181 |   (function_statement
182 |     (emmy_documentation
183 |       (emmy_header))
184 |     (function_start)
185 |     (function_name
186 |       (identifier)
187 |       (table_dot)
188 |       (identifier))
189 |     (function_body_paren)
190 |     (function_body_paren)
191 |     (function_body
192 |       (return_statement
193 |         (number)))
194 |     (function_end))
195 |   (function_statement
196 |     (emmy_documentation
197 |       (emmy_header))
198 |     (function_start)
199 |     (function_name
200 |       (identifier)
201 |       (table_dot)
202 |       (identifier))
203 |     (function_body_paren)
204 |     (function_body_paren)
205 |     (function_body
206 |       (return_statement
207 |         (number)))
208 |     (function_end))
209 |   (function_statement
210 |     (emmy_documentation
211 |       (emmy_header))
212 |     (function_start)
213 |     (function_name
214 |       (identifier)
215 |       (table_dot)
216 |       (identifier))
217 |     (function_body_paren)
218 |     (function_body_paren)
219 |     (function_body
220 |       (return_statement
221 |         (number)))
222 |     (function_end))
223 |   (function_statement
224 |     (emmy_documentation
225 |       (emmy_header))
226 |     (function_start)
227 |     (function_name
228 |       (identifier)
229 |       (table_dot)
230 |       (identifier))
231 |     (function_body_paren)
232 |     (function_body_paren)
233 |     (function_body
234 |       (return_statement
235 |         (number)))
236 |     (function_end))
237 |   (function_statement
238 |     (emmy_documentation
239 |       (emmy_header))
240 |     (function_start)
241 |     (function_name
242 |       (identifier)
243 |       (table_dot)
244 |       (identifier))
245 |     (function_body_paren)
246 |     (function_body_paren)
247 |     (function_body
248 |       (return_statement
249 |         (number)))
250 |     (function_end))
251 |   (function_statement
252 |     (emmy_documentation
253 |       (emmy_header))
254 |     (function_start)
255 |     (function_name
256 |       (identifier)
257 |       (table_dot)
258 |       (identifier))
259 |     (function_body_paren)
260 |     (function_body_paren)
261 |     (function_body
262 |       (return_statement
263 |         (number)))
264 |     (function_end))
265 |   (function_statement
266 |     (emmy_documentation
267 |       (emmy_header))
268 |     (function_start)
269 |     (function_name
270 |       (identifier)
271 |       (table_dot)
272 |       (identifier))
273 |     (function_body_paren)
274 |     (function_body_paren)
275 |     (function_body
276 |       (return_statement
277 |         (number)))
278 |     (function_end))
279 |   (function_statement
280 |     (emmy_documentation
281 |       (emmy_header))
282 |     (function_start)
283 |     (function_name
284 |       (identifier)
285 |       (table_dot)
286 |       (identifier))
287 |     (function_body_paren)
288 |     (function_body_paren)
289 |     (function_body
290 |       (return_statement
291 |         (number)))
292 |     (function_end))
293 |   (function_statement
294 |     (emmy_documentation
295 |       (emmy_header))
296 |     (function_start)
297 |     (function_name
298 |       (identifier)
299 |       (table_dot)
300 |       (identifier))
301 |     (function_body_paren)
302 |     (function_body_paren)
303 |     (function_body
304 |       (return_statement
305 |         (number)))
306 |     (function_end))
307 |   (function_statement
308 |     (emmy_documentation
309 |       (emmy_header))
310 |     (function_start)
311 |     (function_name
312 |       (identifier)
313 |       (table_dot)
314 |       (identifier))
315 |     (function_body_paren)
316 |     (function_body_paren)
317 |     (function_body
318 |       (return_statement
319 |         (number)))
320 |     (function_end))
321 |   (function_statement
322 |     (emmy_documentation
323 |       (emmy_header))
324 |     (function_start)
325 |     (function_name
326 |       (identifier)
327 |       (table_dot)
328 |       (identifier))
329 |     (function_body_paren)
330 |     (function_body_paren)
331 |     (function_body
332 |       (return_statement
333 |         (number)))
334 |     (function_end))
335 |   (function_statement
336 |     (emmy_documentation
337 |       (emmy_header))
338 |     (function_start)
339 |     (function_name
340 |       (identifier)
341 |       (table_dot)
342 |       (identifier))
343 |     (function_body_paren)
344 |     (function_body_paren)
345 |     (function_body
346 |       (return_statement
347 |         (number)))
348 |     (function_end))
349 |   (function_statement
350 |     (emmy_documentation
351 |       (emmy_header))
352 |     (function_start)
353 |     (function_name
354 |       (identifier)
355 |       (table_dot)
356 |       (identifier))
357 |     (function_body_paren)
358 |     (function_body_paren)
359 |     (function_body
360 |       (return_statement
361 |         (number)))
362 |     (function_end))
363 |   (function_statement
364 |     (emmy_documentation
365 |       (emmy_header))
366 |     (function_start)
367 |     (function_name
368 |       (identifier)
369 |       (table_dot)
370 |       (identifier))
371 |     (function_body_paren)
372 |     (function_body_paren)
373 |     (function_body
374 |       (return_statement
375 |         (number)))
376 |     (function_end))
377 |   (module_return_statement
378 |     (identifier)))
379 | 


--------------------------------------------------------------------------------
/lua/docgen/transformers.lua:
--------------------------------------------------------------------------------
  1 | local get_node_text = vim.treesitter.get_node_text
  2 | 
  3 | local log = require "docgen.log"
  4 | 
  5 | local for_each_child = function(node, cb)
  6 |   local named_children_count = node:named_child_count()
  7 |   for child = 0, named_children_count - 1 do
  8 |     local child_node = node:named_child(child)
  9 |     cb(child_node)
 10 |   end
 11 | end
 12 | 
 13 | ---@brief [[
 14 | --- Transforms generated tree from tree sitter -> metadata nodes that we can use for the project.
 15 | --- Structure of a program is: (TODO)
 16 | ---@brief ]]
 17 | ---@tag docgen-transformers
 18 | local transformers = {}
 19 | 
 20 | --- Takes any node and recursively transforms its children into the corresponding metadata required by |docgen|.
 21 | local call_transformer = function(accumulator, str, node, return_module)
 22 |   if transformers[node:type()] then
 23 |     return transformers[node:type()](accumulator, str, node, return_module)
 24 |   end
 25 | end
 26 | 
 27 | transformers._function = function(accumulator, str, node, return_module)
 28 |   if not return_module then
 29 |     return
 30 |   end
 31 | 
 32 |   local name_node = node:field("name")[1]
 33 |   local documentation_node = node:field("documentation")[1]
 34 | 
 35 |   assert(documentation_node, "Documentation must exist for this variable")
 36 |   assert(name_node, "Variable must have a name")
 37 | 
 38 |   local name = vim.trim(get_node_text(name_node, str))
 39 | 
 40 |   if not accumulator.functions then
 41 |     accumulator.functions = {}
 42 |   end
 43 |   if not accumulator.function_list then
 44 |     accumulator.function_list = {}
 45 |   end
 46 | 
 47 |   if not name:match(return_module .. "[.:].*") then
 48 |     return
 49 |   end
 50 | 
 51 |   -- If we already have the function skip
 52 |   -- Can happen now with function_statement and variable_declaration
 53 |   -- (but we still need to match both queries)
 54 |   if accumulator.functions[name] == nil then
 55 |     accumulator.functions[name] = {
 56 |       name = name,
 57 |       format = "function",
 58 |     }
 59 |     table.insert(accumulator.function_list, name)
 60 |     call_transformer(accumulator.functions[name], str, documentation_node)
 61 |   end
 62 | end
 63 | 
 64 | --- Transform briefs into the accumulator.brief
 65 | transformers.documentation_brief = function(accumulator, str, node)
 66 |   if not accumulator.brief then
 67 |     accumulator.brief = {}
 68 |   end
 69 | 
 70 |   local result = get_node_text(node, str)
 71 |   if result:sub(1, 1) == " " then
 72 |     result = result:sub(2)
 73 |   end
 74 | 
 75 |   table.insert(accumulator.brief, result)
 76 | end
 77 | 
 78 | transformers.documentation_class = function(accumulator, str, node)
 79 |   if not accumulator.classes then
 80 |     accumulator.classes = {}
 81 |   end
 82 |   if not accumulator.class_list then
 83 |     accumulator.class_list = {}
 84 |   end
 85 | 
 86 |   local class_node = node:named_child(0)
 87 | 
 88 |   local type_node = class_node:named_child(0)
 89 |   local parent_or_desc = class_node:named_child(1)
 90 |   local desc_node = class_node:named_child(2)
 91 | 
 92 |   local class = {}
 93 |   local name = get_node_text(type_node, str)
 94 |   class.name = name
 95 | 
 96 |   if parent_or_desc ~= nil then
 97 |     if desc_node == nil then
 98 |       class.desc = { get_node_text(parent_or_desc, str) }
 99 |     else
100 |       class.parent = get_node_text(parent_or_desc, str)
101 |       class.desc = { get_node_text(desc_node, str) }
102 |     end
103 |   else
104 |     class.desc = {}
105 |   end
106 | 
107 |   class.fields = {}
108 |   class.field_list = {}
109 | 
110 |   local named_children_count = node:named_child_count()
111 |   for child = 1, named_children_count - 1 do
112 |     transformers.emmy_field(class, str, node:named_child(child))
113 |   end
114 | 
115 |   accumulator.classes[name] = class
116 |   table.insert(accumulator.class_list, name)
117 | end
118 | 
119 | transformers.documentation_tag = function(accumulator, str, node)
120 |   accumulator.tag = get_node_text(node, str)
121 | end
122 | 
123 | transformers.function_statement = transformers._function
124 | transformers.variable_declaration = transformers._function
125 | 
126 | transformers.documentation_command = function(accumulator, str, node)
127 |   accumulator.commands = accumulator.commands or {}
128 | 
129 |   local usage = vim.trim(get_node_text(node:field("usage")[1], str))
130 |   usage = usage:gsub("^:", "")
131 | 
132 |   local name = vim.split(usage, " ")[1]
133 |   table.insert(accumulator.commands, {
134 |     name = name,
135 |     usage = ":" .. usage,
136 |     documentation = vim.tbl_map(function(child)
137 |       return get_node_text(child, str)
138 |     end, node:field "documentation"),
139 |   })
140 | end
141 | 
142 | transformers.emmy_documentation = function(accumulator, str, node)
143 |   accumulator.class = {}
144 | 
145 |   accumulator.fields = {}
146 |   accumulator.field_list = {}
147 | 
148 |   accumulator.parameters = {}
149 |   accumulator.parameter_list = {}
150 | 
151 |   log.trace("Accumulator:", accumulator)
152 | 
153 |   for_each_child(node, function(child_node)
154 |     call_transformer(accumulator, str, child_node)
155 |   end)
156 | end
157 | 
158 | transformers.emmy_header = function(accumulator, str, node)
159 |   return transformers.emmy_comment(accumulator, str, node)
160 | end
161 | 
162 | transformers.emmy_comment = function(accumulator, str, node)
163 |   -- TODO: Make this not ugly
164 |   local text = get_node_text(node, str)
165 | 
166 |   local raw_lines = vim.split(text, "\n")
167 |   if raw_lines[1] == "" then
168 |     table.remove(raw_lines, 1)
169 |   end
170 | 
171 |   if not accumulator.description then
172 |     accumulator.description = {}
173 |   end
174 | 
175 |   for _, line in ipairs(raw_lines) do
176 |     local start, finish = line:find "^%s*---"
177 |     if start then
178 |       line = line:sub(finish + 3)
179 |     end
180 | 
181 |     if line:sub(1, 1) == " " then
182 |       line = line:sub(2)
183 |     end
184 | 
185 |     table.insert(accumulator.description, line)
186 |   end
187 | end
188 | 
189 | transformers.emmy_class = function(accumulator, str, node)
190 |   local type_node = node:named_child(0)
191 |   local parent_or_desc = node:named_child(1)
192 |   local desc_node = node:named_child(2)
193 | 
194 |   local name = get_node_text(type_node, str)
195 |   accumulator.class.name = name
196 | 
197 |   if desc_node == nil then
198 |     accumulator.class.desc = { get_node_text(parent_or_desc, str) }
199 |   else
200 |     accumulator.class.parent = get_node_text(parent_or_desc, str)
201 |     accumulator.class.desc = { get_node_text(desc_node, str) }
202 |   end
203 | end
204 | 
205 | transformers.emmy_field = function(accumulator, str, node)
206 |   local name_node = node:field("name")[1]
207 |   assert(name_node, "Field must have a name")
208 | 
209 |   local types = {}
210 |   local desc
211 |   for _, child in ipairs(node:field "type") do
212 |     local child_type = child:type()
213 |     if child_type == "emmy_type" or child_type == "identifier" then
214 |       table.insert(types, get_node_text(child, str))
215 |     elseif child_type == "|" then
216 |       -- do nothing
217 |     else
218 |       print(string.format("[docgen] [Error]: We should not be here // emmy_field #2 (%s)", child:type()))
219 |     end
220 |   end
221 | 
222 |   for _, child in ipairs(node:field "description") do
223 |     if desc ~= nil then
224 |       print "[docgen] [Error]: We should not be here // emmy_field #1"
225 |     else
226 |       desc = get_node_text(child, str)
227 |     end
228 |   end
229 | 
230 |   local name = get_node_text(name_node, str)
231 | 
232 |   accumulator.fields[name] = {
233 |     name = name,
234 |     type = types,
235 |     description = { desc },
236 |   }
237 | 
238 |   if not vim.tbl_contains(accumulator.field_list, name) then
239 |     table.insert(accumulator.field_list, name)
240 |   end
241 | end
242 | 
243 | transformers.emmy_parameter = function(accumulator, str, node)
244 |   local name_node = node:named_child(0)
245 |   assert(name_node, "Parameters must have a name")
246 | 
247 |   local types = {}
248 |   local desc
249 |   for i = 1, node:named_child_count() - 1 do
250 |     if node:named_child(i):type() == "emmy_type" then
251 |       table.insert(types, get_node_text(node:named_child(i), str))
252 |     elseif node:named_child(i):type() == "parameter_description" then
253 |       if desc ~= nil then
254 |         print "[docgen] [Error]: We should not be here // emmy_parameter #1"
255 |       else
256 |         desc = get_node_text(node:named_child(i), str)
257 |       end
258 |     else
259 |       print "[docgen] [Error]: We should not be here // emmy_parazn #2"
260 |     end
261 |   end
262 | 
263 |   local name = get_node_text(name_node, str)
264 | 
265 |   accumulator.parameters[name] = {
266 |     name = name,
267 |     type = types,
268 |     description = { desc },
269 |   }
270 | 
271 |   if not vim.tbl_contains(accumulator.parameter_list, name) then
272 |     table.insert(accumulator.parameter_list, name)
273 |   end
274 | end
275 | 
276 | local create_emmy_type_function = function(identifier)
277 |   return function(accumulator, str, node)
278 |     if not accumulator[identifier] then
279 |       accumulator[identifier] = {}
280 |     end
281 | 
282 |     local text = vim.trim(get_node_text(node, str))
283 |     text = text:gsub(string.format("---@%s ", identifier), "")
284 | 
285 |     table.insert(accumulator[identifier], text)
286 |   end
287 | end
288 | 
289 | transformers.emmy_return = create_emmy_type_function "return"
290 | transformers.emmy_see = create_emmy_type_function "see"
291 | transformers.emmy_todo = create_emmy_type_function "todo"
292 | transformers.emmy_usage = create_emmy_type_function "usage"
293 | transformers.emmy_varargs = create_emmy_type_function "varargs"
294 | 
295 | transformers.emmy_eval = function(accumulator, str, node)
296 |   local ok, result = pcall(loadstring("return " .. get_node_text(node, str)))
297 | 
298 |   if ok then
299 |     if type(result) == "table" then
300 |       for k, v in pairs(result) do
301 |         -- assert(type(v) == 'string', "Not implemented to be nested tables yet." .. vim.inspect(accumulator))
302 |         -- local current_accumulator = accumulator
303 |         -- if type(v) == 'table' then
304 |         --   -- curre
305 |         -- end
306 | 
307 |         if not accumulator[k] then
308 |           accumulator[k] = {}
309 |         end
310 | 
311 |         table.insert(accumulator[k], v)
312 |       end
313 |     end
314 |   else
315 |     print("ERR:", result)
316 |   end
317 | end
318 | 
319 | transformers.documentation_config = function(accumulator, str, node)
320 |   local ok, result = pcall(loadstring("return " .. get_node_text(node, str)))
321 | 
322 |   if ok then
323 |     if type(result) == "table" then
324 |       if not accumulator then
325 |         accumulator = {}
326 |       end
327 | 
328 |       accumulator["config"] = result
329 |     end
330 |   else
331 |     print("ERR:", result)
332 |   end
333 | end
334 | 
335 | return call_transformer
336 | 


--------------------------------------------------------------------------------
/test/corpus/statements.txt:
--------------------------------------------------------------------------------
  1 | 
  2 | ==================
  3 | Nil is handled
  4 | ==================
  5 | 
  6 | x = nil
  7 | 
  8 | ---
  9 | 
 10 | (program
 11 |   (variable_declaration
 12 |     (variable_declarator (identifier))
 13 |     (nil)))
 14 | 
 15 | ==================
 16 | true is handled
 17 | ==================
 18 | 
 19 | x = true
 20 | 
 21 | ---
 22 | 
 23 | (program
 24 |   (variable_declaration
 25 |     (variable_declarator (identifier))
 26 |     (boolean)))
 27 | 
 28 | ==================
 29 | false is handled
 30 | ==================
 31 | 
 32 | x = false
 33 | 
 34 | ---
 35 | 
 36 | (program
 37 |   (variable_declaration
 38 |     (variable_declarator (identifier))
 39 |     (boolean)))
 40 | 
 41 | ==================
 42 | Simple assignment
 43 | ==================
 44 | 
 45 | x = 1
 46 | 
 47 | ---
 48 | 
 49 | (program
 50 |   (variable_declaration
 51 |     (variable_declarator (identifier))
 52 |     (number)))
 53 | 
 54 | ==================
 55 | Simple assignment from variable
 56 | ==================
 57 | 
 58 | x = y
 59 | 
 60 | ---
 61 | 
 62 | (program
 63 |   (variable_declaration
 64 |     (variable_declarator (identifier))
 65 |     (identifier)))
 66 | 
 67 | ==================
 68 | Accepts addition
 69 | ==================
 70 | 
 71 | x = 1 + 2
 72 | 
 73 | ---
 74 | 
 75 | (program
 76 |   (variable_declaration
 77 |     (variable_declarator (identifier))
 78 |     (binary_operation (number) (number))))
 79 | 
 80 | ==================
 81 | Accepts addition of variables
 82 | ==================
 83 | 
 84 | x = y + z
 85 | 
 86 | ---
 87 | 
 88 | (program
 89 |   (variable_declaration
 90 |     (variable_declarator (identifier))
 91 |     (binary_operation (identifier) (identifier))))
 92 | 
 93 | ==================
 94 | Can make local variables
 95 | ==================
 96 | 
 97 | local x = 2
 98 | 
 99 | ---
100 | 
101 | (program
102 |   (variable_declaration
103 |     (local)
104 |     (variable_declarator (identifier))
105 |     (number)))
106 | 
107 | ==================
108 | Can do multiple sets
109 | ==================
110 | 
111 | x, y, z = 1, 2, 3
112 | 
113 | ---
114 | 
115 | (program
116 |   (variable_declaration
117 |     (variable_declarator (identifier))
118 |     (variable_declarator (identifier))
119 |     (variable_declarator (identifier))
120 |     (number)
121 |     (number)
122 |     (number)))
123 | 
124 | ==================
125 | Can make an empty table
126 | ==================
127 | 
128 | t = {}
129 | 
130 | ---
131 | 
132 | (program
133 |   (variable_declaration
134 |     (variable_declarator (identifier))
135 |     (tableconstructor)))
136 | 
137 | ==================
138 | Can make a table with a number
139 | ==================
140 | 
141 | t = { 1 }
142 | 
143 | ---
144 | 
145 | (program
146 |   (variable_declaration
147 |     name: (variable_declarator (identifier))
148 |     value: (tableconstructor
149 |             (fieldlist (field value: (number))))))
150 | 
151 | ==================
152 | Can make a table with a list of numbers
153 | ==================
154 | 
155 | t = { 1, 2, 3 }
156 | 
157 | ---
158 | 
159 | (program
160 |   (variable_declaration
161 |     name: (variable_declarator (identifier))
162 |     value: (tableconstructor
163 |             (fieldlist
164 |               (field value: (number))
165 |               (field value: (number))
166 |               (field value: (number))))))
167 | 
168 | ==================
169 | Can make a table with keys
170 | ==================
171 | 
172 | t = { x = 1, y = 2 }
173 | 
174 | ---
175 | 
176 | (program
177 |   (variable_declaration
178 |     name: (variable_declarator (identifier))
179 |     value: (tableconstructor
180 |             (fieldlist
181 |               (field
182 |                 name: (identifier)
183 |                 value: (number))
184 |               (field
185 |                 name: (identifier)
186 |                 value: (number))))))
187 | 
188 | ==================
189 | Can make a table with expression keys
190 | ==================
191 | 
192 | t = { [x] = 1, ["y"] = 2 }
193 | 
194 | ---
195 | 
196 | (program
197 |   (variable_declaration
198 |     name: (variable_declarator (identifier))
199 |     value: (tableconstructor
200 |             (fieldlist
201 |               (field
202 |                 field_left_bracket: (field_left_bracket)
203 |                 key: (identifier)
204 |                 field_right_bracket: (field_right_bracket)
205 |                 value: (number))
206 |               (field
207 |                 field_left_bracket: (field_left_bracket)
208 |                 key: (string)
209 |                 field_right_bracket: (field_right_bracket)
210 |                 value: (number))))))
211 |         
212 | 
213 | ==================
214 | Can make a table with some expressions, some nothing and some keys
215 | ==================
216 | 
217 | t = { 1, 2, x = 1, ["y"] = 2 }
218 | 
219 | ---
220 | 
221 | (program
222 |   (variable_declaration
223 |     name: (variable_declarator (identifier))
224 |     value: (tableconstructor
225 |             (fieldlist
226 |               (field value: (number))
227 |               (field value: (number))
228 |               (field
229 |                 name: (identifier)
230 |                 value: (number))
231 |               (field
232 |                 field_left_bracket: (field_left_bracket)
233 |                 key: (string)
234 |                 field_right_bracket: (field_right_bracket)
235 |                 value: (number))))))
236 |         
237 | 
238 | ==================
239 | Can assign a function result
240 | ==================
241 | 
242 | foo = my_func()
243 | 
244 | ---
245 | 
246 | (program
247 |   (variable_declaration
248 |     name: (variable_declarator (identifier))
249 |     value: (function_call
250 |             prefix: (identifier)
251 |             (function_call_paren)
252 |             (function_call_paren))))
253 |       
254 | 
255 | ==================
256 | Can assign a function with params
257 | ==================
258 | 
259 | foo = my_func(x, 2, "3")
260 | 
261 | ---
262 | 
263 | (program
264 |   (variable_declaration
265 |     name: (variable_declarator (identifier))
266 |     value: (function_call
267 |             prefix: (identifier)
268 |             (function_call_paren)
269 |             args: (function_arguments
270 |                     (identifier)
271 |                     (number)
272 |                     (string))
273 |             (function_call_paren))))
274 |       
275 | 
276 | ==================
277 | Can call a function with a string value
278 | ==================
279 | 
280 | foo = my_func "hello world"
281 | 
282 | ---
283 | 
284 | (program
285 |   (variable_declaration
286 |     name: (variable_declarator (identifier))
287 |     value: (function_call
288 |             prefix: (identifier)
289 |             args: (string_argument))))
290 | 
291 | ==================
292 | Can call a function with a table value
293 | ==================
294 | 
295 | foo = my_func {}
296 | 
297 | ---
298 | 
299 | (program
300 |   (variable_declaration
301 |     name: (variable_declarator (identifier))
302 |     value: (function_call
303 |             prefix: (identifier)
304 |             args: (table_argument))))
305 | 
306 | ==================
307 | Can call a function returned by a function
308 | ==================
309 | 
310 | foo = my_func()()
311 | 
312 | ---
313 | 
314 | (program
315 |   (variable_declaration
316 |     name: (variable_declarator (identifier))
317 |     value: (function_call
318 |             prefix: (function_call
319 |                       prefix: (identifier)
320 |                       (function_call_paren)
321 |                       (function_call_paren))
322 |             (function_call_paren)
323 |             (function_call_paren))))
324 | 
325 | ==================
326 | Can call a table function // TODO: Decide if it makes sense with identifiers
327 | ==================
328 | 
329 | foo = my_table.func()
330 | 
331 | ---
332 | 
333 | (program
334 |   (variable_declaration
335 |     (variable_declarator (identifier))
336 |     (function_call
337 |       (identifier) (identifier)
338 |       (function_call_paren)
339 |       (function_call_paren))))
340 |       
341 | 
342 | ==================
343 | Can call a table function with lots of ids
344 | ==================
345 | 
346 | foo = my_table.func.x.y()
347 | 
348 | ---
349 | 
350 | (program
351 |   (variable_declaration
352 |     (variable_declarator (identifier))
353 |     (function_call
354 |       (identifier) (identifier) (identifier) (identifier)
355 |       (function_call_paren)
356 |       (function_call_paren))))
357 |       
358 | 
359 | ==================
360 | Can set namelist
361 | ==================
362 | 
363 | local x, y, z = 1, 2, 3
364 | 
365 | ---
366 | 
367 | (program
368 |   (variable_declaration
369 |     (local)
370 |     name: (variable_declarator (identifier))
371 |     name: (variable_declarator (identifier))
372 |     name: (variable_declarator (identifier))
373 |     value: (number)
374 |     value: (number)
375 |     value: (number)))
376 | 
377 | ==================
378 | Can do comments
379 | ==================
380 | 
381 | -- Comment
382 | 
383 | 
384 | ---
385 | 
386 | (program
387 |   (comment))
388 | 
389 | ==================
390 | Multi lines
391 | ==================
392 | 
393 | x = 1
394 | y = 2
395 | 
396 | ---
397 | 
398 | (program
399 |   (variable_declaration (variable_declarator (identifier)) (number))
400 |   (variable_declaration (variable_declarator (identifier)) (number)))
401 |   
402 | 
403 | ==================
404 | Can do comments at the end of a line
405 | ==================
406 | 
407 | x = nil -- Comment
408 | 
409 | ---
410 | 
411 | (program
412 |   (variable_declaration
413 |     (variable_declarator (identifier))
414 |     (nil))
415 |   (comment))
416 | 
417 | ==================
418 | Can handle parens around an expression
419 | ==================
420 | 
421 | x = (1)
422 | 
423 | ---
424 | 
425 | (program
426 |   (variable_declaration (variable_declarator (identifier)) (left_paren) (number) (right_paren)))
427 | 
428 | ==================
429 | Multi declaration
430 | ==================
431 | 
432 | local x, y = 1, 2
433 | 
434 | ---
435 | 
436 | (program
437 |   (variable_declaration
438 |     (local)
439 |     (variable_declarator
440 |       (identifier))
441 |     (variable_declarator
442 |       (identifier))
443 |     (number)
444 |     (number)))
445 | 
446 | ==================
447 | Can do multi-line local with nothing else happening
448 | ==================
449 | 
450 | local documentation_node, name_node
451 | local x = 1
452 | 
453 | ---
454 | 
455 | (program
456 |   (variable_declaration
457 |     (local)
458 |     (variable_declarator
459 |       (identifier))
460 |     (variable_declarator
461 |       (identifier)))
462 |   (variable_declaration
463 |     (local)
464 |     (variable_declarator
465 |       (identifier))
466 |     (number)))
467 | 
468 | ==================
469 | Can do single statments
470 | ==================
471 | 
472 | local x
473 | x = {}
474 | 
475 | ---
476 | 
477 | (program
478 |   (variable_declaration
479 |     (local)
480 |     (variable_declarator
481 |       (identifier)))
482 |   (variable_declaration
483 |     (variable_declarator
484 |       (identifier))
485 |     (tableconstructor)))
486 | 
487 | ==================
488 | Can do floats
489 | ==================
490 | 
491 | local x = 0.8
492 | 
493 | ---
494 | 
495 | (program (variable_declaration (local) (variable_declarator (identifier)) (number)))
496 | 


--------------------------------------------------------------------------------
/src/tree_sitter/array.h:
--------------------------------------------------------------------------------
  1 | #ifndef TREE_SITTER_ARRAY_H_
  2 | #define TREE_SITTER_ARRAY_H_
  3 | 
  4 | #ifdef __cplusplus
  5 | extern "C" {
  6 | #endif
  7 | 
  8 | #include "./alloc.h"
  9 | 
 10 | #include 
 11 | #include 
 12 | #include 
 13 | #include 
 14 | #include 
 15 | 
 16 | #ifdef _MSC_VER
 17 | #pragma warning(disable : 4101)
 18 | #elif defined(__GNUC__) || defined(__clang__)
 19 | #pragma GCC diagnostic push
 20 | #pragma GCC diagnostic ignored "-Wunused-variable"
 21 | #endif
 22 | 
 23 | #define Array(T)       \
 24 |   struct {             \
 25 |     T *contents;       \
 26 |     uint32_t size;     \
 27 |     uint32_t capacity; \
 28 |   }
 29 | 
 30 | /// Initialize an array.
 31 | #define array_init(self) \
 32 |   ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
 33 | 
 34 | /// Create an empty array.
 35 | #define array_new() \
 36 |   { NULL, 0, 0 }
 37 | 
 38 | /// Get a pointer to the element at a given `index` in the array.
 39 | #define array_get(self, _index) \
 40 |   (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
 41 | 
 42 | /// Get a pointer to the first element in the array.
 43 | #define array_front(self) array_get(self, 0)
 44 | 
 45 | /// Get a pointer to the last element in the array.
 46 | #define array_back(self) array_get(self, (self)->size - 1)
 47 | 
 48 | /// Clear the array, setting its size to zero. Note that this does not free any
 49 | /// memory allocated for the array's contents.
 50 | #define array_clear(self) ((self)->size = 0)
 51 | 
 52 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
 53 | /// less than the array's current capacity, this function has no effect.
 54 | #define array_reserve(self, new_capacity) \
 55 |   _array__reserve((Array *)(self), array_elem_size(self), new_capacity)
 56 | 
 57 | /// Free any memory allocated for this array. Note that this does not free any
 58 | /// memory allocated for the array's contents.
 59 | #define array_delete(self) _array__delete((Array *)(self))
 60 | 
 61 | /// Push a new `element` onto the end of the array.
 62 | #define array_push(self, element)                            \
 63 |   (_array__grow((Array *)(self), 1, array_elem_size(self)), \
 64 |    (self)->contents[(self)->size++] = (element))
 65 | 
 66 | /// Increase the array's size by `count` elements.
 67 | /// New elements are zero-initialized.
 68 | #define array_grow_by(self, count) \
 69 |   do { \
 70 |     if ((count) == 0) break; \
 71 |     _array__grow((Array *)(self), count, array_elem_size(self)); \
 72 |     memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
 73 |     (self)->size += (count); \
 74 |   } while (0)
 75 | 
 76 | /// Append all elements from one array to the end of another.
 77 | #define array_push_all(self, other)                                       \
 78 |   array_extend((self), (other)->size, (other)->contents)
 79 | 
 80 | /// Append `count` elements to the end of the array, reading their values from the
 81 | /// `contents` pointer.
 82 | #define array_extend(self, count, contents)                    \
 83 |   _array__splice(                                               \
 84 |     (Array *)(self), array_elem_size(self), (self)->size, \
 85 |     0, count,  contents                                        \
 86 |   )
 87 | 
 88 | /// Remove `old_count` elements from the array starting at the given `index`. At
 89 | /// the same index, insert `new_count` new elements, reading their values from the
 90 | /// `new_contents` pointer.
 91 | #define array_splice(self, _index, old_count, new_count, new_contents)  \
 92 |   _array__splice(                                                       \
 93 |     (Array *)(self), array_elem_size(self), _index,                \
 94 |     old_count, new_count, new_contents                                 \
 95 |   )
 96 | 
 97 | /// Insert one `element` into the array at the given `index`.
 98 | #define array_insert(self, _index, element) \
 99 |   _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
100 | 
101 | /// Remove one element from the array at the given `index`.
102 | #define array_erase(self, _index) \
103 |   _array__erase((Array *)(self), array_elem_size(self), _index)
104 | 
105 | /// Pop the last element off the array, returning the element by value.
106 | #define array_pop(self) ((self)->contents[--(self)->size])
107 | 
108 | /// Assign the contents of one array to another, reallocating if necessary.
109 | #define array_assign(self, other) \
110 |   _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
111 | 
112 | /// Swap one array with another
113 | #define array_swap(self, other) \
114 |   _array__swap((Array *)(self), (Array *)(other))
115 | 
116 | /// Get the size of the array contents
117 | #define array_elem_size(self) (sizeof *(self)->contents)
118 | 
119 | /// Search a sorted array for a given `needle` value, using the given `compare`
120 | /// callback to determine the order.
121 | ///
122 | /// If an existing element is found to be equal to `needle`, then the `index`
123 | /// out-parameter is set to the existing value's index, and the `exists`
124 | /// out-parameter is set to true. Otherwise, `index` is set to an index where
125 | /// `needle` should be inserted in order to preserve the sorting, and `exists`
126 | /// is set to false.
127 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \
128 |   _array__search_sorted(self, 0, compare, , needle, _index, _exists)
129 | 
130 | /// Search a sorted array for a given `needle` value, using integer comparisons
131 | /// of a given struct field (specified with a leading dot) to determine the order.
132 | ///
133 | /// See also `array_search_sorted_with`.
134 | #define array_search_sorted_by(self, field, needle, _index, _exists) \
135 |   _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
136 | 
137 | /// Insert a given `value` into a sorted array, using the given `compare`
138 | /// callback to determine the order.
139 | #define array_insert_sorted_with(self, compare, value) \
140 |   do { \
141 |     unsigned _index, _exists; \
142 |     array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
143 |     if (!_exists) array_insert(self, _index, value); \
144 |   } while (0)
145 | 
146 | /// Insert a given `value` into a sorted array, using integer comparisons of
147 | /// a given struct field (specified with a leading dot) to determine the order.
148 | ///
149 | /// See also `array_search_sorted_by`.
150 | #define array_insert_sorted_by(self, field, value) \
151 |   do { \
152 |     unsigned _index, _exists; \
153 |     array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
154 |     if (!_exists) array_insert(self, _index, value); \
155 |   } while (0)
156 | 
157 | // Private
158 | 
159 | typedef Array(void) Array;
160 | 
161 | /// This is not what you're looking for, see `array_delete`.
162 | static inline void _array__delete(Array *self) {
163 |   if (self->contents) {
164 |     ts_free(self->contents);
165 |     self->contents = NULL;
166 |     self->size = 0;
167 |     self->capacity = 0;
168 |   }
169 | }
170 | 
171 | /// This is not what you're looking for, see `array_erase`.
172 | static inline void _array__erase(Array *self, size_t element_size,
173 |                                 uint32_t index) {
174 |   assert(index < self->size);
175 |   char *contents = (char *)self->contents;
176 |   memmove(contents + index * element_size, contents + (index + 1) * element_size,
177 |           (self->size - index - 1) * element_size);
178 |   self->size--;
179 | }
180 | 
181 | /// This is not what you're looking for, see `array_reserve`.
182 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
183 |   if (new_capacity > self->capacity) {
184 |     if (self->contents) {
185 |       self->contents = ts_realloc(self->contents, new_capacity * element_size);
186 |     } else {
187 |       self->contents = ts_malloc(new_capacity * element_size);
188 |     }
189 |     self->capacity = new_capacity;
190 |   }
191 | }
192 | 
193 | /// This is not what you're looking for, see `array_assign`.
194 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
195 |   _array__reserve(self, element_size, other->size);
196 |   self->size = other->size;
197 |   memcpy(self->contents, other->contents, self->size * element_size);
198 | }
199 | 
200 | /// This is not what you're looking for, see `array_swap`.
201 | static inline void _array__swap(Array *self, Array *other) {
202 |   Array swap = *other;
203 |   *other = *self;
204 |   *self = swap;
205 | }
206 | 
207 | /// This is not what you're looking for, see `array_push` or `array_grow_by`.
208 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
209 |   uint32_t new_size = self->size + count;
210 |   if (new_size > self->capacity) {
211 |     uint32_t new_capacity = self->capacity * 2;
212 |     if (new_capacity < 8) new_capacity = 8;
213 |     if (new_capacity < new_size) new_capacity = new_size;
214 |     _array__reserve(self, element_size, new_capacity);
215 |   }
216 | }
217 | 
218 | /// This is not what you're looking for, see `array_splice`.
219 | static inline void _array__splice(Array *self, size_t element_size,
220 |                                  uint32_t index, uint32_t old_count,
221 |                                  uint32_t new_count, const void *elements) {
222 |   uint32_t new_size = self->size + new_count - old_count;
223 |   uint32_t old_end = index + old_count;
224 |   uint32_t new_end = index + new_count;
225 |   assert(old_end <= self->size);
226 | 
227 |   _array__reserve(self, element_size, new_size);
228 | 
229 |   char *contents = (char *)self->contents;
230 |   if (self->size > old_end) {
231 |     memmove(
232 |       contents + new_end * element_size,
233 |       contents + old_end * element_size,
234 |       (self->size - old_end) * element_size
235 |     );
236 |   }
237 |   if (new_count > 0) {
238 |     if (elements) {
239 |       memcpy(
240 |         (contents + index * element_size),
241 |         elements,
242 |         new_count * element_size
243 |       );
244 |     } else {
245 |       memset(
246 |         (contents + index * element_size),
247 |         0,
248 |         new_count * element_size
249 |       );
250 |     }
251 |   }
252 |   self->size += new_count - old_count;
253 | }
254 | 
255 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`.
256 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
257 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
258 |   do { \
259 |     *(_index) = start; \
260 |     *(_exists) = false; \
261 |     uint32_t size = (self)->size - *(_index); \
262 |     if (size == 0) break; \
263 |     int comparison; \
264 |     while (size > 1) { \
265 |       uint32_t half_size = size / 2; \
266 |       uint32_t mid_index = *(_index) + half_size; \
267 |       comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
268 |       if (comparison <= 0) *(_index) = mid_index; \
269 |       size -= half_size; \
270 |     } \
271 |     comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
272 |     if (comparison == 0) *(_exists) = true; \
273 |     else if (comparison < 0) *(_index) += 1; \
274 |   } while (0)
275 | 
276 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
277 | /// parameter by reference in order to work with the generic sorting function above.
278 | #define _compare_int(a, b) ((int)*(a) - (int)(b))
279 | 
280 | #ifdef _MSC_VER
281 | #pragma warning(default : 4101)
282 | #elif defined(__GNUC__) || defined(__clang__)
283 | #pragma GCC diagnostic pop
284 | #endif
285 | 
286 | #ifdef __cplusplus
287 | }
288 | #endif
289 | 
290 | #endif  // TREE_SITTER_ARRAY_H_
291 | 


--------------------------------------------------------------------------------
/lua/nlsp/rpc.lua:
--------------------------------------------------------------------------------
  1 | local vim = vim
  2 | local uv = vim.loop
  3 | local log = require "nlsp.log"
  4 | local protocol = require "vim.lsp.protocol"
  5 | local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
  6 | 
  7 | -- TODO replace with a better implementation.
  8 | 
  9 | local M = {}
 10 | 
 11 | --@private
 12 | --- Encodes to JSON.
 13 | ---
 14 | --@param data (table) Data to encode
 15 | --@returns (string) Encoded object
 16 | local function json_encode(data)
 17 |   local status, result = pcall(vim.fn.json_encode, data)
 18 |   if status then
 19 |     return true, result
 20 |   else
 21 |     return nil, result
 22 |   end
 23 | end
 24 | --@private
 25 | --- Decodes from JSON.
 26 | ---
 27 | --@param data (string) Data to decode
 28 | --@returns (table) Decoded JSON object
 29 | local function json_decode(data)
 30 |   local status, result = pcall(vim.fn.json_decode, data)
 31 |   if status then
 32 |     return true, result
 33 |   else
 34 |     return nil, result
 35 |   end
 36 | end
 37 | 
 38 | local function format_message_with_content_length(encoded_message)
 39 |   local message = table.concat {
 40 |     "Content-Length: ",
 41 |     tostring(#encoded_message),
 42 |     "\r\n\r\n",
 43 |     encoded_message,
 44 |   }
 45 | 
 46 |   log.trace(message)
 47 |   return message
 48 | end
 49 | 
 50 | function M.read_message()
 51 |   local line = io.read "*l"
 52 |   local length = line:lower():match "content%-length:%s*(%d+)"
 53 |   return json_decode(io.read(2 + length):sub(2))
 54 | end
 55 | 
 56 | function M.send_message(payload, pipe)
 57 |   if not pipe then
 58 |     pipe = io.stdout
 59 |   end
 60 | 
 61 |   log.debug("rpc.send.payload", payload)
 62 |   local ok, encoded = json_encode(payload)
 63 |   if ok then
 64 |     pipe:write(format_message_with_content_length(encoded))
 65 |   else
 66 |     error("Could not encode:" .. payload)
 67 |   end
 68 | end
 69 | 
 70 | function M.respond(id, err, result)
 71 |   assert(type(id) == "number", "id must be a number")
 72 |   M.send_message { jsonrpc = "2.0", id = id, error = err, result = result }
 73 | end
 74 | 
 75 | function M.notify(method, params)
 76 |   assert(type(method) == "string", "method must be a string")
 77 |   M.send_message { jsonrpc = "2.0", method = method, params = params or {} }
 78 | end
 79 | 
 80 | --@private
 81 | --- Parses an LSP Message's header
 82 | ---
 83 | --@param header: The header to parse.
 84 | --@returns Parsed headers
 85 | local function parse_headers(header)
 86 |   if type(header) ~= "string" then
 87 |     return nil
 88 |   end
 89 |   local headers = {}
 90 |   for line in vim.gsplit(header, "\r\n", true) do
 91 |     if line == "" then
 92 |       break
 93 |     end
 94 |     local key, value = line:match "^%s*(%S+)%s*:%s*(.+)%s*$"
 95 |     if key then
 96 |       key = key:lower():gsub("%-", "_")
 97 |       headers[key] = value
 98 |     else
 99 |       local _ = log.error() and log.error("invalid header line %q", line)
100 |       error(string.format("invalid header line %q", line))
101 |     end
102 |   end
103 |   headers.content_length = tonumber(headers.content_length)
104 |     or error(string.format("Content-Length not found in headers. %q", header))
105 |   return headers
106 | end
107 | 
108 | -- This is the start of any possible header patterns. The gsub converts it to a
109 | -- case insensitive pattern.
110 | local header_start_pattern = ("content"):gsub("%w", function(c)
111 |   return "[" .. c .. c:upper() .. "]"
112 | end)
113 | 
114 | --@private
115 | --- The actual workhorse.
116 | local function request_parser_loop()
117 |   local buffer = ""
118 |   while true do
119 |     -- A message can only be complete if it has a double CRLF and also the full
120 |     -- payload, so first let's check for the CRLFs
121 |     local start, finish = buffer:find("\r\n\r\n", 1, true)
122 |     -- Start parsing the headers
123 |     if start then
124 |       -- This is a workaround for servers sending initial garbage before
125 |       -- sending headers, such as if a bash script sends stdout. It assumes
126 |       -- that we know all of the headers ahead of time. At this moment, the
127 |       -- only valid headers start with "Content-*", so that's the thing we will
128 |       -- be searching for.
129 |       -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
130 |       local buffer_start = buffer:find(header_start_pattern)
131 |       local headers = parse_headers(buffer:sub(buffer_start, start - 1))
132 |       buffer = buffer:sub(finish + 1)
133 |       local content_length = headers.content_length
134 |       -- Keep waiting for data until we have enough.
135 |       while #buffer < content_length do
136 |         buffer = buffer .. (coroutine.yield() or error "Expected more data for the body. The server may have died.") -- TODO hmm.
137 |       end
138 |       local body = buffer:sub(1, content_length)
139 |       buffer = buffer:sub(content_length + 1)
140 |       -- Yield our data.
141 |       buffer = buffer
142 |         .. (coroutine.yield(headers, body) or error "Expected more data for the body. The server may have died.") -- TODO hmm.
143 |     else
144 |       -- Get more data since we don't have enough.
145 |       buffer = buffer .. (coroutine.yield() or error "Expected more data for the header. The server may have died.") -- TODO hmm.
146 |     end
147 |   end
148 | end
149 | 
150 | local client_errors = vim.tbl_add_reverse_lookup {
151 |   INVALID_SERVER_MESSAGE = 1,
152 |   INVALID_SERVER_JSON = 2,
153 |   NO_RESULT_CALLBACK_FOUND = 3,
154 |   READ_ERROR = 4,
155 |   NOTIFICATION_HANDLER_ERROR = 5,
156 |   SERVER_REQUEST_HANDLER_ERROR = 6,
157 |   SERVER_RESULT_CALLBACK_ERROR = 7,
158 | }
159 | 
160 | --- Constructs an error message from an LSP error object.
161 | ---
162 | --@param err (table) The error object
163 | --@returns (string) The formatted error message
164 | local function format_rpc_error(err)
165 |   validate {
166 |     err = { err, "t" },
167 |   }
168 | 
169 |   -- There is ErrorCodes in the LSP specification,
170 |   -- but in ResponseError.code it is not used and the actual type is number.
171 |   local code
172 |   if protocol.ErrorCodes[err.code] then
173 |     code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
174 |   else
175 |     code = string.format("code_name = unknown, code = %s,", err.code)
176 |   end
177 | 
178 |   local message_parts = { "RPC[Error]", code }
179 |   if err.message then
180 |     table.insert(message_parts, "message =")
181 |     table.insert(message_parts, string.format("%q", err.message))
182 |   end
183 |   if err.data then
184 |     table.insert(message_parts, "data =")
185 |     table.insert(message_parts, vim.inspect(err.data))
186 |   end
187 |   return table.concat(message_parts, " ")
188 | end
189 | 
190 | --- Creates an RPC response object/table.
191 | ---
192 | --@param code RPC error code defined in `vim.lsp.protocol.ErrorCodes`
193 | --@param message (optional) arbitrary message to send to server
194 | --@param data (optional) arbitrary data to send to server
195 | local function rpc_response_error(code, message, data)
196 |   -- TODO should this error or just pick a sane error (like InternalError)?
197 |   local code_name = assert(protocol.ErrorCodes[code], "Invalid RPC error code")
198 |   return setmetatable({
199 |     code = code,
200 |     message = message or code_name,
201 |     data = data,
202 |   }, {
203 |     __tostring = format_rpc_error,
204 |   })
205 | end
206 | 
207 | local default_handlers = {}
208 | --@private
209 | --- Default handler for notifications sent to an LSP server.
210 | ---
211 | --@param method (string) The invoked LSP method
212 | --@param params (table): Parameters for the invoked LSP method
213 | function default_handlers.notification(method, params)
214 |   local _ = log.debug() and log.debug("notification", method, params)
215 | end
216 | --@private
217 | --- Default handler for requests sent to an LSP server.
218 | ---
219 | --@param method (string) The invoked LSP method
220 | --@param params (table): Parameters for the invoked LSP method
221 | --@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
222 | function default_handlers.server_request(method, params)
223 |   local _ = log.debug() and log.debug("server_request", method, params)
224 |   return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
225 | end
226 | --@private
227 | --- Default handler for when a client exits.
228 | ---
229 | --@param code (number): Exit code
230 | --@param signal (number): Number describing the signal used to terminate (if
231 | ---any)
232 | function default_handlers.on_exit(code, signal)
233 |   local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
234 | end
235 | --@private
236 | --- Default handler for client errors.
237 | ---
238 | --@param code (number): Error code
239 | --@param err (any): Details about the error
240 | ---any)
241 | function default_handlers.on_error(code, err)
242 |   local _ = log.error() and log.error("client_error:", client_errors[code], err)
243 | end
244 | 
245 | --- Starts an LSP server process and create an LSP RPC client object to
246 | --- interact with it.
247 | ---
248 | --@param cmd (string) Command to start the LSP server.
249 | --@param cmd_args (table) List of additional string arguments to pass to {cmd}.
250 | --@param handlers (table, optional) Handlers for LSP message types. Valid
251 | ---handler names are:
252 | --- - `"notification"`
253 | --- - `"server_request"`
254 | --- - `"on_error"`
255 | --- - `"on_exit"`
256 | --@param extra_spawn_params (table, optional) Additional context for the LSP
257 | --- server process. May contain:
258 | --- - {cwd} (string) Working directory for the LSP server process
259 | --- - {env} (table) Additional environment variables for LSP server process
260 | --@returns Client RPC object.
261 | ---
262 | --@returns Methods:
263 | --- - `notify()` |vim.lsp.rpc.notify()|
264 | --- - `request()` |vim.lsp.rpc.request()|
265 | ---
266 | --@returns Members:
267 | --- - {pid} (number) The LSP server's PID.
268 | --- - {handle} A handle for low-level interaction with the LSP server process
269 | ---   |vim.loop|.
270 | local function start(cmd, cmd_args, handlers, extra_spawn_params)
271 |   --@private
272 |   local function on_error(errkind, ...)
273 |     assert(client_errors[errkind])
274 |     -- TODO what to do if this fails?
275 |     pcall(handlers.on_error, errkind, ...)
276 |   end
277 | 
278 |   --@private
279 |   local function pcall_handler(errkind, status, head, ...)
280 |     if not status then
281 |       on_error(errkind, head, ...)
282 |       return status, head
283 |     end
284 |     return status, head, ...
285 |   end
286 |   --@private
287 |   local function try_call(errkind, fn, ...)
288 |     return pcall_handler(errkind, pcall(fn, ...))
289 |   end
290 | 
291 |   -- TODO periodically check message_callbacks for old requests past a certain
292 |   -- time and log them. This would require storing the timestamp. I could call
293 |   -- them with an error then, perhaps.
294 | 
295 |   local request_parser = coroutine.wrap(request_parser_loop)
296 |   request_parser()
297 |   stdout:read_start(function(err, chunk)
298 |     if err then
299 |       -- TODO better handling. Can these be intermittent errors?
300 |       on_error(client_errors.READ_ERROR, err)
301 |       return
302 |     end
303 |     -- This should signal that we are done reading from the client.
304 |     if not chunk then
305 |       return
306 |     end
307 |     -- Flush anything in the parser by looping until we don't get a result
308 |     -- anymore.
309 |     while true do
310 |       local headers, body = request_parser(chunk)
311 |       -- If we successfully parsed, then handle the response.
312 |       if headers then
313 |         handle_body(body)
314 |         -- Set chunk to empty so that we can call request_parser to get
315 |         -- anything existing in the parser to flush.
316 |         chunk = ""
317 |       else
318 |         break
319 |       end
320 |     end
321 |   end)
322 | end
323 | 
324 | return M
325 | -- vim:sw=2 ts=2 et
326 | 


--------------------------------------------------------------------------------
/lua/docgen/help.lua:
--------------------------------------------------------------------------------
  1 | local render = require("docgen.renderer").render
  2 | local render_without_first_line_prefix = require("docgen.renderer").render_without_first_line_prefix
  3 | 
  4 | ---@brief [[
  5 | --- All help formatting related utilties. Used to transform output from |docgen| into vim style documentation.
  6 | --- Other documentation styles are possible, but have not yet been implemented.
  7 | ---@brief ]]
  8 | 
  9 | ---@tag docgen-help-formatter
 10 | local help = {}
 11 | 
 12 | local trim_trailing = function(str)
 13 |   return str:gsub("%s*$", "")
 14 | end
 15 | 
 16 | local align_text = function(left, right, width)
 17 |   left = left or ""
 18 |   right = right or ""
 19 | 
 20 |   local remaining = width - #left - #right
 21 | 
 22 |   return string.format("%s%s%s", left, string.rep(" ", remaining), right)
 23 | end
 24 | 
 25 | --- Format an entire generated metadata from |docgen|
 26 | ---@param metadata table: The metadata from docgen
 27 | help.format = function(metadata)
 28 |   if vim.tbl_isempty(metadata) then
 29 |     return ""
 30 |   end
 31 | 
 32 |   local formatted = ""
 33 | 
 34 |   local add = function(text, no_nl)
 35 |     formatted = string.format("%s%s%s", formatted, (text or ""), (no_nl and "" or "\n"))
 36 |   end
 37 | 
 38 |   -- TODO: Make top level
 39 | 
 40 |   add(string.rep("=", 80))
 41 |   if metadata.tag then
 42 |     -- Support multiple tags
 43 |     local tags = vim.tbl_map(function(x)
 44 |       return string.format("*%s*", x)
 45 |     end, vim.split(metadata.tag, "%s+"))
 46 | 
 47 |     local left = (function()
 48 |       if metadata.config and metadata.config.name and type(metadata.config.name) == "string" then
 49 |         return metadata.config.name:upper()
 50 |       else
 51 |         local ret = vim.split(metadata.tag, "%s+")
 52 |         if ret and ret[1] then
 53 |           ret = vim.split(ret[1], "%.")
 54 |           ret = ret[#ret]
 55 |         end
 56 |         return ret:upper()
 57 |       end
 58 |     end)()
 59 |     add(align_text(left, table.concat(tags, " "), 80))
 60 |     add()
 61 |   end
 62 | 
 63 |   -- Make brief
 64 |   if metadata.brief then
 65 |     local result = help.format_brief(metadata.brief)
 66 | 
 67 |     if not result then
 68 |       error "Missing result"
 69 |     end
 70 | 
 71 |     add(result)
 72 |     add()
 73 |   end
 74 | 
 75 |   -- Make commands
 76 |   local commands = metadata.commands or {}
 77 |   if not vim.tbl_isempty(commands) then
 78 |     add(help.format_commands(commands, metadata.config))
 79 |     add()
 80 |   end
 81 | 
 82 |   -- Make classes
 83 |   local metadata_classes = vim.deepcopy(metadata.class_list or {})
 84 | 
 85 |   if metadata.config then
 86 |     if type(metadata.config.class_order) == "function" then
 87 |       metadata.config.class_order(metadata_classes)
 88 |     elseif metadata.config.class_order == "ascending" then
 89 |       table.sort(metadata_classes)
 90 |     elseif metadata.config.class_order == "descending" then
 91 |       table.sort(metadata_classes, function(a, b)
 92 |         return a > b
 93 |       end)
 94 |     end
 95 |   end
 96 |   for _, class_name in ipairs(metadata_classes) do
 97 |     local v = metadata.classes[class_name]
 98 | 
 99 |     local result = help.format_class_metadata(v, metadata.config)
100 |     if not result then
101 |       error "Missing result"
102 |     end
103 | 
104 |     add(result)
105 |     add()
106 |   end
107 | 
108 |   -- Make functions
109 |   local metadata_keys = vim.deepcopy(metadata.function_list or {})
110 | 
111 |   if metadata.config then
112 |     if type(metadata.config.function_order) == "function" then
113 |       metadata.config.function_order(metadata_keys)
114 |     elseif metadata.config.function_order == "ascending" then
115 |       table.sort(metadata_keys)
116 |     elseif metadata.config.function_order == "descending" then
117 |       table.sort(metadata_keys, function(a, b)
118 |         return a > b
119 |       end)
120 |     end
121 | 
122 |     -- config.module:
123 |     --   Replace the module return name with this.
124 |     if metadata.config.module then
125 |       if type(metadata.return_module) == "string" then
126 |         metadata.config.transform_name = function(_, name)
127 |           return (string.gsub(name, metadata.return_module, metadata.config.module, 1))
128 |         end
129 |       end
130 |     end
131 |   end
132 | 
133 |   metadata_keys = vim.tbl_filter(function(func_name)
134 |     if string.find(func_name, ".__", 1, true) then
135 |       return false
136 |     end
137 | 
138 |     if string.find(func_name, ":__", 1, true) then
139 |       return false
140 |     end
141 | 
142 |     if func_name:sub(1, 2) == "__" then
143 |       return false
144 |     end
145 | 
146 |     return func_name:sub(1, 2) ~= "__"
147 |   end, metadata_keys)
148 | 
149 |   for _, func_name in ipairs(metadata_keys) do
150 |     local v = metadata.functions[func_name]
151 | 
152 |     local result = help.format_function_metadata(v, metadata.config)
153 |     if not result then
154 |       error "Missing result"
155 |     end
156 | 
157 |     add(result)
158 |     add()
159 |   end
160 | 
161 |   add()
162 | 
163 |   return formatted
164 | end
165 | 
166 | help.format_brief = function(brief_metadata)
167 |   return render(brief_metadata, "", 79)
168 | end
169 | 
170 | -- TODO(conni2461): Do we want some configuration for alignment?!
171 | help.__left_side_parameter_field = function(input, max_name_width, space_prefix)
172 |   local name = string.format("%s%s{%s} ", space_prefix, space_prefix, input.name)
173 |   local diff = max_name_width - #input.name
174 | 
175 |   return string.format("%s%s(%s)  ", name, string.rep(" ", diff), table.concat(input.type, "|"))
176 | end
177 | 
178 | help.format_parameter_field = function(input, space_prefix, max_name_width, align_width)
179 |   local left_side = help.__left_side_parameter_field(input, max_name_width, space_prefix)
180 | 
181 |   local width = math.max(align_width, 78)
182 |   local right_side = render_without_first_line_prefix(input.description, string.rep(" ", align_width), width)
183 |   if right_side == "" then
184 |     return string.format("%s\n", trim_trailing(left_side))
185 |   end
186 | 
187 |   local diff = align_width - #left_side
188 |   assert(diff >= 0, "Otherwise we have a big error somewhere in docgen")
189 | 
190 |   return string.format("%s%s%s\n", left_side, string.rep(" ", diff), right_side)
191 | end
192 | 
193 | help.iter_parameter_field = function(input, list, name, space_prefix)
194 |   local output = ""
195 |   if list and table.getn(list) > 0 then
196 |     output = string.format("%s\n%s%s: ~\n", output, space_prefix, name)
197 |     local max_name_width = 0
198 |     for _, e in ipairs(list) do
199 |       local width = #input[e].name
200 |       if width > max_name_width then
201 |         max_name_width = width
202 |       end
203 |     end
204 | 
205 |     local left_width = 0
206 |     for _, e in ipairs(list) do
207 |       local width = #(help.__left_side_parameter_field(input[e], max_name_width, space_prefix))
208 |       if width > left_width then
209 |         left_width = width
210 |       end
211 |     end
212 | 
213 |     for _, e in ipairs(list) do
214 |       output =
215 |         string.format("%s%s", output, help.format_parameter_field(input[e], space_prefix, max_name_width, left_width))
216 |     end
217 |   end
218 |   return output
219 | end
220 | 
221 | help.format_class_metadata = function(class, config)
222 |   config = config or {}
223 |   local space_prefix = string.rep(" ", config.space_prefix or 4)
224 | 
225 |   local doc = ""
226 |   local left_side = class.parent and string.format("%s : %s", class.name, class.parent) or class.name
227 | 
228 |   local header = align_text(left_side, string.format("*%s*", class.name), 78)
229 |   doc = string.format("%s%s\n", doc, header)
230 | 
231 |   local description = render(class.desc, space_prefix, 79)
232 |   doc = string.format("%s%s\n", doc, description)
233 | 
234 |   if class.parent then
235 |     doc = string.format("%s\n%sParents: ~\n%s%s|%s|\n", doc, space_prefix, space_prefix, space_prefix, class.parent)
236 |   end
237 | 
238 |   if type(config.field_order) == "function" then
239 |     config.field_order(class.field_list)
240 |   elseif config.field_order == "ascending" then
241 |     table.sort(class.field_list)
242 |   elseif config.field_order == "descending" then
243 |     table.sort(class.field_list, function(a, b)
244 |       return a > b
245 |     end)
246 |   end
247 |   doc = string.format(
248 |     "%s%s",
249 |     doc,
250 |     help.iter_parameter_field(class.fields, class.field_list, config.field_heading or "Fields", space_prefix)
251 |   )
252 | 
253 |   return doc
254 | end
255 | 
256 | ---@class DocgenCommand
257 | ---@field name string
258 | ---@field usage string
259 | ---@field documentation string[]
260 | 
261 | --- Format commands
262 | ---@param command_metadata DocgenCommand[]
263 | ---@param config any
264 | help.format_commands = function(command_metadata, config)
265 |   local doc = ""
266 | 
267 |   for _, command in ipairs(command_metadata) do
268 |     config = config or {}
269 | 
270 |     local right_side = string.format("*:%s*", command.name)
271 |     local header = align_text("", right_side, 78)
272 | 
273 |     doc = doc .. header .. "\n"
274 |     doc = doc .. command.usage .. " ~\n"
275 |     doc = doc .. render(command.documentation, "    ", 79)
276 |     doc = doc .. "\n\n"
277 |   end
278 | 
279 |   return doc
280 | end
281 | 
282 | help.format_function_metadata = function(function_metadata, config)
283 |   config = config or {}
284 |   local space_prefix = string.rep(" ", 4)
285 | 
286 |   local name = function_metadata.name
287 |   local tag = config.transform_name and config.transform_name(function_metadata, name) or name
288 | 
289 |   local left_side = string.format(
290 |     "%s(%s)",
291 |     name,
292 |     table.concat(
293 |       vim.tbl_map(function(val)
294 |         return string.format("{%s}", function_metadata.parameters[val].name)
295 |       end, function_metadata.parameter_list),
296 |       ", "
297 |     )
298 |   )
299 | 
300 |   -- Add single whitespace on the left to ensure that it reads as help tag
301 |   local right_side = string.format(" *%s()*", tag)
302 | 
303 |   -- TODO(conni2461): LONG function names break this thing
304 |   local header = align_text(left_side, right_side, 78)
305 | 
306 |   local doc = ""
307 |   doc = string.format("%s%s\n", doc, header)
308 | 
309 |   local description = render(function_metadata.description or {}, space_prefix, 79)
310 |   doc = string.format("%s%s\n", doc, description)
311 | 
312 |   -- TODO(conni2461): CLASS
313 | 
314 |   -- Handles parameter if used
315 |   doc = string.format(
316 |     "%s%s",
317 |     doc,
318 |     help.iter_parameter_field(
319 |       function_metadata.parameters,
320 |       function_metadata.parameter_list,
321 |       "Parameters",
322 |       space_prefix
323 |     )
324 |   )
325 | 
326 |   if type(config.field_order) == "function" then
327 |     config.field_order(function_metadata.field_list)
328 |   elseif config.field_order == "ascending" then
329 |     table.sort(function_metadata.field_list)
330 |   elseif config.field_order == "descending" then
331 |     table.sort(function_metadata.field_list, function(a, b)
332 |       return a > b
333 |     end)
334 |   end
335 |   -- Handle fields if used
336 |   doc = string.format(
337 |     "%s%s",
338 |     doc,
339 |     help.iter_parameter_field(
340 |       function_metadata.fields,
341 |       function_metadata.field_list,
342 |       config.field_heading or "Fields",
343 |       space_prefix
344 |     )
345 |   )
346 | 
347 |   local gen_misc_doc = function(identification, ins)
348 |     if function_metadata[identification] then
349 |       local title = string.format("%s%s", identification:sub(1, 1):upper(), identification:sub(2, -1))
350 | 
351 |       if doc:sub(#doc, #doc) ~= "\n" then
352 |         doc = string.format("%s\n", doc)
353 |       end
354 |       doc = string.format("%s\n%s%s: ~\n", doc, space_prefix, title)
355 |       for _, x in ipairs(function_metadata[identification]) do
356 |         doc = string.format("%s%s\n", doc, render({ string.format(ins, x) }, string.rep(space_prefix, 2), 78))
357 |       end
358 |     end
359 |   end
360 | 
361 |   gen_misc_doc("varargs", "%s")
362 |   gen_misc_doc("return", "%s")
363 |   gen_misc_doc("usage", "%s")
364 |   gen_misc_doc("see", "|%s()|")
365 | 
366 |   return doc
367 | end
368 | 
369 | return help
370 | 


--------------------------------------------------------------------------------
/HOWTO.md:
--------------------------------------------------------------------------------
  1 | # How to write emmy documentation
  2 | 
  3 | ## Brief
  4 | 
  5 | Brief is used to describe a module. This is an example input:
  6 | 
  7 | ```lua
  8 | ---@brief [[
  9 | --- This will document a module and will be found at the top of each file. It uses an internal markdown renderer
 10 | --- so you don't need to worry about formatting. It will wrap the lines into one paragraph and
 11 | --- will make sure that the max line width is < 80.
 12 | ---
 13 | --- To start a new paragraph with a newline.
 14 | ---
 15 | --- To explicitly do a breakline do a `
` at the end.
16 | --- This is useful sometimes 17 | --- 18 | --- We also support itemize and enumerate 19 | --- - Item 1 20 | --- - Item 1.1 This item will be wrapped as well and the result will be as expected. This is really handy. 21 | --- - Item 1.1.1 22 | --- - Item 1.2 23 | --- - Item 2 24 | --- 25 | --- 1. Item 26 | --- 1.1. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna 27 | --- aliquyam erat, sed diam voluptua. 28 | --- 1.1.1. Item 29 | --- 1.2. Item 30 | --- 2. Item 31 | --- 32 | ---
 33 | --- You can disable formatting with a
 34 | --- pre block.
 35 | --- This is useful if you want to draw a table or write some code
 36 | --- 
37 | --- 38 | ---@brief ]] 39 | 40 | ``` 41 | 42 | Output: 43 | 44 | ``` 45 | ================================================================================ 46 | This will document a module and will be found at the top of each file. It uses 47 | an internal markdown renderer so you don't need to worry about formatting. It 48 | will wrap the lines into one paragraph and will make sure that the max line 49 | width is < 80. 50 | 51 | To start a new paragraph with a newline. 52 | 53 | To explicitly do a breakline do a `
` at the end. 54 | This is useful sometimes 55 | 56 | We also support itemize and enumerate 57 | - Item 1 58 | - Item 1.1 This item will be wrapped as well and the result will be as 59 | expected. This is really handy. 60 | - Item 1.1.1 61 | - Item 1.2 62 | - Item 2 63 | 64 | 1. Item 65 | 1.1. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy 66 | eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 67 | voluptua. 68 | 1.1.1. Item 69 | 1.2. Item 70 | 2. Item 71 | 72 | You can disable formatting with a 73 | pre block. 74 | This is useful if you want to draw a table or write some code 75 | 76 | 77 | 78 | 79 | ``` 80 | 81 | ## Tag 82 | 83 | Add a tag to your module. This is suggested: 84 | 85 | ```lua 86 | ---@tag your_module 87 | 88 | ``` 89 | 90 | Output: 91 | 92 | ``` 93 | ================================================================================ 94 | *your_module* 95 | 96 | 97 | 98 | ``` 99 | 100 | ## Config 101 | 102 | You can configure docgen on file basis. For example you can define how `functions` or `classes` are sorted. 103 | 104 | ```lua 105 | ---@config { ['function_order'] = 'ascending', ['class_order'] = 'descending' } 106 | 107 | ``` 108 | Available keys value pairs are: 109 | - `function_order`: 110 | - `file_order` (default) 111 | - `ascending` 112 | - `descending` 113 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end` 114 | - If you have a typo it will do `file_order` sorting 115 | - `class_order`: 116 | - `file_order` (default) 117 | - `ascending` 118 | - `descending` 119 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end` 120 | - If you have a typo it will do `file_order` sorting 121 | - `field_order`: 122 | - `file_order` (default) 123 | - `ascending` 124 | - `descending` 125 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end` 126 | - If you have a typo it will do `file_order` sorting 127 | 128 | ## Function Header 129 | 130 | You can describe your functions. 131 | 132 | Note: We will only generate documentation for functions that are exported with the module. 133 | 134 | ```lua 135 | local m = {} 136 | 137 | --- We will not generate documentation for this function 138 | local some_func = function() 139 | return 5 140 | end 141 | 142 | --- We will not generate documentation for this function 143 | --- because it has `__` as prefix. This is the one exception 144 | m.__hidden = function() 145 | return 5 146 | end 147 | 148 | --- The documentation for this function will be generated. 149 | --- The markdown renderer will be used again.
150 | --- With the same set of features 151 | m.actual_func = function() 152 | return 5 153 | end 154 | 155 | return m 156 | 157 | ``` 158 | 159 | Output: 160 | 161 | ``` 162 | ================================================================================ 163 | m.actual_func() *m.actual_func()* 164 | The documentation for this function will be generated. The markdown 165 | renderer will be used again. 166 | With the same set of features 167 | 168 | 169 | 170 | 171 | 172 | ``` 173 | 174 | ## Parameter 175 | 176 | You can specify parameters and document them with `---@param name type: desc` 177 | 178 | ```lua 179 | local math = {} 180 | 181 | --- Will return the bigger number 182 | ---@param a number: first number 183 | ---@param b number: second number 184 | math.max = function(a, b) 185 | if a > b then 186 | return a 187 | end 188 | return b 189 | end 190 | 191 | return math 192 | 193 | ``` 194 | 195 | Output: 196 | 197 | ``` 198 | ================================================================================ 199 | math.max({a}, {b}) *math.max()* 200 | Will return the bigger number 201 | 202 | 203 | Parameters: ~ 204 | {a} (number) first number 205 | {b} (number) second number 206 | 207 | 208 | 209 | 210 | ``` 211 | 212 | ## Field 213 | 214 | Can be used to describe a parameter table. 215 | 216 | ```lua 217 | local x = {} 218 | 219 | --- This function has documentation 220 | ---@param t table: some input table 221 | ---@field k1 number: first key of input table 222 | ---@field key function: second key of input table 223 | ---@field key3 table: third key of input table 224 | function x.hello(t) 225 | return 0 226 | end 227 | 228 | return x 229 | 230 | ``` 231 | 232 | Output: 233 | 234 | ``` 235 | ================================================================================ 236 | x.hello({t}) *x.hello()* 237 | This function has documentation 238 | 239 | 240 | Parameters: ~ 241 | {t} (table) some input table 242 | 243 | Fields: ~ 244 | {k1} (number) first key of input table 245 | {key} (function) second key of input table 246 | {key3} (table) third key of input table 247 | 248 | 249 | 250 | 251 | ``` 252 | 253 | ## Return 254 | 255 | You can specify a return parameter with `---@return type: desc` 256 | 257 | ```lua 258 | local math = {} 259 | 260 | --- Will return the bigger number 261 | ---@param a number: first number 262 | ---@param b number: second number 263 | ---@return number: bigger number 264 | function math.max = function(a, b) 265 | if a > b then 266 | return a 267 | end 268 | return b 269 | end 270 | 271 | return math 272 | 273 | ``` 274 | 275 | Output: 276 | 277 | ``` 278 | ================================================================================ 279 | math.max({a}, {b}) *math.max()* 280 | Will return the bigger number 281 | 282 | 283 | Parameters: ~ 284 | {a} (number) first number 285 | {b} (number) second number 286 | 287 | Return: ~ 288 | number: bigger number 289 | 290 | 291 | 292 | 293 | ``` 294 | 295 | ## See 296 | 297 | Reference something else. 298 | 299 | ```lua 300 | local math = {} 301 | 302 | --- Will return the smaller number 303 | ---@param a number: first number 304 | ---@param b number: second number 305 | ---@return number: smaller number 306 | ---@see math.max 307 | function math.min(a, b) 308 | if a < b then 309 | return a 310 | end 311 | return b 312 | end 313 | 314 | --- Will return the bigger number 315 | ---@param a number: first number 316 | ---@param b number: second number 317 | ---@return number: bigger number 318 | ---@see math.min 319 | function math.max(a, b) 320 | if a > b then 321 | return a 322 | end 323 | return b 324 | end 325 | 326 | return math 327 | 328 | ``` 329 | 330 | Output: 331 | 332 | ``` 333 | ================================================================================ 334 | math.min({a}, {b}) *math.min()* 335 | Will return the smaller number 336 | 337 | 338 | Parameters: ~ 339 | {a} (number) first number 340 | {b} (number) second number 341 | 342 | Return: ~ 343 | number: smaller number 344 | 345 | See: ~ 346 | |math.max()| 347 | 348 | 349 | math.max({a}, {b}) *math.max()* 350 | Will return the bigger number 351 | 352 | 353 | Parameters: ~ 354 | {a} (number) first number 355 | {b} (number) second number 356 | 357 | Return: ~ 358 | number: bigger number 359 | 360 | See: ~ 361 | |math.min()| 362 | 363 | 364 | 365 | 366 | ``` 367 | 368 | ## Class 369 | 370 | You can define your own classes and types to give a better sense of the Input or Ouput of a function. 371 | Another good usecase for this are structs defined by ffi. 372 | 373 | This is a more complete (not functional) example where we define the documentation of the c struct 374 | `passwd` and return this struct with a function. 375 | 376 | ```lua 377 | local m = {} 378 | 379 | ---@class passwd @The passwd c struct 380 | ---@field pw_name string: username 381 | ---@field pw_passwd string: user password 382 | ---@field pw_uid number: user id 383 | ---@field pw_gid number: groupd id 384 | ---@field pw_gecos string: user information 385 | ---@field pw_dir string: user home directory 386 | ---@field pw_shell string: user default shell 387 | 388 | --- Get user by id 389 | ---@param id number: user id 390 | ---@return passwd: returns a password table 391 | function m.get_user(id) 392 | return ffi.C.getpwuid(id) 393 | end 394 | 395 | return m 396 | 397 | ``` 398 | 399 | Output: 400 | 401 | ``` 402 | ================================================================================ 403 | passwd *passwd* 404 | The passwd c struct 405 | 406 | Fields: ~ 407 | {pw_name} (string) username 408 | {pw_passwd} (string) user password 409 | {pw_uid} (number) user id 410 | {pw_gid} (number) groupd id 411 | {pw_gecos} (string) user information 412 | {pw_dir} (string) user home directory 413 | {pw_shell} (string) user default shell 414 | 415 | 416 | m.get_user({id}) *m.get_user()* 417 | Get user by id 418 | 419 | 420 | Parameters: ~ 421 | {id} (number) user id 422 | 423 | Return: ~ 424 | passwd: returns a password table 425 | 426 | 427 | 428 | 429 | ``` 430 | 431 | ## Eval 432 | 433 | You can evaluate arbitrary code. For example if you have a static table you can 434 | do generate a table that will be part of the `description` output. 435 | 436 | 437 | ```lua 438 | local m = {} 439 | 440 | --- The documentation for this function will be generated. 441 | --- The markdown renderer will be used again.
442 | --- With the same set of features 443 | ---@eval { ['description'] = require('your_module').__format_keys() } 444 | m.actual_func = function() 445 | return 5 446 | end 447 | 448 | local static_values = { 449 | 'a', 450 | 'b', 451 | 'c', 452 | 'd', 453 | } 454 | 455 | m.__format_keys = function() 456 | -- we want to do formatting 457 | local table = { '
', 'Static Values: ~' }
458 | 
459 |   for _, v in ipairs(static_values) do
460 |     table.insert(table, '    ' .. v)
461 |   end
462 | 
463 |   table.insert(table, '
') 464 | return table 465 | end 466 | 467 | return m 468 | 469 | ``` 470 | 471 | Output: 472 | 473 | ``` 474 | ================================================================================ 475 | m.actual_func() *m.actual_func()* 476 | The documentation for this function will be generated. The markdown 477 | renderer will be used again. 478 | With the same set of features. 479 | 480 | Static Values: ~ 481 | a 482 | b 483 | c 484 | d 485 | ``` 486 | 487 | --------------------------------------------------------------------------------