Code Monkey home page Code Monkey logo

ts-node-action's Introduction

TS Node Action

A framework for running functions on Tree-sitter nodes, and updating the buffer with the result.

cycle case

multiline

condition formatting

ternerizing

operator flipflop

split join blocks

Installation

Lazy.nvim:

{
    'ckolkey/ts-node-action',
     dependencies = { 'nvim-treesitter' },
     opts = {},
},

packer:

use({
    'ckolkey/ts-node-action',
     requires = { 'nvim-treesitter' },
     config = function()
         require("ts-node-action").setup({})
     end
})

Note

It's not required to call require("ts-node-action").setup() to initialize the plugin, but a table can be passed into the setup function to specify new actions for nodes or additional langs.

Usage

Bind require("ts-node-action").node_action to something. This is left up to the user.

For example, this would bind the function to K:

vim.keymap.set(
    { "n" },
    "K",
    require("ts-node-action").node_action,
    { desc = "Trigger Node Action" },
)

If tpope/vim-repeat is installed, calling node_action() is dot-repeatable.

If setup() is called, user commands :NodeAction and :NodeActionDebug are defined.

See available_actions() below for how to set this up with LSP Code Actions.

Configuration

The setup() function accepts a table that conforms to the following schema:

{
    ['*'] = { -- Global table is checked for all langs
        ["node_type"] = fn,
        ...
    },
    lang = {
        ["node_type"] = fn,
        ...
    },
    ...
}
  • lang should be the treesitter parser lang, or '*' for the global table
  • node_type should be the value of vim.treesitter.get_node_at_cursor()

A definition on the lang table will take precedence over the * (global) table.

Multiple Actions for a Node Type

To define multiple actions for a node type, structure your node_type value as a table of tables, like so:

["node_type"] = {
  { function_one, name = "Action One" },
  { function_two, name = "Action Two" },
}

vim.ui.select will use the value of name to when prompting you on which action to perform.

If you want to bypass vim.ui.select and instead just want all actions to be applied without prompting, you can pass ask = false as an argument in the node_type value. Using the same example as above, it would look like this:

["node_type"] = {
  { function_one, name = "Action One" },
  { function_two, name = "Action Two" },
  ask = false,
}

Writing your own Node Actions

All node actions should be a function that takes one argument: the tree-sitter node under the cursor.

You can read more about their API via :help tsnode

This function can return one or two values:

  • The first being the text to replace the node with. The replacement text can be either a "string" or { "table", "of", "strings" }. With a table of strings, each string will be on it's own line.

  • The second (optional) returned value is a table of options. Supported keys are: cursor, callback, format, and target.

Here's how that can look.

{
  cursor   = { row = 0, col = 0 },
  callback = function() ... end,
  format   = true,
  target   = <tsnode>
}

Options

cursor

If the cursor key is present with an empty table value, the cursor will be moved to the start of the line where the current node is (row = 0 col = 0 relative to node start_row and start_col).

callback

If callback is present, it will simply get called without arguments after the buffer has been updated, and after the cursor has been positioned.

format

Boolean value. If true, will run = operator on new buffer text. Requires indentexpr to be set.

target

TSNode. If present, this node will be used as the target for replacement instead of the node under your cursor.

Here's a simplified example of how a node-action function gets called:

local action = node_actions[lang][node:type()]
local replacement, opts = action(node)
replace_node(node, replacement, opts or {})

API

require("ts-node-action").node_action() Main function for plugin. Should be assigned by user, and when called will attempt to run the assigned function for the node your cursor is currently on.


require("ts-node-action").debug() Prints some helpful information about the current node, as well as the loaded node actions for all langs


require("ts-node-action").available_actions() Exposes the function assigned to the node your cursor is currently on, as well as its name


null-ls Integration

Users can set up integration with null-ls and use it to display available node actions by registering the builtin ts_node_action code action source

local null_ls = require("null-ls")
null_ls.setup({
  sources = {
    null_ls.builtins.code_actions.ts_node_action,
    ...
  }
})

This will present the available node action(s) for the node under your cursor alongside your lsp/null-ls code actions.


Helpers

require("ts-node-action.helpers").node_text(node)
@node: tsnode
@return: string

Returns the text of the specified node.


require("ts-node-action.helpers").node_is_multiline(node)
@node: tsnode
@return: boolean

