aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter/playground.lua
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2023-06-06 08:23:20 -0700
committerGitHub <noreply@github.com>2023-06-06 08:23:20 -0700
commitc48b1421af28d0317c807bca00c7e2fff97d9ad0 (patch)
tree6737f131d5dc773318ee9816ca7b7b92af4702ea /runtime/lua/vim/treesitter/playground.lua
parent175e5c8b96fe0756040fcb31f46d9c97b3957776 (diff)
downloadrneovim-c48b1421af28d0317c807bca00c7e2fff97d9ad0.tar.gz
rneovim-c48b1421af28d0317c807bca00c7e2fff97d9ad0.tar.bz2
rneovim-c48b1421af28d0317c807bca00c7e2fff97d9ad0.zip
refactor!: rename "playground" => "dev" #23919
Problem: "playground" is new jargon that overlaps with existing concepts: "dev" (`:help dev`) and "view" (also "scratch" `:help scratch-buffer`) . Solution: We should consistently use "dev" as the namespace for where "developer tools" live. For purposes of a "throwaway sandbox object", we can use the name "view". - Rename `TSPlayground` => `TSView` - Rename `playground.lua` => `dev.lua`
Diffstat (limited to 'runtime/lua/vim/treesitter/playground.lua')
-rw-r--r--runtime/lua/vim/treesitter/playground.lua439
1 files changed, 0 insertions, 439 deletions
diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
deleted file mode 100644
index 8293c1bd0a..0000000000
--- a/runtime/lua/vim/treesitter/playground.lua
+++ /dev/null
@@ -1,439 +0,0 @@
-local api = vim.api
-
----@class TSPlaygroundModule
-local M = {}
-
----@class TSPlayground
----@field ns integer API namespace
----@field opts table Options table with the following keys:
---- - anon (boolean): If true, display anonymous nodes
---- - lang (boolean): If true, display the language alongside each node
----@field nodes TSP.Node[]
----@field named TSP.Node[]
-local TSPlayground = {}
-
----@class TSP.Node
----@field id integer Node id
----@field text string Node text
----@field named boolean True if this is a named (non-anonymous) node
----@field depth integer Depth of the node within the tree
----@field lnum integer Beginning line number of this node in the source buffer
----@field col integer Beginning column number of this node in the source buffer
----@field end_lnum integer Final line number of this node in the source buffer
----@field end_col integer Final column number of this node in the source buffer
----@field lang string Source language of this node
----@field root TSNode
-
---- Traverse all child nodes starting at {node}.
----
---- This is a recursive function. The {depth} parameter indicates the current recursion level.
---- {lang} is a string indicating the language of the tree currently being traversed. Each traversed
---- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order
---- they were visited.
----
---- {injections} is a table mapping node ids from the primary tree to language tree injections. Each
---- injected language has a series of trees nested within the primary language's tree, and the root
---- node of each of these trees is contained within a node in the primary tree. The {injections}
---- table maps nodes in the primary tree to root nodes of injected trees.
----
----@param node TSNode Starting node to begin traversal |tsnode|
----@param depth integer Current recursion depth
----@param lang string Language of the tree currently being traversed
----@param injections table<integer,TSP.Node> Mapping of node ids to root nodes of injected language trees (see
---- explanation above)
----@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree
----@private
-local function traverse(node, depth, lang, injections, tree)
- local injection = injections[node:id()]
- if injection then
- traverse(injection.root, depth, injection.lang, injections, tree)
- end
-
- for child, field in node:iter_children() do
- local type = child:type()
- local lnum, col, end_lnum, end_col = child:range()
- local named = child:named()
- local text ---@type string
- if named then
- if field then
- text = string.format('%s: (%s)', field, type)
- else
- text = string.format('(%s)', type)
- end
- else
- text = string.format('"%s"', type:gsub('\n', '\\n'))
- end
-
- table.insert(tree, {
- id = child:id(),
- text = text,
- named = named,
- depth = depth,
- lnum = lnum,
- col = col,
- end_lnum = end_lnum,
- end_col = end_col,
- lang = lang,
- })
-
- traverse(child, depth + 1, lang, injections, tree)
- end
-
- return tree
-end
-
---- Create a new Playground object.
----
----@param bufnr integer Source buffer number
----@param lang string|nil Language of source buffer
----
----@return TSPlayground|nil
----@return string|nil Error message, if any
----
----@package
-function TSPlayground:new(bufnr, lang)
- local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
- if not ok then
- return nil, 'No parser available for the given buffer'
- end
-
- -- For each child tree (injected language), find the root of the tree and locate the node within
- -- the primary tree that contains that root. Add a mapping from the node in the primary tree to
- -- the root in the child tree to the {injections} table.
- local root = parser:parse()[1]:root()
- local injections = {} ---@type table<integer,table>
- parser:for_each_child(function(child, lang_)
- child:for_each_tree(function(tree)
- local r = tree:root()
- local node = root:named_descendant_for_range(r:range())
- if node then
- injections[node:id()] = {
- lang = lang_,
- root = r,
- }
- end
- end)
- end)
-
- local nodes = traverse(root, 0, parser:lang(), injections, {})
-
- local named = {} ---@type TSP.Node[]
- for _, v in ipairs(nodes) do
- if v.named then
- named[#named + 1] = v
- end
- end
-
- local t = {
- ns = api.nvim_create_namespace(''),
- nodes = nodes,
- named = named,
- opts = {
- anon = false,
- lang = false,
- },
- }
-
- setmetatable(t, self)
- self.__index = self
- return t
-end
-
-local decor_ns = api.nvim_create_namespace('ts.playground')
-
----@private
----@param lnum integer
----@param col integer
----@param end_lnum integer
----@param end_col integer
----@return string
-local function get_range_str(lnum, col, end_lnum, end_col)
- if lnum == end_lnum then
- return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col)
- end
- return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
-end
-
---- Write the contents of this Playground into {bufnr}.
----
----@param bufnr integer Buffer number to write into.
----@package
-function TSPlayground:draw(bufnr)
- vim.bo[bufnr].modifiable = true
- local lines = {} ---@type string[]
- local lang_hl_marks = {} ---@type table[]
-
- for _, item in self:iter() do
- local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col)
- local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
- local line =
- string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str)
-
- if self.opts.lang then
- lang_hl_marks[#lang_hl_marks + 1] = {
- col = #line - #lang_str,
- end_col = #line,
- }
- end
-
- lines[#lines + 1] = line
- end
-
- api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
-
- api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1)
-
- for i, m in ipairs(lang_hl_marks) do
- api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, {
- hl_group = 'Title',
- end_col = m.end_col,
- })
- end
-
- vim.bo[bufnr].modifiable = false
-end
-
---- Get node {i} from this Playground object.
----
---- The node number is dependent on whether or not anonymous nodes are displayed.
----
----@param i integer Node number to get
----@return TSP.Node
----@package
-function TSPlayground:get(i)
- local t = self.opts.anon and self.nodes or self.named
- return t[i]
-end
-
---- Iterate over all of the nodes in this Playground object.
----
----@return (fun(): integer, TSP.Node) Iterator over all nodes in this Playground
----@return table
----@return integer
----@package
-function TSPlayground:iter()
- return ipairs(self.opts.anon and self.nodes or self.named)
-end
-
---- @class InspectTreeOpts
---- @field lang string? The language of the source buffer. If omitted, the
---- filetype of the source buffer is used.
---- @field bufnr integer? Buffer to draw the tree into. If omitted, a new
---- buffer is created.
---- @field winid integer? Window id to display the tree buffer in. If omitted,
---- a new window is created with {command}.
---- @field command string? Vimscript command to create the window. Default
---- value is "60vnew". Only used when {winid} is nil.
---- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a
---- function, it accepts the buffer number of the source
---- buffer as its only argument and should return a string.
-
---- @param opts InspectTreeOpts
-function M.inspect_tree(opts)
- vim.validate({
- opts = { opts, 't', true },
- })
-
- opts = opts or {}
-
- local buf = api.nvim_get_current_buf()
- local win = api.nvim_get_current_win()
- local pg = assert(TSPlayground:new(buf, opts.lang))
-
- -- Close any existing playground window
- if vim.b[buf].playground then
- local w = vim.b[buf].playground
- if api.nvim_win_is_valid(w) then
- api.nvim_win_close(w, true)
- end
- end
-
- local w = opts.winid
- if not w then
- vim.cmd(opts.command or '60vnew')
- w = api.nvim_get_current_win()
- end
-
- local b = opts.bufnr
- if b then
- api.nvim_win_set_buf(w, b)
- else
- b = api.nvim_win_get_buf(w)
- end
-
- vim.b[buf].playground = w
-
- vim.wo[w].scrolloff = 5
- vim.wo[w].wrap = false
- vim.wo[w].foldmethod = 'manual' -- disable folding
- vim.bo[b].buflisted = false
- vim.bo[b].buftype = 'nofile'
- vim.bo[b].bufhidden = 'wipe'
- vim.b[b].disable_query_linter = true
- vim.bo[b].filetype = 'query'
-
- local title --- @type string?
- local opts_title = opts.title
- if not opts_title then
- local bufname = api.nvim_buf_get_name(buf)
- title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.'))
- elseif type(opts_title) == 'function' then
- title = opts_title(buf)
- end
-
- assert(type(title) == 'string', 'Window title must be a string')
- api.nvim_buf_set_name(b, title)
-
- pg:draw(b)
-
- api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- api.nvim_buf_set_keymap(b, 'n', '<CR>', '', {
- desc = 'Jump to the node under the cursor in the source buffer',
- callback = function()
- local row = api.nvim_win_get_cursor(w)[1]
- local pos = pg:get(row)
- api.nvim_set_current_win(win)
- api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col })
- end,
- })
- api.nvim_buf_set_keymap(b, 'n', 'a', '', {
- desc = 'Toggle anonymous nodes',
- callback = function()
- local row, col = unpack(api.nvim_win_get_cursor(w))
- local curnode = pg:get(row)
- while curnode and not curnode.named do
- row = row - 1
- curnode = pg:get(row)
- end
-
- pg.opts.anon = not pg.opts.anon
- pg:draw(b)
-
- if not curnode then
- return
- end
-
- local id = curnode.id
- for i, node in pg:iter() do
- if node.id == id then
- api.nvim_win_set_cursor(w, { i, col })
- break
- end
- end
- end,
- })
- api.nvim_buf_set_keymap(b, 'n', 'I', '', {
- desc = 'Toggle language display',
- callback = function()
- pg.opts.lang = not pg.opts.lang
- pg:draw(b)
- end,
- })
-
- local group = api.nvim_create_augroup('treesitter/playground', {})
-
- api.nvim_create_autocmd('CursorMoved', {
- group = group,
- buffer = b,
- callback = function()
- api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- local row = api.nvim_win_get_cursor(w)[1]
- local pos = pg:get(row)
- api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, {
- end_row = pos.end_lnum,
- end_col = math.max(0, pos.end_col),
- hl_group = 'Visual',
- })
-
- local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
-
- -- Move the cursor if highlighted range is completely out of view
- if pos.lnum < topline and pos.end_lnum < topline then
- api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 })
- elseif pos.lnum > botline and pos.end_lnum > botline then
- api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 })
- end
- end,
- })
-
- api.nvim_create_autocmd('CursorMoved', {
- group = group,
- buffer = buf,
- callback = function()
- if not api.nvim_buf_is_loaded(b) then
- return true
- end
-
- api.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
-
- local cursor_node = vim.treesitter.get_node({
- bufnr = buf,
- lang = opts.lang,
- ignore_injections = false,
- })
- if not cursor_node then
- return
- end
-
- local cursor_node_id = cursor_node:id()
- for i, v in pg:iter() do
- if v.id == cursor_node_id then
- local start = v.depth
- local end_col = start + #v.text
- api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, {
- end_col = end_col,
- hl_group = 'Visual',
- })
- api.nvim_win_set_cursor(w, { i, 0 })
- break
- end
- end
- end,
- })
-
- api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
- group = group,
- buffer = buf,
- callback = function()
- if not api.nvim_buf_is_loaded(b) then
- return true
- end
-
- pg = assert(TSPlayground:new(buf, opts.lang))
- pg:draw(b)
- end,
- })
-
- api.nvim_create_autocmd('BufLeave', {
- group = group,
- buffer = b,
- callback = function()
- api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- end,
- })
-
- api.nvim_create_autocmd('BufLeave', {
- group = group,
- buffer = buf,
- callback = function()
- if not api.nvim_buf_is_loaded(b) then
- return true
- end
-
- api.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
- end,
- })
-
- api.nvim_create_autocmd('BufHidden', {
- group = group,
- buffer = buf,
- once = true,
- callback = function()
- if api.nvim_win_is_valid(w) then
- api.nvim_win_close(w, true)
- end
- end,
- })
-end
-
-return M