Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add region-restricted parsing and highlighting #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]
- Added support for parsing and highlighting a single region, to enable `jupyter-repl` [integration](https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/78).

## [0.18.0] - 2022-02-12
- Added APIs to traverse the syntax tree: `tsc-traverse-do`, `tsc-traverse-mapc`, `tsc-traverse-iter`. The traversal is depth-first pre-order.
Expand Down
47 changes: 41 additions & 6 deletions lisp/tree-sitter-hl.el
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,21 @@ See `tree-sitter-hl-add-patterns'."
(setf (map-elt tree-sitter-hl--patterns-alist lang-symbol)
(append (list patterns) (remove patterns old-list))))))

(defun tree-sitter-hl-dry-up-region (beg end)
"Make the highlighting in the region between BEG and END 'permanent'."
(let ((pos beg) next)
(while (< pos end)
;; Determine the end of the current contiguous block...
(setq next (next-single-property-change pos 'face))
;; ... which should be capped to END.
(when (or (null next)
(> next end))
(setq next end))
;; Convert `face' to `font-lock-face'.
(when-let ((face (get-text-property pos 'face)))
(put-text-property pos next 'font-lock-face face))
(setq pos next))))

;;; ----------------------------------------------------------------------------
;;; Internal workings.

Expand Down Expand Up @@ -524,12 +539,32 @@ If LOUDLY is non-nil, print debug messages."
"Highlight the region (BEG . END).

This is a wrapper around `tree-sitter-hl--highlight-region' that falls back to
OLD-FONTIFY-FN when the current buffer doesn't have `tree-sitter-hl-mode'
enabled. An example is `jupyter-repl-mode', which copies and uses other major
modes' fontification functions to highlight its input cells. See
https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/78#issuecomment-1005987817."
(if tree-sitter-hl--query
(tree-sitter-hl--highlight-region beg end loudly)
OLD-FONTIFY-FN in certain situations:

1. When the current buffer doesn't have `tree-sitter-hl-mode' enabled. An
example is `jupyter-repl-mode', which copies and uses other major modes'
fontification functions to highlight its input cells. See
https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/78#issuecomment-1005987817.

2. When the code to parse and highlight is confined to a single region. The
motivating use case parsing and highlighting the current input cell in
`jupyter-repl-mode'. See `tree-sitter-get-parse-region-function'."
(if (and tree-sitter-hl--query tree-sitter-tree)
(if tree-sitter--parse-region
;; Highlight only the region that `tree-sitter' parsed. Use the
;; underlying fontification function for the rest.
(pcase-let ((`(,code-beg . ,code-end) tree-sitter--parse-region))
(let ((tree-hl-beg (max beg code-beg))
(tree-hl-end (min end code-end)))
(if (<= tree-hl-end tree-hl-beg)
(funcall old-fontify-fn beg end loudly)
(when (< beg tree-hl-beg)
(funcall old-fontify-fn beg tree-hl-beg loudly))
(when (< tree-hl-end end)
(funcall old-fontify-fn tree-hl-end end loudly))
(tree-sitter-hl--highlight-region beg end loudly)
`(jit-lock-bounds ,beg . ,end))))
(tree-sitter-hl--highlight-region beg end loudly))
(funcall old-fontify-fn beg end loudly)))

(defun tree-sitter-hl--invalidate (&optional old-tree)
Expand Down
50 changes: 49 additions & 1 deletion lisp/tree-sitter.el
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ syntax tree. It is run after `tree-sitter-mode-hook'."

(defcustom tree-sitter-after-on-hook nil
"Functions to call after enabling `tree-sitter-mode'.
Use this to enable other minor modes that depends on the syntax tree."
Use this to enable other minor modes that depend on the syntax tree."
:type 'hook
:group 'tree-sitter)

Expand All @@ -64,6 +64,22 @@ Use this to enable other minor modes that depends on the syntax tree."
(defvar-local tree-sitter-language nil
"Tree-sitter language.")

(defvar-local tree-sitter-get-parse-region-function nil
"The function that provides the region to parse.

This is intended to be used by major modes that want to parse code chunks, one
at a time. An example is `jupyter-repl-mode'.

This function should return a (BEG . END) cons cell. If it returns nil, the
whole buffer will be parsed. If there is no code chunk to parse, the major mode
should call `tree-sitter-pause' instead.")

(defvar-local tree-sitter--parse-region nil
"The region that corresponds to the current syntax tree.
Normally this is nil, which means the whole buffer was parsed.

This is non-nil only if `tree-sitter-get-parse-region-function' was set.")

(defvar-local tree-sitter--text-before-change nil)

(defvar-local tree-sitter--beg-before-change nil)
Expand Down Expand Up @@ -140,6 +156,19 @@ OLD-LEN is the char length of the old text."
(defun tree-sitter--do-parse ()
"Parse the current buffer and update the syntax tree."
(let ((old-tree tree-sitter-tree))
;; Check and set range restriction (should be provided by the major mode).
(when (functionp tree-sitter-get-parse-region-function)
(when-let ((region (funcall tree-sitter-get-parse-region-function)))
;; TODO: Check validity.
(setq tree-sitter--parse-region region)
(tsc-set-included-ranges
tree-sitter-parser
(pcase-let ((`(,beg . ,end) region))
(tsc--save-context
(vector (vector (position-bytes beg)
(position-bytes end)
(tsc--point-from-position beg)
(tsc--point-from-position end))))))))
(setq tree-sitter-tree
;; https://github.com/emacs-tree-sitter/elisp-tree-sitter/issues/3
(tsc--without-restriction
Expand Down Expand Up @@ -180,6 +209,25 @@ signal an error."
(when err
,@error-forms))))

(defun tree-sitter-pause ()
"Pause incremental parsing in the current bufer.
This should only be called by major modes that use
`tree-sitter-get-parse-region-function'.

Functions that depend on the syntax tree will stop working until
`tree-sitter-resume' is called."
(setq tree-sitter-tree nil))

(defun tree-sitter-resume ()
"Resume incremental parsing. If it was paused before, do a full parse first."
(tree-sitter--do-parse))

;;;###autoload
(defun tree-sitter-enable (lang-symbol)
"Turn on `tree-sitter-mode' for LANG-SYMBOL in the current buffer."
(setq tree-sitter-language (tree-sitter-require lang-symbol))
(tree-sitter-mode))

;;;###autoload
(define-minor-mode tree-sitter-mode
"Minor mode that keeps an up-to-date syntax tree using incremental parsing."
Expand Down