master
Hung 2023-06-01 08:20:36 -07:00
parent c3bfb4c48f
commit d7691ecaef
7 changed files with 192 additions and 67 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.worktree
.luarc.json

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2023 Pegasust
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

143
README.md
View File

@ -1 +1,144 @@
# Treesitter QL # Treesitter QL
A Neovim plugin allowing users to perform workspace-wise operations (highlighting,
list processing, mutation) on existing Treesitter query in Scheme.
## Features
### Buffer selection (`require('tsql').buf_match`)
* Filter logic via `buf_match.{path,filetype,ext,any}`.
#### Combinators
* With Tacit programming `BufMatch.or_(buf_match.path("world"), BufMatch.not_(buf_match.ext("txt")))`
* With method pipelines `buf_match.path("world").or(buf_match.ext("txt").not_())`
* Hybrid works too `buf_match.path("world").or(BufMatch.not(buf_match.ext("txt")))`
### Node query (`require('tsql').token_select`)
Currently support string-passthru of Treesitter query in Scheme
`token_select.from_scm("function")`
### Sink (`require('tsql').sink_by`)
* Any `{sink: fun(self, QNode[]) -> void}` works!
* Processes all workspace nodes.
* Highlight specific patterns in your text with `sink_by.highlight()`.
Clear all highlights by `require('tsql').clear_highlights()`
* Format and print your nodes with `M.sink_by.print()`. This allows you to easily inspect your nodes.
* Copy nodes to your clipboard with `M.sink_by.nvim_yank_buf()`.
#### Format (`require('tsql').format`)
- Type: `Format = fun(QNode[]): string`
- `display: Format`: Representation in a concise/DSL format. This is inspired by Rust's `Display` trait
- `dump: Format`: Pretty-print string format for Lua table. Think of this like RON for Rust,
some language-native object representation.
- `debug: Format`: Aliased from `dump` so that it's consistent with Rust's `Debug` trait
#### Pre-sink list processing
WIP
## Usage
Here's a basic example of how to use tsql.nvim:
```lua
local ts = require('tsql')
-- ts.t(<buf_match>, <ts_query>, <sink>)
-- Matches all strings in our neovim workspace.
ts.t(ts.buf_match.any(), ts.ts_query.from_scm("string"), ts.sink_by.print())
```
## Installation
Use your favorite package manager to install the plugin. For example, with [vim-plug](https://github.com/junegunn/vim-plug):
```vim
Plug 'pegasust/tsql.nvim'
```
Don't forget to run `:PlugInstall` to actually install the plugin.
## Configuration
To configure tsql.nvim, you can provide a configuration table to the `M.setup` function. Here's an example:
```lua
local tsql = require('tsql')
tsql.setup({
nvim_hl_group = "Search" -- defines the highlight group used for highlighting
})
```
By default, `nvim_hl_group` is set to "Search".
## Commands
The following commands are available:
* `:Noh` - Clear all highlights added by this plugin.
* `:lua local ts = require('tsql'); ts.t(<buf_match>, <ts_query>, <sink>)`: Perform tsql in Lua bindings
* Example: `:lua local ts = require('tsql'); ts.t(ts.buf_match.any(), ts.ts_query.from_scm("string"), ts.sink_by.print())`
* Prints all strings in all buffers reachable from `nvim`
* Note that `ts.sink_by.print()` will use `ts.format.default`, which is `ts.format.display` without additional configurations
### TDSL (Work in progress)
* `:Tdsl */scm:string/p` - DSL without interacting with Lua API. This example
prints all strings on all buffers in default (display) format
#### Advanced example (`feat:TDSL` + `feat:list_process`)
Highlight all strings within the current buffer that has more than one occurences
```
:Tdsl bufnr:0/scm:string/group_by(t:qnode:text) | values | filter(count | ge(2)) | flatten | h
```
If you're a FP nerd, power to you! Here's the breakdown of the pre-sink processing:
- `group_by`: Group items in a list based on a key returned by a function.
Function Signature: `group_by(func: (item: T) -> K, list: T[]) -> Map<K, T[]>`
- `flatten`: Flatten a list of lists into a single list. `flatten([[a], [b, c], []]) -> [a, b, c]`
Function Signature: `flatten(list: T[][]) -> T[]`
- `values`: Creates an iterator that goes through all values of a map (created by `group_by` in this case)
Function Signature: `values(map: Map<K, V>) -> V[]`
- `filter_map`: Return a new list containing only the items where the given function maps to non-null
Function Signature: `filter_map(fn: (item: T) -> Option<T>) -> (T[] -> T[])`
- `some_if`: Lifts a predicate (`T -> bool`) into an "option predicate": `T -> Option<T>`
Function Signature: `some_if(fn: (item: T) -> bool) -> (T -> Option<T>)`
- `count`: Counts the number of elements in an interable
Function Signature: `count(list: T[]) -> number`
- `ge`: A higher-order function to compare if a number is greater or equal to a set number. `ge(2)(3) == 3 >= 2`
Function Signature: `ge(lower: number) -> (number -> bool)`
## Documentation
For detailed information on each function and class, refer to the source code.
It contains extensive inline documentation that should be enough to understand each part of the plugin.
## Contribution
If you want to contribute to the development of tsql.nvim, feel free to open a pull request.
## License
Tsql.nvim is distributed under the MIT license. See the LICENSE file in the repository for details.

View File

@ -1,64 +0,0 @@
{
"workspace.library": [
"/nix/store/7mwww77s7jads5ymvn9vv11vkywfh1sp-vim-pack-dir",
"/Users/hungtran/.local/share/nvim/plugged/plenary.nvim",
"/Users/hungtran/.local/share/nvim/plugged/nlua.nvim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-treesitter",
"/Users/hungtran/.local/share/nvim/plugged/nvim-treesitter-textobjects",
"/Users/hungtran/.local/share/nvim/plugged/telescope.nvim",
"/Users/hungtran/.local/share/nvim/plugged/telescope-fzf-native.nvim",
"/Users/hungtran/.local/share/nvim/plugged/telescope-file-browser.nvim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-lspconfig",
"/Users/hungtran/.local/share/nvim/plugged/cmp-nvim-lsp",
"/Users/hungtran/.local/share/nvim/plugged/cmp-path",
"/Users/hungtran/.local/share/nvim/plugged/cmp-buffer",
"/Users/hungtran/.local/share/nvim/plugged/cmp-cmdline",
"/Users/hungtran/.local/share/nvim/plugged/nvim-cmp",
"/Users/hungtran/.local/share/nvim/plugged/lspkind-nvim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-yati",
"/Users/hungtran/.local/share/nvim/plugged/yang.vim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-autopairs",
"/Users/hungtran/.local/share/nvim/plugged/nvim-ts-autotag",
"/Users/hungtran/.local/share/nvim/plugged/guess-indent.nvim",
"/Users/hungtran/.local/share/nvim/plugged/Comment.nvim",
"/Users/hungtran/.local/share/nvim/plugged/gitsigns.nvim",
"/Users/hungtran/.local/share/nvim/plugged/vim-fugitive",
"/Users/hungtran/.local/share/nvim/plugged/mason.nvim",
"/Users/hungtran/.local/share/nvim/plugged/mason-lspconfig.nvim",
"/Users/hungtran/.local/share/nvim/plugged/harpoon",
"/Users/hungtran/.local/share/nvim/plugged/neogit",
"/Users/hungtran/.local/share/nvim/plugged/trouble.nvim",
"/Users/hungtran/.local/share/nvim/plugged/vim-dispatch",
"/Users/hungtran/.local/share/nvim/plugged/vim-jack-in",
"/Users/hungtran/.local/share/nvim/plugged/vim-dispatch-neovim",
"/Users/hungtran/.local/share/nvim/plugged/conjure",
"/Users/hungtran/.local/share/nvim/plugged/nvim-jqx",
"/Users/hungtran/.local/share/nvim/plugged/nvim-surround",
"/Users/hungtran/.local/share/nvim/plugged/rust-tools.nvim",
"/Users/hungtran/.local/share/nvim/plugged/inlay-hints.nvim",
"/Users/hungtran/.local/share/nvim/plugged/gruvbox",
"/Users/hungtran/.local/share/nvim/plugged/lualine.nvim",
"/Users/hungtran/.local/share/nvim/plugged/indent-blankline.nvim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-web-devicons",
"/Users/hungtran/.local/share/nvim/plugged/hlargs.nvim",
"/Users/hungtran/.local/share/nvim/plugged/todo-comments.nvim",
"/Users/hungtran/.local/share/nvim/plugged/nvim-treesitter-context",
"/Users/hungtran/.local/share/nvim/plugged/playground",
"/Users/hungtran/.local/share/nvim/plugged/cmp_luasnip",
"/Users/hungtran/.local/share/nvim/plugged/LuaSnip",
"/Users/hungtran/.local/share/nvim/plugged/zk-nvim",
"/Users/hungtran/.local/share/nvim/plugged/vim-caser",
"/Users/hungtran/.config/nvim",
"/Users/hungtran/.local/share/nvim/site",
"/nix/store/2xr2f568qzslyfmb7zw3sdp8kdv0w7qi-neovim-unwrapped-236c207/share/nvim/runtime",
"/nix/store/2xr2f568qzslyfmb7zw3sdp8kdv0w7qi-neovim-unwrapped-236c207/lib/nvim",
"/Users/hungtran/.local/share/nvim/plugged/nlua.nvim/after",
"/Users/hungtran/.local/share/nvim/plugged/cmp-nvim-lsp/after",
"/Users/hungtran/.local/share/nvim/plugged/cmp-path/after",
"/Users/hungtran/.local/share/nvim/plugged/cmp-buffer/after",
"/Users/hungtran/.local/share/nvim/plugged/cmp-cmdline/after",
"/Users/hungtran/.local/share/nvim/plugged/playground/after",
"/Users/hungtran/.local/share/nvim/plugged/cmp_luasnip/after",
"${3rd}/luassert/library"
]
}

View File

@ -17,11 +17,21 @@ M.sink_by = {}
---@alias Format fun(nodes: QNode[]): string ---@alias Format fun(nodes: QNode[]): string
M.format = {} M.format = {}
M.format.default = M.format.display
---@class Sink ---@class Sink
---@field sink fun(self, nodes: QNode[]) ---@field sink fun(self, nodes: QNode[])
M.Sink = {} M.Sink = {}
M.Sink.__index = M.Sink M.Sink.__index = M.Sink
---@return Sink
---@param func fun(self, nodes:QNode[])
function M.sink_by.pure_fn(func)
return setmetatable({
sink = func
}, M.Sink)
end
---@return Sink ---@return Sink
function M.sink_by.highlight() function M.sink_by.highlight()
return setmetatable({ return setmetatable({
@ -72,9 +82,14 @@ function M.format.dump(nodes)
return vim.inspect(nodes, { newline = '\n', indent = ' ' }) return vim.inspect(nodes, { newline = '\n', indent = ' ' })
end end
---@param format Format M.format.debug = M.format.dump
---@param format Format | nil
---@return Sink ---@return Sink
function M.sink_by.print(format) function M.sink_by.print(format)
if format == nil then
format = M.format.default
end
return setmetatable({ return setmetatable({
---@type fun(nodes: QNode[]) ---@type fun(nodes: QNode[])
sink = function(nodes) sink = function(nodes)
@ -86,6 +101,9 @@ end
---@param format Format ---@param format Format
---@return Sink ---@return Sink
function M.sink_by.nvim_yank_buf(format) function M.sink_by.nvim_yank_buf(format)
if format == nil then
format = M.format.default
end
return setmetatable({ return setmetatable({
---@type fun(nodes: QNode[]) ---@type fun(nodes: QNode[])
sink = function(nodes) sink = function(nodes)
@ -209,6 +227,9 @@ function M.setup(config)
M.config.nvim_ns = vim.api.nvim_create_namespace("tsql") M.config.nvim_ns = vim.api.nvim_create_namespace("tsql")
vim.api.nvim_create_user_command("Noh", M.clear_highlights) vim.api.nvim_create_user_command("Noh", M.clear_highlights)
vim.api.nvim_create_user_command("Tdsl", function(cmd)
M.s(cmd.args):do_nvim(M.store)
end)
M.store = M.Store:new() M.store = M.Store:new()
end end

View File

@ -104,6 +104,10 @@ function M.buf_match.filetype(...)
end) end)
end end
function M.buf_match.any()
return M.BufMatch.new(function(_) return true end)
end
---@vararg string OR for path ---@vararg string OR for path
---@return BufMatch ---@return BufMatch
function M.buf_match.path(...) function M.buf_match.path(...)

View File

@ -39,12 +39,12 @@ function M.TSQuery:find_nodes(files)
local tree = parser:parse()[1] local tree = parser:parse()[1]
local root = tree:root() local root = tree:root()
---@type Query ---@type Query
local query = vim.treesitter.parse_query(file.lang, self.query) local query = vim.treesitter.query.parse_query(file.lang, self.query)
for _, match, _ in query:iter_matches(root, file.bufnr, 0, -1) do for _, match, _ in query:iter_matches(root, file.bufnr, 0, -1) do
for id, node in pairs(match) do for id, node in pairs(match) do
local start_row, start_col, end_row, end_col = node:range(false) local start_row, start_col, end_row, end_col = node:range(false)
local start = { row_0 = start_row, col_0 = start_col } local start = { row_0 = start_row, col_0 = start_col }
-- NOTE: Will need to validate that this is correct to be exclusive -- CORRECTNESS:
-- :lua local parser = vim.treesitter.get_parser(0, 'lua'); local tree = parser:parse()[1]; local query = vim.treesitter.parse_query('lua', '(identifier) @name'); for id, node in query:iter_captures(tree:root(), 0) do local name = query.captures[id]; if name == 'name' and vim.treesitter.get_node_text(node, 0) == 'TSQuery' then local sr, sc, er, ec = node:range(); print(string.format("TSQuery Start: (%d, %d), End: (%d, %d)", sr, sc, er, ec)); end; end -- :lua local parser = vim.treesitter.get_parser(0, 'lua'); local tree = parser:parse()[1]; local query = vim.treesitter.parse_query('lua', '(identifier) @name'); for id, node in query:iter_captures(tree:root(), 0) do local name = query.captures[id]; if name == 'name' and vim.treesitter.get_node_text(node, 0) == 'TSQuery' then local sr, sc, er, ec = node:range(); print(string.format("TSQuery Start: (%d, %d), End: (%d, %d)", sr, sc, er, ec)); end; end
local end_ex_col = { row_0 = end_row, col_0 = end_col } local end_ex_col = { row_0 = end_row, col_0 = end_col }
local qnode = { buf = file, start = start, end_ex_col = end_ex_col } local qnode = { buf = file, start = start, end_ex_col = end_ex_col }