Returns true if node spans multiple lines, and false if it's a single line.


require("ts-node-action.helpers").padded_node_text(node, padding)
@node: tsnode
@padding: table
@return: string

For formatting unnamed tsnodes. For example, if you pass in an unnamed node representing the text ,, you could pass in a padding table (below) to add a trailing whitespace to , nodes.

{ [","] = "%s " }

Nodes not specified in table are returned unchanged.

Builtin Actions

Cycle Case

require("ts-node-action.actions").cycle_case(formats)
@param formats table|nil

formats param can be a table of strings specifying the different formats to cycle through. By default it's

{ "snake_case", "pascal_case", "screaming_snake_case", "camel_case" }

A table can also be used in place of a string to implement a custom formatter. Every format is a table that implements the following interface:

  • pattern (string)
  • apply (function)
  • standardize (function)

pattern

A Lua pattern (string) that matches the format

apply

A function that takes a table of standardized strings as it's argument, and returns a string in the format

standardize

A function that takes a string in this format, and returns a table of strings, all lower case, no special chars. ie:

    standardize("ts_node_action") -> { "ts", "node", "action" }
    standardize("tsNodeAction")   -> { "ts", "node", "action" }
    standardize("TsNodeAction")   -> { "ts", "node", "action" }
    standardize("TS_NODE_ACTION") -> { "ts", "node", "action" }

[!NOTE] The order of formats can be important, as some identifiers are the same for multiple formats. Take the string 'action' for example. This is a match for both snakecase _and camel_case. It's therefore important to place a format between those two so we can correctly change the string.


Builtin actions are all higher-order functions so they can easily have options overridden on a per-lang basis. Check out the implementations under lua/filetypes/ to see how!

(*) Ruby js/ts/tsx/jsx Lua Python PHP Rust C# JSON HTML YAML R
toggle_boolean()
cycle_case()
cycle_quotes()
toggle_multiline()
toggle_operator()
toggle_int_readability()
toggle_block()
if/else <-> ternary
if block/postfix
toggle_hash_style()
conceal_string()

Testing

To run the test suite, clone the repo and run ./run_spec. It should pull all dependencies into spec/support/ on first run, then execute the tests.

This is still a little WIP.

Contributing

If you come up with something that would be a good fit, pull requests for node actions are welcome!

Visit: https://www.github.com/ckolkey/ts-node-action

ts-node-action's People

Contributors

alexpw avatar axieax avatar bo5o avatar ckolkey avatar custompro98 avatar ericdriussi avatar fbearoff avatar fbuchlak avatar kiyoon avatar lkhphuc avatar lucastavaresa avatar matthias-q avatar mosheavni avatar nex-s avatar orhnk avatar troysigx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ts-node-action's Issues

Configuration seems to not work for non-default languages.

Hi! First of all, thanks for the awesome plugin.
I tried configuring this for php like so

require("ts-node-action").setup({
    php = {
        ["arguments"] = require("ts-node-action").toggle_multiline,
    },
})

To check if the default action will work, but I get the:

No action defined for 'php' node type: 'arguments'

Do you have any ideas?

[Feature Request] "Complete Statement" like Intellij IDEs

  1. In Intellij IDEA, when the user presses <C-S-Enter> when the cursor is positioned on a line of the code where the line can be finished by adding a ; at the end of the line, the editor would add a semicolon to finish the statement.

  2. If the cursor is positioned on a function declaration or a function definition, <C-S-Enter> would add parentheses and brackets for the function parameters and the function body where it's appropriate, and also moves the cursor to where it's appropriate.

  3. When calling a function, IDE would add parentheses when the user presses <C-S-Enter> on the function identifier.

There may be some more use cases what IntelliJ "complete statement" action does that I'm not aware of, but if above functionalities can be implemented it would be great additions to coding experience with Neovim.

If anyone is unfamiliar with what the "complete statement action" is in IntelliJ, see https://www.jetbrains.com/idea/guide/tips/complete-statement/

Suggest: apply = on multiline actions

I noticed for split actions the new lines are not indented properly. I think this can be addressed by applying = reflow on all multiline replacements? Won't that work?

Great plugin concept by the way. It's extremely powerful and helps showcase what treesitter can really do.

