Giter Site home page Giter Site logo

mickeynp / combobulate Goto Github PK

View Code? Open in Web Editor NEW
822.0 26.0 47.0 3.56 MB

Structured Editing and Navigation in Emacs with Tree-Sitter

License: GNU General Public License v3.0

Emacs Lisp 91.86% Makefile 0.11% Python 4.74% Dockerfile 0.10% HTML 0.95% TypeScript 1.66% CSS 0.59%
emacs tree-sitter

combobulate's Introduction

Structured Navigation and Editing with Combobulate

image

What is Combobulate?

Combobulate is under active development. Expect bugs.

Combobulate is a package that adds structured editing and movement to a wide range of programming languages. Unlike most programming major modes that use error-prone imperative code and regular expressions to determine what's what in your code, Combobulate uses Emacs 29's tree-sitter library. Tree-sitter maintains a concrete syntax tree of your code; it gives Combobulate absolute clarity of all aspects of your code, enabling more correct movement and editing than you would otherwise have.

image

Combobulate extends the existing editing and navigation capabilities of Emacs in addition to adding a range of new features you can't easily do in Emacs without Combobulate.

Combobulate's design philosophy is this:

  1. It must be extensible and relatively easy to add new languages.

    Open combobulate-<lang>.el to see how each mode is defined.

  2. Commands should broadly work the same across all languages.
  3. A strong emphasis on extending and enriching the existing movement and editing commands where possible.
  4. Combobulate must have a shallow learning curve.

If you want a guided tour, then I recommend you read:

What does Combobulate do exactly?

Combobulate is as a companion to the existing movement and editing facilities in Emacs. It's designed to sympathetically improve or supplant editing and movement in many major modes that are otherwise lacking or incorrect. If you're familiar with Emacs's many movement and editing commands, then Combobulate will feel right at home.

There's a large emphasis on Combobulate being both easy to use, and a natural extension to Emacs's already-powerful editing and movement commands.

image

For instance, navigating up or down list structures with C-M-u and C-M-d is much improved. Whether you're navigating in or out of structures in Python or JSX elements in Typescript or Javascript.

Combobulate also improves list-like navigation bound to C-M-n and C-M-p, as shown above also. They now understand hierarchical code much better, and they will navigate between statements in code as well as parameters in functions or key pairs in dictionaries.

image

Combobulate also adds a wide range of code editing commands, such as the ability to drag logical pieces of code up or down regardless of the size or complexity of the code. If you can navigate between elements with C-M-<p/n>, then you can drag them around with M-P and M-N.

image

Much like the popular expand regions package, Combobulate can also expand the region one syntactically interesting unit at time: first the string point is in, then the list that is in, and so on.

image

Combobulate can also place cursors (using the optional multiple cursors package) at syntactically important points in your code, like: dictionary elements; function arguments; or attributes in JSX elements.

image

You can clone code with ease. If Combobulate cannot guess the exact thing you want to clone, you'll be asked to interactively preview and pick the right node to clone.

image

Combobulate can splice your code. Any code. For instance, you can keep some of your HTML tags and splice them into the parent node, removing it in the process.

and much more!

Getting Started with Combobulate

Combobulate has to support your programming language for it to work properly. More importantly, it should ideally be the right Grammar version as the table below explains. If you use a newer, or older, version you may run into issues --- not just in Combobulate, but Emacs also!

Here is a list of the languages currently supported.

Language

Supported? Grammar Version
CSS ✅ |v 0.20.0
HTML ✅ [1] |v 0.20.1
JSON ✅ |v 0.20.2
Javascript+JSX ✅ |v 0.20.1
Typescript+TSX ✅ |v 0.20.3
Python ✅ |v 0.20.4
YAML ✅ |v 0.5.0

Don't see your language? If you want your favourite language added, then why not try it yourself? Have a look at ``combobulate-json.el`` for an example.

When you have installed Combobulate correctly -- see below -- then it'll turn on when you open a file in one of its supported major modes. If it does not do this, try M-x combobulate-mode to activate Combobulate's minor mode.

If it's working, you'll see a © appear in your mode line.

At that point, Combobulate is now working. Combobulate rebinds a wide range of common navigation and editing keys. You can see a complete list by typing M-x describe-keymap RET combobulate-key-map.

Note that Combobulate may enable or disable keys depending on the major mode it is active in.

Furthermore, Combobulate ships with a Magit-like transient UI that you can access by typing C-c o o. It exists primarily to teach you about Combobulate's capabilities: every key binding in it is also available without the popup.

[1] Either use the version built into Emacs 30, or you can download my html-ts-mode here, and read more about how to build your own tree-sitter major mode by reading Let's Write a Tree-Sitter Major Mode.

Top Tips for using Combobulate

Unlike most of Emacs's major modes and the specialized movement and editing they (may) offer, Combobulate is quite strict. It's strict about where you invoke certain commands or key bindings, and what happens when you type in different parts of the same line of code.

