From 77fc8cd9e789f9568b5a3c1b148805b538c9c63a Mon Sep 17 00:00:00 2001 From: Stephan Seitz Date: Sun, 5 Jul 2020 16:33:35 +0200 Subject: [PATCH] Add strip! directive for queries The strip! directive is implemented in the tree-sitter tags crate in the main tree-sitter repo (https://github.com/tree-sitter/tree-sitter/blob/de53b82e2c48d6221af9881e50cc57d961de3365/tags/src/lib.rs#L167). It is used specifically for tag extraction to shrink the range of a node (or better the text of a node). For our textobjects queries the strip! directive might be use full since it allows us to strip aways whitespaces and leading/trailing characters. --- lua/nvim-treesitter/query.lua | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/lua/nvim-treesitter/query.lua b/lua/nvim-treesitter/query.lua index 72cead9dd..95bdde07b 100644 --- a/lua/nvim-treesitter/query.lua +++ b/lua/nvim-treesitter/query.lua @@ -1,5 +1,6 @@ local api = vim.api local ts = vim.treesitter +local utils = require 'nvim-treesitter.utils' local M = {} @@ -71,6 +72,56 @@ function M.get_query(lang, query_name) end end + --This function is copied from Neovims treesitter.lua to enforece same behavior +local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true} +local function check_magic(str) + if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then + return str + end + return '\\v'..str +end + +local function strip_beginning(lines, regex) + local current_col = 0 + local current_line = 0 + local re = vim.regex(check_magic('^('..regex..')')) + for linenr, line in ipairs(lines) do + current_line = linenr + local match_start, match_end = re:match_str(line) + if match_start ~= 0 then + break + else + current_col = match_end + end + if match_end ~= vim.str_byteindex(line, #line) then + break + end + current_line = current_line + 1 + end + return current_line - 1, current_col +end + +local function strip_end(lines, regex) + local current_col = 0 + local line_diff = 0 + local re = vim.regex(check_magic('('..regex..')$')) + for linenr=#lines, 1, -1 do + local line = lines[linenr] + line_diff = #lines - linenr + local match_start, match_end = re:match_str(line) + if match_end ~= vim.str_byteindex(line, #line) then + break + else + current_col = match_start + end + if match_start ~= 0 then + break + end + line_diff = line_diff + 1 + end + return line_diff, current_col +end + function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row) -- A function that splits a string on '.' local function split(string) @@ -120,6 +171,39 @@ function M.iter_prepared_matches(query, qnode, bufnr, start_row, end_row) if pred[1] == "set!" and type(pred[2]) == "string" then insert_to_path(prepared_match, split(pred[2]), pred[3]) end + if pred[1] == "strip!" and #pred == 3 then + local query_name = query.captures[pred[2]] + local regex = pred[3] + + local node = utils.get_at_path(prepared_match, query_name..'.node') + local ts_utils = require 'nvim-treesitter.ts_utils' + local node_lines = ts_utils.get_node_text(node, bufnr) + local start_line, start_col, end_line, end_col = node:range() + + local strip_line, strip_col = strip_beginning(node_lines, regex) + start_line = start_line + strip_line + if strip_line == 0 then + start_col = start_col + strip_col + else + start_col =strip_col + end + local strip_line, strip_col = strip_end(node_lines, regex) + end_line = end_line - strip_line + if strip_line == 0 then + end_col = end_col - strip_col + else + end_col = strip_col + end + + end_line = math.max(end_line, start_line) + if end_line == start_line then + end_col = math.max(end_col, start_col) + end + + insert_to_path(prepared_match, + split(query_name..'.processed_range'), + {start_line, start_col, end_line, end_col}) + end end end