1 Star 0 Fork 0

Aggron/treemacs

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
Extensions.org 12.19 KB
一键复制 编辑 原始数据 按行查看 历史

Content

Treemacs Extension Tutorial

Try it

The code in this file is loadable with org-babel-load-file, you can see tehe results by calling showcase-display-buffer-list.

Extensions for Projects

First our dependencies:

(require 'treemacs)
(require 'dash)

Now let’s define a source for the data we’ll be displaying: a list of buffers, grouped by their major modes, with transient buffers (whose names start with a space) removed. It doesn’t really make much sense to add a buffer list like that under some project, but this example is 1) reasonably practical, and 2) very simple, allowing us to concentrate on integrating it with treemacs.

(defun showcase--get-buffer-groups ()
  "Get the list of buffers, grouped by their major mode."
  (->> (buffer-list)
       (--reject (eq ?\ (aref (buffer-name it) 0)))
       (--group-by (buffer-local-value 'major-mode it))))

The output of this function would look roughly like this. This is the structure our extension will have when we’re done.

; ((org-mode
;   #<buffer Extensions.org>)
;  (emacs-lisp-mode
;   #<buffer init.el>
;   #<buffer treemacs-customization.el>
;   #<buffer *scratch*>
;   #<buffer treemacs-extensions.el>)
;  (spacemacs-buffer-mode
;   #<buffer *spacemacs*>)
;  (messages-buffer-mode
;   #<buffer *Messages*>)
;  (compilation-mode
;   #<buffer *Compile-Log*>)
;  (magit-status-mode
;   #<buffer magit: treemacs>))

Before we begin defining what our nodes look like we will slightly get ahead of ourselves and define the function that allows us to visit the buffer node at point. The buffer that is represented by the node will be stored in its :buffer property, so all we need to do is extract it, make sure it’s alive, and show it in next-window. The function could make use of the prefix argument, but we won’t us it here. This function can now be used to give our leaf nodes a TAB, RET, or double-mouse1 action (the latter is a work in progress).

(defun showcase-visit-buffer (&rest _)
  "Switch to the buffer saved in node at point."
  (let* ((node (treemacs-current-button))
         (buffer (treemacs-button-get node :buffer)))
    (when (buffer-live-p buffer)
      (select-window (next-window))
      (switch-to-buffer buffer))))

Now we’ll get to building our custom treemacs tree. Node types are defined from the bottom up, so we start with the leaves of our tree, nodes that represent some specific buffer. treemacs-define-leaf-node needs 3 things: a name, an icon, and optional keyword arguments for TAB, RET, and doubleclick actions. This is why we defined our visit-buffer command in advance. Instead of using treemacs-as-icon to iconize a string you can also use treemacs-icon-for-file or directly use one of treemacs’ own icons (like treemacs-icon-css).

(treemacs-define-leaf-node buffer-leaf
  (treemacs-as-icon "• " 'face 'font-lock-builtin-face)
  :ret-action #'showcase-visit-buffer
  :tab-action #'showcase-visit-buffer
  :mouse1-action (lambda (&rest args) (interactive) (showcase-visit-buffer args)))

Now we move further up and define our first expandable node type that represents a group of buffers with a specific major mode. This is where it gets interesting. Aside from the two icons for the node being either open or closed we need a query function and a render action. The former will be called by treemacs to this node is expanded and must provide a list of child nodes to display.

In the context of the invocation of the query function the node being expanded is bound under the name btn, named so because under all its layers of abstraction treemacs’ nodes (specifically the text and not the icons) are buttons as per the builtin button.el library. Its functions (or rather their faster treemacs variants) can all be invoked on treemacs nodes, including treemacs-button-get, which we use here to retrieve the list of buffers that we will have stored in the node’s :buffers property.

In the next step treemacs will loop over the list returned by the query function, invoking the render action form for each. Every element in the iteration will be bound as item. Whatever code is used in the render action, it must end in a call to treemacs-render-node, which creates the strings treemacs will be inserting. It requires an icon, a display name, an initial state, a face, a (reasonably) unique key, and an optional list of arbitrary additional properties to store.

Here we can see why node definition must go bottom to top. The leaf node definition from above has given us the variables for both the icon and the initial state. Since we are iterating over a list of buffers we can use the buffer name for the display label. Buffers are also unique, so we can use them as the node’s unique key as well. There are requirements for the choice of the face. Finally we also store the buffer in every node’s :buffer property so we can later use it in showcase-visit-buffer.

(treemacs-define-expandable-node buffer-group
  :icon-open (treemacs-as-icon "- " 'face 'font-lock-string-face)
  :icon-closed (treemacs-as-icon "+ " 'face 'font-lock-string-face)
  :query-function (treemacs-button-get btn :buffers)
  :render-action
  (treemacs-render-node
   :icon treemacs-buffer-leaf-icon
   :label-form (buffer-name item)
   :state treemacs-buffer-leaf-state
   :face 'font-lock-string-face
   :key-form item
   :more-properties (:buffer item)))

Third things third we define a root node to hold the list of buffers together. It works much the same way as just defining an expandable node, but requires additional information. So far we’ve created a small render chain. Buffers are rendered by buffers groups, buffer groups are rendered by the buffer root, but the buffer root is not rendered by the next highest node, but by treemacs itself. So we set :root-marker to t and provide a :root-label, :root-face and a :root-key-form, same as when calling into treemacs-render-node.

(treemacs-define-expandable-node buffers-root
  :icon-open (treemacs-as-icon "- " 'face 'font-lock-string-face)
  :icon-closed (treemacs-as-icon "+ " 'face 'font-lock-string-face)
  :query-function (showcase--get-buffer-groups)
  :render-action
  (treemacs-render-node
   :icon treemacs-icon-buffer-group-closed
   :label-form (symbol-name (car item))
   :state treemacs-buffer-group-closed-state
   :face 'font-lock-keyword-face
   :key-form (car item)
   :more-properties (:buffers (cdr item)))
  :root-marker t
  :root-label "Buffers"
  :root-face 'font-lock-type-face
  :root-key-form 'Buffers)

This code will have defined a function called treemacs-BUFFERS-ROOT-extension which we can use as our extension, but first we need another, final building block. We are building an extension for projects, but we have yet to decide which projects it is for. In other words we need a predicate. So let’s assume we want our extension to show up only for the first project in the workspace.

(defun showcase-extension-predicate (project)
  (eq project
      (-> (treemacs-current-workspace)
          (treemacs-workspace->projects)
          (car))))

With everything in place we can now tell treemacs about our extension. The final argument :position decides whether the extension will be rendered at the very start or the very end of the project’s immediate children.

(treemacs-define-project-extension
 :extension #'treemacs-BUFFERS-ROOT-extension
 :predicate #'showcase-extension-predicate
 :position 'top)

Extension for Directories

Extension for arbitraray directories work much the same way as extensions for projects. The only differences are that a directory predicate takes a file path argument of type string and that the final call is made to treemacs-define-directory-extension.

Extensions at the Top level

It is also possible to place extensions at the very top of the display tree, on the same level as projects. To make this work treemacs-define-expandable-node must receive not a :root-marker, but a :top-level-marker. Other than that nothing changes and the same restrictions apply, but the treemacs-BUFFERS-ROOT-extension that we’ve created in our example will be able to be passed to treemacs-define-top-level-extension.

(treemacs-define-top-level-extension
 :extension #'treemacs-BUFFERS-ROOT-extension
 :position 'top)

Every top-level element in treemacs has its own project struct and extensions are no different. It’s even more important in top-level extensions since the project object is required not just for internal house-keeping, but is needed to address nodes in the given tree. To that end an extensions project is always found in a buffer local variable named treemacs-${name}-extension-project, where ${name} is the name passed to treemacs-define-expandable-node.

Note that neither predicates, nor the use of a ~’bottom~ position have yet been implemented.

Extensions beyond Treemacs

Finally you can also use the extensions api as a generalized set of building block for tree structures, to be used in any other buffer. First let’s define an appropriate extension. We will re-use the buffer-root code from above, except this time we’ll mark it as a :project and call it buffer-root-top.

(treemacs-define-expandable-node buffers-root-top
  :icon-open (treemacs-as-icon "- " 'face 'font-lock-string-face)
  :icon-closed (treemacs-as-icon "+ " 'face 'font-lock-string-face)
  :query-function (showcase--get-buffer-groups)
  :render-action
  (treemacs-render-node
   :icon treemacs-icon-buffer-group-closed
   :label-form (symbol-name (car item))
   :state treemacs-buffer-group-closed-state
   :face 'font-lock-keyword-face
   :key-form (car item)
   :more-properties (:buffers (cdr item)))
  :top-level-marker t
  :root-label "Buffers"
  :root-face 'font-lock-type-face
  :root-key-form 'Buffers)

With this we have all we need to display the buffer overview in any buffer of our choice. The required setup is minimal, we just need to display such a buffer and call treemacs-initialize inside it, and the extension can be used:

(defun showcase-display-buffer-list ()
  (interactive)
  (let* ((buffer (get-buffer-create "*Showcase Buffer List*"))
         (window (display-buffer-in-side-window buffer '((side . right)))))
    (select-window window)
    (treemacs-initialize)
    (treemacs-BUFFERS-ROOT-TOP-extension)))

Node Navigation and Updates

TODO

Setting the Working Directory

Treemacs sets the value of default-directory based on the nearest path at point. This allows commands like find-file and magit-status to do what you mean based on the current context. This option is also available for custom nodes: just set the property :default-directory and treemacs will make use of its value when the node is in focus.

About Properties

treemacs-render-node allows to add arbitrary propertis to a node, which can quickly lead to subtle, difficult-to-trace conflicts since treemacs itself makes extensive use of that option. To avoid such issues the following keywords and symbols must not be used as properties:

  • :project
  • :state
  • :depth
  • :path
  • :key
  • :parent
  • :default-face
  • :symlink
  • :marker
  • :index
  • :custom
  • ~’button~
  • ~’category~
  • ~’face~
  • ~’keymap~
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/aggronmagi/treemacs.git
git@gitee.com:aggronmagi/treemacs.git
aggronmagi
treemacs
treemacs
master

搜索帮助