That is because of the concrete syntax tree. Place your point anywhere in your source code and there might be 5, 10, 15 or more "nodes" in the tree where that point intersects wildly different nodes.

For example: your point is on a string; but it's also in a list; which is in a dictionary; which is in an expression statement; which is in a for-loop; which is in a function, and so on.

Because a concrete syntax tree is so exacting and so detailed, it's hard for Combobulate to infer what you want to do with the same casual insouciance of commands that don't use a syntax tree: there are simply too many choices, and too many nodes.

You, the human, know which node you want -- but Combobulate does not necessarily know that!

So, for the best results, put your point at the beginning of the thing you want to interact with --- at least until you've gotten the hang of how Combobulate decides what it thinks you're asking for.

Finally, note that any command that edits your code is at best a "best guess" effort. Carefully scrutinize what Combobulate does after you invoke any command that edits your code.

How do I install Combobulate?

NOTE: The tree-sitter grammars can and do change. If that happens, it can cause issues in Emacs or Combobulate. If you see strange query validation errors or broken highlighting, try using an older grammar checkout from Git. See the table above or the example code below.

Combobulate is not on MELPA or any other package repository, yet. For Combobulate to install properly you must meet the following requirements:

  1. You must be running Emacs 29 or later.
  2. Your Emacs must be compiled with tree-sitter support. Read How to Get Started with Tree Sitter to learn how to compile Emacs and install tree-sitter.

    In C-h v system-configuration-features look for TREE_SITTER.

  3. You must have language grammars installed for the languages you want to use Combobulate with.

    However, you can optionally ask Emacs to download, compile and install these language grammars, but you'll need a suitable C compiler. If you're using Linux, then no problem. Non-Linux users may need to install or configure their operating system to do this.

  4. You're interested in using Combobulate with one of the supported languages you saw in the table above.

    (Adding support for new languages is reasonably easy though!)

  5. You have a git checkout of Combobulate ready.

There are two code snippets below that will help you get started. One assumes you don't know how to install and set up tree-sitter grammars, whereas the other one does.

Complete Example with Tree-Sitter Grammar Installation

This is a complete example and is for illustration only.

Note that this example uses major-mode-remap-alist to turn your regular major modes into the tree-sitter-enabled modes. You can always undo the changes made to this variable to return to what you had before.