Separately from this I'm hoping to contribute a toggle action for js/ts to convert a template string expression into regular strings and vice versa. Something you couldn't dare attempt with regexes!

Notice of Breaking Changes

Given the 'pre-1.0' nature of this plugin, there are going to be some breaking changes as we figure out a nice API. I'll post those changes here, so subscribe if you're interested.

feat: expand / collapse html like tag

Hi! I did some afternoon coding and implemented a fun feature for myself. I was wondering if you think this is something that could be included in ts-node-action itself.

Here is a demo:

Screen.Recording.2024-03-02.at.17.07.49.mov

If you think this might be a good addition, I might have the time to open a PR for further discussion.

Thanks!

`toggle_multiline` ignores whitespaces

I want to use toggle_multiline() in SQL therefore I have specified the two node types in my config like this:

sql = {
    ["select_expression"] = require("ts-node-action.actions").toggle_multiline(),
    ["column_definitions"] = require("ts-node-action.actions").toggle_multiline(),
},

Here is a SQL snippet and Treesitter AST as an example:

create table tab (
    a int,
    b float,
    c text
);

AST:

statement [0, 0] - [4, 2]
  create_table [0, 0] - [4, 1]
    keyword_create [0, 0] - [0, 6]
    keyword_table [0, 7] - [0, 12]
    table_reference [0, 13] - [0, 16]
      name: identifier [0, 13] - [0, 16]
    column_definitions [0, 17] - [4, 1]
      column_definition [1, 4] - [1, 9]
        name: identifier [1, 4] - [1, 5]
        type: int [1, 6] - [1, 9]
          keyword_int [1, 6] - [1, 9]
      column_definition [2, 4] - [2, 11]
        name: identifier [2, 4] - [2, 5]
        type: float [2, 6] - [2, 11]
          keyword_float [2, 6] - [2, 11]
      column_definition [3, 4] - [3, 10]
        name: identifier [3, 4] - [3, 5]
        type: keyword_text [3, 6] - [3, 10]

When I trigger toggle_multiline() to collapse the column_definitions the whitespaces between the identifier and the type are lost

result:

create table tab (aint,bfloat,ctext);

How can I fix this?

I am using the SQL grammer that is shipped with nvim-treesitter.

Deprecating vim.pretty_print()

Pre-PR

DEMO

tmp-2023-09-27_06.26.05.mp4

Note

In nvim v0.10 vim.pretty_print() will be deprecated.
use vim.print() instead

Not enabled on tsx file by default? Is this a bug?

Curious why did I have to add the tsx = line to my setup function?

local ts_node_action = require("ts-node-action")
...
ts_node_action.setup({
    tsx = ts_node_action.node_actions.typescriptreact,
})

I would assume default node actions would just work but they were not enabled until I added this.

Getting Started - Setting Keymaps for Builtin Actions

Apologies for asking such a basic question, but I'm having a hard time getting keymaps to work for the builtin functions.

I can successfully create and use the keymap for node_action, as described under Usage, e.g.:

vim.keymap.set(
    { "n" },
    "K",
    require("ts-node-action").node_action,
    { desc = "Trigger Node Action" },
)

But when I try something similar for the builtin actions, they aren't working. For example, I tried a few variations of:

vim.keymap.set(
    { "n" },
    "C",
    require('ts-node-action.actions').cycle_quotes(),
    { desc = "Cycle Quotes" },
)

My setup function is currently empty, e.g. ts_node_action.setup({}). I assumed that for the builtins, I wouldn't need to specify the languages and node types, but maybe that's my issue?

Any help is appreciated!

I'm looking for a way to hang more actions off the same node type with a direct keymap.

We can attach more than one action to a given node type, but this forces us to make a choice each time. For example I am going to author a new node action on a property_identifier for a few of the languages I use often which will grab the var name and make a simple debug print out of it in the next line of code.

But I don't want to have to delete the existing actions.cycle_case() mapped to property_identifier, and neither do i want to insert another action to make to trigger this.

I'd prefer to be able to establish different sets of node actions that are mapped to different key binds! So I could have, say, a mapping that will apply "traditional" actions like cycle case and toggle multiline/operator/quote etc, a separate mapping that applies "expanding" refactoring automations as an example, another mapping that applies "contracting" refactoring automations... etc. You get the idea.

Similar plugin: `Wansmer/treesj`