;; `M-x combobulate' (default: `C-c o o') to start using Combobulate
(use-package treesit
  :mode (("\\.tsx\\'" . tsx-ts-mode))
  :preface
  (defun mp-setup-install-grammars ()
    "Install Tree-sitter grammars if they are absent."
    (interactive)
    (dolist (grammar
              '((css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.20.0"))
                (html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.20.1"))
                (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.20.1" "src"))
                (json . ("https://github.com/tree-sitter/tree-sitter-json" "v0.20.2"))
                (python . ("https://github.com/tree-sitter/tree-sitter-python" "v0.20.4"))
                (toml "https://github.com/tree-sitter/tree-sitter-toml")
                (tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src"))
                (typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src"))
                (yaml . ("https://github.com/ikatyang/tree-sitter-yaml" "v0.5.0"))))
      (add-to-list 'treesit-language-source-alist grammar)
      ;; Only install `grammar' if we don't already have it
      ;; installed. However, if you want to *update* a grammar then
      ;; this obviously prevents that from happening.
      (unless (treesit-language-available-p (car grammar))
        (treesit-install-language-grammar (car grammar)))))

  ;; Optional, but recommended. Tree-sitter enabled major modes are
  ;; distinct from their ordinary counterparts.
  ;;
  ;; You can remap major modes with `major-mode-remap-alist'. Note
  ;; that this does *not* extend to hooks! Make sure you migrate them
  ;; also
  (dolist (mapping
         '((python-mode . python-ts-mode)
           (css-mode . css-ts-mode)
           (typescript-mode . typescript-ts-mode)
           (js2-mode . js-ts-mode)
           (bash-mode . bash-ts-mode)
           (css-mode . css-ts-mode)
           (json-mode . json-ts-mode)
           (js-json-mode . json-ts-mode)))
    (add-to-list 'major-mode-remap-alist mapping))
  :config
  (mp-setup-install-grammars)
  ;; Do not forget to customize Combobulate to your liking:
  ;;
  ;;  M-x customize-group RET combobulate RET
  ;;
  (use-package combobulate
    :preface
    ;; You can customize Combobulate's key prefix here.
    ;; Note that you may have to restart Emacs for this to take effect!
    (setq combobulate-key-prefix "C-c o")

    ;; Optional, but recommended.
    ;;
    ;; You can manually enable Combobulate with `M-x
    ;; combobulate-mode'.
    :hook
      ((python-ts-mode . combobulate-mode)
       (js-ts-mode . combobulate-mode)
       (html-ts-mode . combobulate-mode)
       (css-ts-mode . combobulate-mode)
       (yaml-ts-mode . combobulate-mode)
       (typescript-ts-mode . combobulate-mode)
       (json-ts-mode . combobulate-mode)
       (tsx-ts-mode . combobulate-mode))
    ;; Amend this to the directory where you keep Combobulate's source
    ;; code.
    :load-path ("path-to-git-checkout-of-combobulate")))

Simple Combobulate Setup

(use-package treesit
  :mode (("\\.tsx\\'" . tsx-ts-mode))
  :config
  ;; Do not forget to customize Combobulate to your liking:
  ;;
  ;;  M-x customize-group RET combobulate RET
  ;;
  (use-package combobulate
    :preface
    ;; You can customize Combobulate's key prefix here.
    ;; Note that you may have to restart Emacs for this to take effect!
    (setq combobulate-key-prefix "C-c o")
    :hook
      ((python-ts-mode . combobulate-mode)
       (js-ts-mode . combobulate-mode)
       (html-ts-mode . combobulate-mode)
       (css-ts-mode . combobulate-mode)
       (yaml-ts-mode . combobulate-mode)
       (typescript-ts-mode . combobulate-mode)
       (json-ts-mode . combobulate-mode)
       (tsx-ts-mode . combobulate-mode))
    ;; Amend this to the directory where you keep Combobulate's source
    ;; code.
    :load-path ("path-to-git-checkout-of-combobulate")))

combobulate's People

Contributors

chriselrod avatar damiencassou avatar guillaumebrunerie avatar josteink avatar lechten avatar mickeynp avatar phuhl avatar ziqi-yang 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

combobulate's Issues

Python hierarchical navigation blocked into body when `if` and `for` have function call in condition / right-side

Using hierarchical navigation tools, there is no way to get from the for y in f(x): line into the body (pass) of the following buffer state:

|for y in f(x):
    pass
t = 1

combobulate-navigate-next (C-M-n) takes me t = 1. That seems fine.

combobulate-navigate-down-list-maybe (C-M-d) takes me here:

for y in |f(x):
    pass
t = 1

combobulate-navigate-next (C-M-n) takes me nowhere from this point (if it supposed to).

The same generally occurs for a line like:

if f(x):
    pass

...I can always use linear and s-exp commands to get into the body, but I would have thought hierarchical tools would let me get there. They do in the following case:

|for y in fx:
    pass
# => combobulate-navigate-down-list-maybe (C-M-d) =>
for y in fx:
    |pass

GNU Emacs 30.0.50 (master), Combobulate master@5806ad7

Migrate to builtin treesit.el becoming available in Emacs 29.1

You should either migrate (or also support) treesit.el module when it's available in Emacs 29.1. The API is similar but not equivalent.

Typically you

  • replace (require 'tree-sitter) with (require 'treesit
  • replace tsc- with treesit-

Moreover, treesit has the semantic difference that a node name is represented as a string instead of a symbol.

Moreover treesit only exposes an api for char offsets (no bytes offsets) whereas tree-sitter exposes both.

There might be more differences but these are the ones I remember right now.

logical next / logical prev: what is it supposed to do?

Given this TSX code:

export default function Test() {
  return (
    <>
      <strong>abc</strong>
      <strong>def</strong>
      <strong>hij</strong>
    </>
  );
}

When I place the point at (0,0) and invoke combobulate-navigate-logical-next via M-e it moves to abc. Pressing it again moves it behind the first </strong>. Further invocations move it to each text fragment and then to the corresponding </strong>.

To be honest I fail to see the the intended use case. I would have guessed that it moves from expression to expression, so e.g. from export to return to <> to <strong> to abc etc, pp. But obviously I am wrong. But is the current behaviour intended?

Guide for adding new languages

Hello,
looks like an awesome package, and I would love to use it for structual navation in Julia and orgmode files.

Since the goal this project is to "relatively easy to add new languages" I gave it a shot, starting off with the combobulate-python.el file, and removing (for now) everything related to indentation.

However, I'm running into some confusions. Several times, the function combobulate-production-rules-get loads information from several json files (e.g. python.json, python-grammar.json, python-nodes.json), which have to be written and somehow processed with the build-relationships.py` script (?)

For now, I would just like to have a minimum viable version of combobulate that supports basic navigation like traversing the tree up/down/sidewards/etc. Since I understand elements like treesit-node-next-sibling etc are all implemented, I imagine I should be able to get a version of combobulate-julia.el going without touching any .json files at all. Is that correct?

Perhaps it would be helpful to provide a language-agnostic version combobulate-anylang.el that gets started with basic navigation functionality, and which can further be refined with custom rules etc etc.

Thanks!

Follow Python Decorator Spec

Python decorators are prefixed with a @ (PEP 318, Python3 Reference)

I did not want to create a PR for such a simple change, so here's the diff:

diff --git a/combobulate-python.el b/combobulate-python.el
index 3428901..6cb6e78 100644
--- a/combobulate-python.el
+++ b/combobulate-python.el
@@ -243,7 +243,7 @@ again to cycle indentation."))))
            :mark-node nil
            :nodes ("function_definition" "class_definition")
            :name "decorate"
-           :template ((p "Decorator name: ")
+           :template ("@" (p "Decorator name: ")
                       n>))
           (:description
            "if ...:"

While testing this, I noticed that tempo.el does not have graceful exits. So, typing C-g while being prompted for this envelope would insert the "@" character anyways.

New architecture

Problem

I see several problems with the way combobulate's code is split into files:

  1. There is no library of functions another elisp hacker could build on without adding a dependency on combobulate's user interface.
  2. Combobulate decides which language is used in a given buffer by looking at the current major-mode. This is not good enough for Javascript vs. Typescript.
  3. combobulate.el requires language-specific combobulate's file, making it awkward for an external package (or an end-user) to specify another language or tweak an existing one.

Possible solution

I suggest a different architecture:

  • libcombobulate.el defines movement commands (e.g., libcombobulate-navigate-up), utility functions (e.g., libcombobulate-get-nearest-navigable-node) and variables (e.g., libcombobulate-navigation-node-types). This file shouldn't say anything about how the user will use combobulate (e.g., no key-bindings, no user interface).
  • libcombobulate-javascript.el, libcombobulate-python.el, ... define language-specific functions and values (for the variables of libcombobulate.el). These files shouldn't say anything about how the user will use combobulate (e.g., no key-bindings, no user interface).
  • avy-combobulate.el, hydra-combobulate.el, multiple-cursors-combobulate.el, ... build on top of libcombobulate.el and an external package.

This architecture doesn't say where to define key bindings. There are several solutions:

  1. The user is responsible for doing that (might make sense early in the project's life) and the README gives an example setup.
  2. A combobulate.el file (depending only on libcombobulate.el) defines all general-purpose key bindings and the user must setup key bindings for the language-specific commands.
  3. Each language-specific file defines its own minor-mode (e.g., combobulate-javascript-mode) with its own key bindings; general-purpose key bindings can be defined in a combobulate-core.el file that all language-specific files import.
  4. There are other solutions.

This is an architectural pattern I've followed for several projects and it always helped me. For example:

I usually decompose my project into several git repositories but that's not mandatory (I just like the project to have clear boundaries).

tsx or javascript?

Hi,

I'm new to the tree-sitter world and I just saw this line in combobulate.el:

(add-to-list 'tree-sitter-major-mode-language-alist '(js2-mode . tsx))

Why using "tsx" as language instead of plain Javascript?

In a plain old JavaScript buffer, should I still use combobulate-setup-js-ts?

Suggestion: would it be possible to "move point back" after cancelling expand region?

I'm trying to use combobulate to replace the expand-region package, and mostly this is working very well. Still there are one or two things on my wish-list.

However, I want to make it clear that I already appreciate the package a lot as it stands, and don't take any additional features as a given. If you think that these suggestions will only lead to feature-creep and aren't aligned with the goals of combobulate, I have the fullest respect for that.

With that said, a general annoyance with using combobulate to expand the region (i.e. combobulate-mark-node-dwim, bound to M-h) is how "irreversible" it feels, especially compared to expand-region.el. Once you expand the region, there is no way to "go back" or shrink the region, and even if you just deactivate the region to potentially start from scratch, the point has been moved. This means you have to pop the mark (C-u C-SPC) one or several (!) times to get back to where you started.

One concrete feature that would nicely address the latter problem would be to ensure that the point is returned to where it was before expanding the region when a keyboard-quit (C-g) is detected immediately after combobulate-mark-node-dwim. As far as my skills go this wouldn't be trivial to implement, and I imagine one might have to enter some kind of minor mode with its own keymap upon combobulate-mark-node-dwim in order to "catch" the keyboard-quit, but I'm by no means an expert and maybe I'm missing something.

Another cool feature would be the ability to "shrink" the region, but this would again entail keeping track of some kind of state in terms of where the point was located before the region was expanded. If you rather prefer to keep the operations of combobulate as pure and state-less as possible, I understand.

Thanks for the great package, it's really useful!

Wrap TSX / JSX Elements

Great stuff. As a big fan of things like lispy, this is a step in the right direction. Very in-line with how Emacs works I think. I'm using combobulate already for TSX files. Works great. Thanks!

The README mentions :

....a prefix map of mode-specific transformations. Currently only used in Javascript and Typescript modes to vanish/wrap JSX elements

I couldn't find a generic "wrap element" command, ie to wrap in an arbitrary tag like <h1>, <div> etc. Did I miss it? If not I'd be happy to add.

combobulate-splice-up throws an error

Given the code below in js-ts-mode:

function f() {
    console.log("foo");
    console.log("bar");
}

If point is the the beginning of console.log("bar"); and I ask for combobulate-splice-up, I get an error message:

combobulate-procedure-start: There are no valid procedures that apply to ‘Expression Statement’.

I would expect the result to be:

function f() {
    console.log("bar");
}

query-builder feedback

Playing with the new query builder: such a powerful tool. It's clear you spent a long while getting the interface just right.

A few things I noticed:

  • I don't know how to quit the query-builder. Just kill the buffer? A q binding on the transient would be nice to quit and clean-up (and maybe prompt to save the query on the query ring).
  • As you experiment, often the positive results of older queries are not erased. I.e. highlighting remains.
  • The completion of types is simply wonderful. Field names for given types also work, but not based on the text in-buffer. I.e. in (dictionary (pair [X]) @hl.silver), with point after the pair , M-Tab gives a list of fields, but entering a fragment of one and completing ((dictionary (pair ke[X]) @hl.silver)) does not.
  • Would be great if @before/@after etc. were presented as capturing completions, in addition to the highlight colors.
  • A binding for M-g p/n to move to the previous/next known error position (if any) would be lovely.

Just started playing; thanks again for this powerful tool.

Python `case` next/previous (sibling) navigation doesn't appear to work

combobulate-navigate-next (C-M-n) does not move the cursor to the beginning of the next case, in the following Python buffer state:

match 1:
    |case 1:
        pass
    case _:
        pass

The expectation is:

match 1:
    case 1:
        pass
    |case _:
        pass

combobulate-navigate-previous (C-M-p) exhibits similar behaviour with the latter buffer state - the cursor does not move, when I would expect it to navigate back to the beginning of the first case.


GNU Emacs 30.0.50 (master), Combobulate master@2e11f42

Navigate backward has unexpected behavior

Having this code:

const Test = () => {
  return (
    <div>
      <p>Test</p>
      <br/>
    </div>
  )
};

My cursor is behind <br/>. When I run combobulate-navigate-backward I expected the cursor to go e.g. to <p>Test|</p> or maybe to |<p>Test</p> but in fact it goes to |</div> (i.e. forwards?).

Am I misunderstanding what combobulate-navigate-backward should do or is this a bug?

Go support

I'm interested in contributing to add at least some minimal support or a starting point for Go.

This is both because I enjoy using Emacs to write Go code, but also because I finally want to learn Emacs Lisp. If this is too complicated for a First Emacs Lisp project then please let me know, but I'm definitely interested in giving it a shot.

Tree-edit collaboration

Hi,

This package looks very cool! I'm glad to see more developments in the structural editing space outside of the lisps. As I mentioned earlier, I'm working on a similar package tree-edit that aims to provide structural editing via. tree-sitter. It has an evil editing package, evil-tree-edit, but the core has been separated into a tree-edit package which is a library supporting some more advanced structural editing operations like raising, slurp/barf, etc. by partially implementing a parser. I'm not sure what your long-term goals are for this project, but what are your thoughts on having a shared library between the two projects, either in tree-edit the library or some other form? If you think the projects are similar enough it might be benefical to collaborate rather than having two parallel implementations going. Let me know what you think!

Option to disable keybindings

Having an easy option to disable all the keybinds would really help people with non standard keybinds (vim-like, kakoune-like, traditional desktop, entirely custom...) get into using this.

Setting up a full set of evil like binds might be a bit out of scope for this project, but just having a variable to disable it from overriding a bunch of binds on load would be really nice and let us then write keymaps that fit better.

Repeat keys with repeat-mode

I know the package is still under active development, but it has already proven so useful for my use case so thanks for that!

I have a question (or maybe a feature request): is it possible to make the combobulate functions repeatable with repeat-mode? I am thinking specifically of C-M-f/b/u/d/p/n/a/e (and repeat-mode to remove the C-M part from any consecutive functions called).

In TSX, attribute envelop should not enter quotes

In TSX, when entering an attribute, "" are entered automatically:

<Component prop_|_

Pressing = will produce

<Component prop="_|_"

However, in many cases the prop values will be expressions like <Component prop={...} so it would be better to do that.

This can be disabled with combobulate-js-ts-enable-attribute-envelopes but I think it should be default to nil for TSX.

Unwrap envelope

I found the envelope functions very nice to use. But I did not find a way to unwrap something.

I.e. when using M-( to wrap <div/> in parenthesis it would be great to also have M-) to remove the parenthesis again. Similar for the other envelope types.

Move inside empty node

Hi, this package is great stuff. Just starting to use it.

I noticed one usecase that seems unsupported (or I just didn't find it?): Moving into an empty node, eg.:

<div></div>

The cursor is before the div and I want to insert something into it. Using combobulate-navigate-down refuses to go in, as there is no node in there.

Cheers

Can't shut combobulate off/only use in some buffers

Hi! I'm enjoying using combobulate-mode in python-ts-mode buffers. However, for various reasons, I can't use it in all Python buffers. But turning combobulate-mode on makes keybinding changes that turning combobulate-mode off doesn't undo. How hard would it be provide a combobulate-teardown function, that does the opposite of combobulate-setup-1?

It's a little more complicated than that, though -- just turning combobulate-mode on in one Python buffer causes TAB to fail in all other Python buffers without combobulate running:

if: There are no production rules in ‘combobulate-navigation-rules’.

If you are adding a new language, ensure the grammar files are:

1. Properly built using ‘build/build-relationships.py’, and that you have sourced the ‘grammar.json’ and ‘node-types.json’ files.
2. That the rebuilt ‘combobulate-rules.el’ file is evaluated in your Emacs session.
3. That the name of the grammar matches that in ‘combobulate-rules.el’.

So once I've used combobulate-mode in one buffer, I need to enable it in all Python buffers. It would be nice if this didn't happen.

Thanks!
Eric

Cycling yaml indentations

It would be nice to have a function similar to combobulate-python-indent-for-tab-command in yaml (probably called combobulate-yaml-indent-for-tab-command).

I am not too familiar with neither combobulate nor treesit yet, but if you have some pointers about how to implement it I could give it a try.

Indentation cycling not working in python

Hi, and thanks for the amazing package.

I just noticed that indentation cycling isn't working as intended for me; a video says more than a thousand words:
Screencast from 2023-02-01 18-20-38.webm

I'm using a recent build of emacs, and a fairly plain built-in eglot + python-ts-mode setup for developing python. However, if it's not clear where this issue is coming from, I'd be more than happy to reproduce it in a more bare-bones setting with emacs -Q.

Trying to remove <> tag in JSX removes wrong tag

Hey there, just stumbled upon a bug:

When having this JSX

<body>
  <>
    <div>Test</div>
  </>
</body>

with the cursor before <>, then running combobulate-javascript-jsx-vanish-node does not remove the <> tags but the body tag.

I wonder how that happens, usually I'd say a malformed regex but as this is based on tree-sitter... 🤔

Cheers

Python `class` not containing functions cause navigation failures

combobulate-navigate-beginning-of-defun (C-M-a), combobulate-navigate-next (C-M-n), combobulate-navigate-previous (C-M-p), all fail when encountering the following functionless class:

class X:
    v = 0

The message returned is: Not enough arguments for format string.

This does not occur when X is given a function member. The following behaves as expected:

class X:
    v = 0
    def _(): pass

GNU Emacs 30.0.50 (master), Combobulate master@2e11f42

Minor `combobulate-navigate-end-of-defun` behavior mismatch with other packages

This is minor but worth documenting, as Combobulate drops in its own end-of-defun on top of treesit's.

Combobulate's combobulate-navigate-end-of-defun (C-M-e) does not behave exactly like treesit-end-of-defun (and Emacs' end-of-defun in Lispier modes).

Given initial buffer state:

|def f():
    pass
# Comment (or empty line)

combobulate-navigate-end-of-defun takes us here:

def f():
    pass|
# Comment (or empty line)

Whereas, treesit-end-of-defun takes us here:

def f():
    pass
|# Comment (or empty line)

GNU Emacs 30.0.50 (master), Combobulate master@5806ad7

When calling indent again, re-use same region?

Just getting started playing with combobulate; so far so good. One thing I noticed right away: combobulate-python-indent-for-tab-command is very useful. But since it outdents to start, any code below at the same level gets picked up for indentation by subsequent Tab's. It would be useful if there were an option for repeated indent-for-tab commands to re-use the automatically determined region from the first indent. I think that's the point of combobulate-python-indent-mark-region=t, but I have that set, and region is deactivated after indent and not used.

Can't transpose 2 functions

In js-ts-mode:

function f() {}
function g() {}

with the cursor at the beginning of the second line, I would expect combobulate-transpose-sexp to swap the 2 functions so function g() is first.

Currently, the result is not valid javascript:

function f() function
{} g() {}

Please note that I don't want to use combobulate-drag-down (which works in this particular case) because both functions might have a several-line long body that should also be transposed.

Some top-level comments have no nearest navigable node in yaml

The problem

Consider the following yaml buffer:

# |Comment
foo: "bar"

with the cursor (|) on the comment line. Calling (combobulate--get-nearest-navigable-node) returns nil, which means that among others M-x combobulate-navigate-next fails with an error and the following stack (with toggle-debug-on-error):

Debugger entered--Lisp error: (wrong-type-argument combobulate-proxy-node nil)
  combobulate--get-siblings(nil)
  combobulate--get-sibling(nil forward)
  combobulate-nav-get-next-sibling(nil)
  combobulate--navigate-next()
  combobulate-navigate-next(1)
  funcall-interactively(combobulate-navigate-next 1)
  command-execute(combobulate-navigate-next record)
  execute-extended-command(nil "combobulate-navigate-next" "combobula navi ne")
  funcall-interactively(execute-extended-command nil "combobulate-navigate-next" "combobula navi ne")
  command-execute(execute-extended-command)

In the above stack it's not 100% but what happens is that (combobulate--navigate-next) calls (combobulate--get-nearest-navigable-node) which returns nil.

Desired behaviour

It would be nice if combobulate-navigate-next would navigate to the next node even if the cursor is at a top-level comment. In the example buffer above, the cursor should be placed at the beginning of the foo: "bar" block.

Note that this is how top-level comments work in for example python-ts-mode.

Unfortunately I'm not familiar enough with combobulate to come up with a quick solution myself (does it relate to the module node in python? Maybe which nodes are defined as navigable?) but I'd be happy to try to help and submit a pull request if I get some pointers. Or maybe this is just a quick fix away.

Any plans for Clojure support?

Hi Mickey,

This package looks great! Just wondering if there's plan for Clojure support? I know that Emacs 29 doesn't ship with Clojure treesitter, but I see a third-party package. Is this a blocker?

Question about navigation use case

Hi, I want to improve my code navigation skill and I am glad I found combobulate. I am trying to adapt it for Rust. However, I run into an issue that I am not able to navigate to some node.

For example, I have setup combobulate as such

(setq combobulate-navigation-node-types '(source_file
                                          call_expression
                                          arguments
                                          identifier)))

I have reduced my use case to a snippet

f(g());
f2();

print from tree-sitter-debug-mode

source_file:
  call_expression:
    identifier:
    arguments:
      call_expression:
        identifier:
        arguments:
  call_expression:
    identifier:
    arguments:

My cursor is at f the first call_expression. I want to navigate to f2 which is the second call_expression but I am not able to do so. I tried combobulate-navigate-next but it doesn't work for me. I think combobulate considers my current node is at identifier node which has the same start position as call_expression node. Is there a way to tell combobulate to select the parent node to disambiguate my intention?

How to rebind Combobulate Prefix Key

Great package, much improved!

Some docs on how to rebind C-c o would be nice. (I use C-c o as the prefix for my custom org mode bindings.)

I noticed C-c o used here and here.

Thanks!

Cycling through indentation in Python

Thanks for the amazing combobulate, I'm slowly getting used to navigating and moving nodes around with tree-sitter + combobulate-mode.

Most of my work is with Python with python-ts-mode and Emacs 29, and I do have an issue with indentation: seems like combobulate-python-indent-for-tab-command will cycle leftwards until it reaches the beginning of the line and then start cycling rightwards.

I would prefer if combobulate-python-indent-for-tab-command could cycle through indentations that result in syntactically correct code, or let me choose the cycling direction (that I can key map to TAB-<left> and TAB-<right>).

For illustration purposes imagine the following Python function that counts lines that begin with a prefix:

def count_lines_with_prefix(path, prefix):
    count = 0

    with open(path) as f:
        for line in f.readlines():
            if line.startswith(prefix):
                count += 1

    return count

Placing my cursor on the return count line and calling combobulate-python-indent-for-tab-command will move it first leftwards thus leading to:

def count_lines_with_prefix(path, prefix):
    count = 0

    with open(path) as f:
        for line in f.readlines():
            if line.startswith(prefix):
                count += 1

return count

Which is syntactically incorrect. If possible, return count could be cycled only through indentation levels that lead to syntactically correct code, which in this example would be going rightwards all the way. Alternatively, providing a TAB-<right> keybind if deciding what is syntactically correct is unfeasible.

Sidenote: a bit strangely, if return count is inside the context manager, pressing <TAB> once will send it immediately to the beginning of the line, skipping two indentation levels. Not sure how combobulate chooses where to begin its cycling.

Happy to work on a patch if my suggestion seems feasible and in line with combobulate's goals.

combobulate-kill-node-dwim makes code invalid

Given this code in js-ts-mode:

function f() {
    return 2;
}

and my point is at the beginning of the first line, I would expect combobulate-kill-node-dwim to kill the whole function. Unfortunately, it only kills the function keyword (which I could have done with M-d).

Great idea!

Hi, this is a great idea and something I've kicked around myself but didn't know how/have the time to start approaching it. I have made many extensions for myself of Lispy and have given this a lot of thought and time coding. How much do you think of combobulate as a generalization of Lispy? One thing in particular that I've thought about is how one might implement a Lispy-like 'special mode' for non-Lisp-like languages so that one doesn't have to explicitly invoke a structural editing mode and can save keystrokes. For Python, for example, something similar to Lispy is possible because of the explicit indentation. Anyway, I haven't tried combobulate out (I will soon) or looked at the code yet, but I am interested in contributing ideas and code if they would be compatible with your conception of the project.

How to select anonymous Javascript function with its variable

How can I select this entire declaration?

const add = (a, b) => {
  return a + b;
}

combolute-mark-defn will select the anonymous function declaration but won't include const add = . That is 100% correct as it is not part of the function. However conceptually in JS we often think of the above as being equivalent to this:

function add(a, b) {
  return a + b;
}

So it'd be nice if, optionally, combobulate could select both uniformly.

M-k / M-h not doing anything in jsx and python modes

I'm intrigued by combobulate-mode, and am trying it right now. Unfortunately, navigation seems wonky, and things like M-h and M-k don't do anything.

The symptoms in both JSX and python-mode are the same: Hitting M-h and M-k in a node that I would expect to be affected by the command), nothing happens: no error, no observale effect.

In JSX I'm working with this example, using 0daf472:

return (
    <element>
      <element>
        Hello World!
      </element>
      <element>
        Hello World!
      </element>
      <element>
        Hello World!
      </element>
    </element>
)

Placing (point) in or before any of the child <element> nodes and hitting M-h or M-k has no effect: no message is displayed, nothing gets marked or killed.

Query Builder Documentation

First off, love the tool and the awesome book!

When using the tool though my main interest is using it for querying and I am having trouble finding documentation for how to build a query. I tried using the Emacs Treesitter documentation and those queries don't seem to work a good chunk of the time and the official Treesitter documentation has the same issue. My super basic example is just looking for comments which the Treesitter documentation shows should be (comment)+ here but when I try this in combobulate I get no matches or no @capture groups.

So where can one find a list of capture groups and documentation for the query syntax?

expand region seems not granular enough

Given this tsx code:

export default function Test() {
  return (
    <>
      <strong>abc</strong>
      <strong>def</strong>
      <strong>hij <span className="foo">bar baz</span></strong>
    </>
  );
}

When I place the point on the baz and invoke combobulate-mark-node-dwim I would have expected that first the baz is selected, then bar baz, the the <span…, them the <strong…, then the fragment, the fragment including the braces, the return, the braces of the function, the function Test(), then the export default.

The current behaviour is selecting the whole span, the strong, the fragment, the braces, braces with return, the function braces, the function Test(), then the export default.

Isn't this a bit too aggressive? `expand-region.el (in a mode it knows) would have selected as described, I think.

Can combobulate select named arguments selectively in Python?

First of all, thanks for this great package!

From PyCharm I am used to the behavior where the expand region is able to select each named argument and parameter:

CleanShot 2023-07-31 at 08 37 42

In Combobulate, it instantly selects the entire function call:

CleanShot 2023-07-31 at 08 39 00

From the node tree shown, it seems like the named arguments aren't properly 'parsed' to a finer level. Thus it doesn't work, right? Is there a way to make it work as in PyCharm?

Name mismatch for javascript mode

I'm trying to test out combobulate, using the setup from the blog post. When trying to activate combobulate in javascript files though, it gives the error "© There is either no tree sitter language in this buffer, or Combobulate does not support it."

Investigating shows that this is happening because (treesit-parser-language (car-safe (treesite-parser-list))) evaluates to javascript, but the key in combobulate-setup-functions-alist is jsx, rather than javascript (lines 120-122 of combobulate.el). I tried (add-to-list 'combobulate-setup-functions-alist '(javascript . combobulate-js-ts-setup)), but that fails later as it tries to lookup symbols based on the name of the parser's language.

In JSX/TSX is there a way to move up/down XML nodes?

Maybe I don't find the function that I'd need to call but what I'd love to have is way to move from |<div> to |<p> (i.e. sexp-down?) and the other way round.

const fn = () => {
  return (
    <div className="foo">
      <p>Text</p>
    </div>
  )
}

I only found ways to go forward and backward. Is this supported, yet? If not, could this be implemented? :)

Thanks for this package, cheers

TSX: void-function combobulate-parser-node

Hi there,

current git master throws an error when inserting an = char. Look for example this source:

export default function Test() {
  return (
    <div>
      <a href />
    </div>
  );
}

When trying to insert an = sign after the href I get this error:

Debugger entered--Lisp error: (void-function combobulate-parser-node)
  combobulate-parser-node(#<treesit-node jsx_self_closing_element in 61-71>)
  combobulate-maybe-insert-attribute()
  funcall-interactively(combobulate-maybe-insert-attribute)
  command-execute(combobulate-maybe-insert-attribute)

Best regards,
CK

When typing an arrow function in Typescript, it will wrongly close a JSX tag

In typescript-ts-mode, when writing JSX, an arrow function inside a prop will close the JSX tag.

For example, with this snippet, (_|_) si the cursor position.

function Test () {
  return <Component prop={x=_|_}></Component>
}

If I type > to continue typing an arrow function, it will autocomplete to:

function Test () {
  return <Component prop={x=>_|_</Component>}></Component>
}

C support

Hi!
As far as I can tell C is not yet supported?
Thanks!

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.