Hi!

I just came across your plugin. It's really nice! Just wanted to tell you that there is actually a similar one: Wansmer/treesj.

You may want to unite efforts to create a unified version tree-sitter node editor.

Cheers! 😏

Add setting to set possible `cycle_case` formats (+ their priority)

I would like to customize the possible cycle_case formats for variables.

So for example in JS I only use camelCase and SCREAMING_SNAKE_CASE.
I would like to set this like

actions.cycle_case({'camel_case', 'screaming_snake_case'})

For python I also need normal snake_case. However I use camelCase there very seldom.
This would look like

actions.cycle_case({'snake_case', 'screaming_snake_case', 'camel_case'})

Here it is important that we try to remember the priority.
When I execute the action on a variable SOME_VARIABLE I want to to change to the first other case option that is not it's current case option and was not tried yet. So it would change to someVariable. If I hit the action again it should change to someVariable.

It might be that this is to unintuitive. I'm also fine if I can just set the order. So we always pick the style that comes right after the current style given parameter list.
So for SOME_VARIABLE it should become someVariable.
If we go this route maybe we could also add another parameter, backwards to the cycle_case() function, that allows for a forwards and backwards mapping (Maybe <M-A>, <M-X>?)

Ideally use constants for the different styles, not as I did with strings ;-)

[Feature Request] Integrating cycle_case action with LSP's textDocument/rename

I would like to integrate the cycle_case action with LSP rename, so that I can change case of all occurrences of a function or say variable at once. Is it possible to bind a code action with another function? Also, is it possible to apply only one type of code action to a node, say maybe I want to bind a key only for the "Cycle Case" code action?

"attempt to index local 'tbl'" error on quotes

Hi, first of all, thanks for the wonderful framework.

I have configured it to work via null-ls (none-ls actually) by sources = { null_ls.builtins.code_actions.ts_node_action }

And everything I've tried works fine, except the quotes. Rather, they not only don't work, it would be half the trouble, but they lead to an error that knocks down the entire ts-node-action and it no longer works.

The error appears when I move the cursor to the quotation from either side, and a message appears:

[null-ls] failed to run generator: ...are/nvim/lazy/ts-node-action/lua/ts-node-action/init.lua:156: attempt to index local 'tbl' (a boolean value)

[Feature Request?] Integrate with Code Actions

Hi there! loving the plugin, really cool work!

I think it might be cool to integrate the node-actions into the lsp code actions.

So if my cursor is on top of a bool and I run vim.lsp.buf.code_action(), the output would also list "Toggle Boolean" as an option.

I have no idea if that is possible nor how to do it, I just though I'd leave the idea hanging ^^

ts-node-action/init.lua:130: attempt to index local 'choice' (a nil value)

My changes are summarized here: 3a0ddbd

Here is the error I get in nvim. the first 3 lines compose the menu made by vanilla ui.select:

Select Action
1: Conceal
2: Split Join
Type number and <Enter> or click with the mouse (q or empty cancels):
E5108: Error executing lua: vim/_editor.lua:420: nvim_exec2(): Vim(normal):E5108: Error executing lua ...are/nvim/lazy/ts-node-action/lua/ts-node-action/init.lua:130: attempt to index
 local 'choice' (a nil value)
stack traceback:
        ...are/nvim/lazy/ts-node-action/lua/ts-node-action/init.lua:130: in function 'on_choice'
        ...ar/neovim/HEAD-8c6b0a5/share/nvim/runtime/lua/vim/ui.lua:52: in function 'select'
        ...are/nvim/lazy/ts-node-action/lua/ts-node-action/init.lua:124: in function 'fn'
        ...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:11: in function <...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:10>
        [C]: in function 'nvim_exec2'
        vim/_editor.lua:420: in function 'cmd'
        ...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:23: in function <...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:5>
stack traceback:
        [C]: in function 'nvim_exec2'
        vim/_editor.lua:420: in function 'cmd'
        ...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:23: in function <...e/nvim/lazy/ts-node-action/lua/ts-node-action/repeat.lua:5>
Press ENTER or type command to continue

Run Node Actions in embedded code

Feature request:
So node actions are activated by filetype and not by the embedded code. I have SQL embedded in Julia/R and different languages embedded in Markdown. Would be nice to have the possibility to use node actions there as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.