From 8f897378590e237e352df11a9558372f19984643 Mon Sep 17 00:00:00 2001 From: David Hanak Date: Thu, 16 Jan 2025 09:41:07 +0100 Subject: [PATCH 1/2] Improvements to fontification 1. correctly fontify escape sequences and regex strings; 2. correctly fontify tuple assigments; 3. make function definition name fontification significantly simpler and more robust, fontify more obscure variants correctly; 4. fontify variable assignments in a few more scenarios: typed assignments and slurps; --- julia-ts-mode.el | 148 +++++++++++++----------------- test/Downloads.jl.faceup | 2 +- test/tree-sitter-corpus.jl | 12 ++- test/tree-sitter-corpus.jl.faceup | 28 ++++-- 4 files changed, 95 insertions(+), 95 deletions(-) diff --git a/julia-ts-mode.el b/julia-ts-mode.el index e6dd56a..b8776bc 100644 --- a/julia-ts-mode.el +++ b/julia-ts-mode.el @@ -144,23 +144,24 @@ Otherwise, the indentation is: (treesit-font-lock-rules :language 'julia :feature 'assignment - `((assignment :anchor [(identifier) (operator)] @font-lock-variable-name-face) - (assignment - :anchor - (field_expression - value: (identifier) "." (identifier) @font-lock-variable-name-face)) - (assignment (open_tuple (identifier) @font-lock-variable-name-face)) - (assignment - :anchor - (open_tuple - (field_expression - value: (identifier) "." (identifier) @font-lock-variable-name-face))) - (local_statement (identifier) @font-lock-variable-name-face) + `(;; plain assignments + ([(identifier) (operator)] @font-lock-variable-name-face @lhs + (:pred julia-ts--assignment-left-side-p @lhs)) + ;; qualified name assignments + ((field_expression "." (identifier) @font-lock-variable-name-face) @lhs + (:pred julia-ts--assignment-left-side-p @lhs)) + ;; typed assignments + ((typed_expression (identifier) @font-lock-variable-name-face "::") @lhs + (:pred julia-ts--assignment-left-side-p @lhs)) + ;; let blocks (let_statement :anchor (identifier) @font-lock-variable-name-face) ((let_statement _ @comma :anchor (identifier) @font-lock-variable-name-face) (:equal "," @comma)) (let_binding :anchor (identifier) @font-lock-variable-name-face) + ;; local and global statements + (local_statement (identifier) @font-lock-variable-name-face) (global_statement (identifier) @font-lock-variable-name-face) + ;; named (keyword) argument names with assigned values (named_argument (identifier) @julia-ts-keyword-argument-face (operator))) :language 'julia @@ -177,81 +178,27 @@ Otherwise, the indentation is: :language 'julia :feature 'definition - `((function_definition - (signature (identifier) @font-lock-function-name-face)) + `(;; function declaration (function_definition - (signature - (call_expression [(identifier) (operator)] @font-lock-function-name-face))) - (function_definition - (signature - (typed_expression - (call_expression [(identifier) (operator)] @font-lock-function-name-face)))) - (function_definition - (signature - (where_expression - (call_expression [(identifier) (operator)] @font-lock-function-name-face)))) - (function_definition - (signature - (where_expression - (typed_expression - (call_expression [(identifier) (operator)] @font-lock-function-name-face))))) - (function_definition - (signature - (call_expression - (field_expression - value: (identifier) "." (identifier) @font-lock-function-name-face)))) - (function_definition - (signature - (typed_expression - (call_expression - (field_expression - value: (identifier) "." (identifier) @font-lock-function-name-face))))) - (macro_definition - (signature - (call_expression (identifier) @font-lock-function-name-face))) - (macro_definition - (signature - (call_expression - (field_expression - value: (identifier) "." (identifier) @font-lock-function-name-face)))) - (abstract_definition - (type_head (identifier) @font-lock-type-face)) - (abstract_definition - (type_head (binary_expression (identifier) @font-lock-type-face))) - (primitive_definition - (type_head (identifier) @font-lock-type-face)) - (primitive_definition - (type_head (binary_expression (identifier) @font-lock-type-face))) - (struct_definition - (type_head (identifier) @font-lock-type-face)) - (struct_definition - (type_head (binary_expression (identifier) @font-lock-type-face))) - (assignment - :anchor - (call_expression [(identifier) (operator)] @font-lock-function-name-face)) - (assignment - :anchor - (call_expression - (parenthesized_expression - [(identifier) (operator)] @font-lock-function-name-face))) - (assignment - :anchor - (call_expression - (field_expression - value: (identifier) "." (identifier) @font-lock-function-name-face))) - (assignment - :anchor - (where_expression - (call_expression (identifier) @font-lock-function-name-face))) - (assignment - :anchor - (where_expression - (call_expression - (field_expression - value: (identifier) "." (identifier) @font-lock-function-name-face)))) + (signature (identifier) @font-lock-function-name-face)) + ;; function definitions (long and short syntax) + (call_expression + [(identifier) (operator)] @font-lock-function-name-face + (:pred julia-ts--fundef-name-p @font-lock-function-name-face)) + (call_expression + (parenthesized_expression + [(identifier) (operator)] @font-lock-function-name-face) + (:pred julia-ts--fundef-name-p @font-lock-function-name-face)) + (call_expression + (field_expression "." [(identifier) (operator)] @font-lock-function-name-face) + (:pred julia-ts--fundef-name-p @font-lock-function-name-face)) + ;; binary operator definition as assignment (assignment :anchor - (binary_expression _ (operator) @font-lock-function-name-face))) + (binary_expression _ (operator) @font-lock-function-name-face)) + ;; struct/abstract type/primitive type definitions + (type_head (identifier) @font-lock-type-face) + (type_head (binary_expression (identifier) @font-lock-type-face))) :language 'julia :feature 'error @@ -315,6 +262,12 @@ Otherwise, the indentation is: :override 'keep `((quote_expression) @julia-ts-quoted-symbol-face) + :language 'julia + :feature 'string + '(((escape_sequence) @font-lock-escape-face) + ((prefixed_string_literal prefix: _ @prefix) @font-lock-regexp-face + (:equal "r" @prefix))) + :language 'julia :feature 'string :override 'keep @@ -432,6 +385,33 @@ Return nil if there is no name or if NODE is not a defun node." (lambda (child) (equal (treesit-node-type child) type))))) +(defun julia-ts--assignment-left-side-p (node) + "Return non-nil if NODE is the left hand side of an `assignment'. +It may be wrapped (within the `'assignment') in an `open_tuple' +or a `tuple_expression' (but only one of those two), and/or a +`splat_expression' as well." + (let* ((p1 (treesit-node-parent node)) + (p2 (if (equal (treesit-node-type p1) "splat_expression") + (treesit-node-parent p1) p1)) + (p3 (if (member (treesit-node-type p2) '("open_tuple" "tuple_expression")) + (treesit-node-parent p2) p2))) + (and (equal (treesit-node-type p3) "assignment") + (member (treesit-node-child p3 0) (list p2 p1 node))))) + +(defun julia-ts--fundef-name-p (node) + "Return non-nil if NODE is the name of a function definition. +NODE is recognized as such if it has an ancestor that is either a +`signature' (long syntax) or an `assignment' (short syntax), via +the ancestor's first child." + (let ((prev node)) + (treesit-parent-until + node + (lambda (ancestor) + (prog1 + (and (member (treesit-node-type ancestor) '("signature" "assignment")) + (equal (treesit-node-child ancestor 0) prev)) + (setq prev ancestor)))))) + ;;;###autoload (add-to-list 'auto-mode-alist '("\\.jl\\'" . julia-ts-mode)) diff --git a/test/Downloads.jl.faceup b/test/Downloads.jl.faceup index a967157..35517c3 100644 --- a/test/Downloads.jl.faceup +++ b/test/Downloads.jl.faceup @@ -149,7 +149,7 @@ in which case both the inner and outer code and message may be of interest. «v:errstr» = err.message «v:status» = err.response.status «v:message» = err.response.message - «v:status_re» = Regex(status == «c:0» ? «s:""» : «s:"\\b»«:julia-ts-string-interpolation-face:$»«D:status»«s:\\b"») + «v:status_re» = Regex(status == «c:0» ? «s:""» : «s:"»«:font-lock-escape-face:\\»«s:b»«:julia-ts-string-interpolation-face:$»«D:status»«:font-lock-escape-face:\\»«s:b"») err.code == Curl.CURLE_OK && «k:return» isempty(message) ? «s:"Error status »«:julia-ts-string-interpolation-face:$»«D:status»«s:"» : diff --git a/test/tree-sitter-corpus.jl b/test/tree-sitter-corpus.jl index c63219b..998f06a 100644 --- a/test/tree-sitter-corpus.jl +++ b/test/tree-sitter-corpus.jl @@ -492,7 +492,6 @@ echo "\033[31mred\033[m" # non-standard string literals # ============================== -# FIXME: \s shouldn't be an escape_sequence here trailing_ws = r"\s+$" version = v"1.0" K"\\" @@ -522,6 +521,17 @@ tup = 1, 2, 3 car, cdr... = list c &= d ÷= e +# open tuple +a, b = 1, 2 +# regular tuple +(a, b) = (1, 2) +# typed assignment +a::Int = 1 +# FQN +Main.a = 12 +# combination +a::Int, b::Int... = 1, 2, 3 + # ============================== # binary arithmetic operators # ============================== diff --git a/test/tree-sitter-corpus.jl.faceup b/test/tree-sitter-corpus.jl.faceup index b7fd53e..330542b 100644 --- a/test/tree-sitter-corpus.jl.faceup +++ b/test/tree-sitter-corpus.jl.faceup @@ -227,7 +227,7 @@ Base.«f:foo»(x) = x n «k:end» -f(n::«t:N», m::«t:M») «k:where» {«t:N» <: «t:Number»} «k:where» {«t:M» <: «t:Integer»} = n^m +«f:f»(n::«t:N», m::«t:M») «k:where» {«t:N» <: «t:Number»} «k:where» {«t:M» <: «t:Integer»} = n^m «t:Foo»{«t:T»}(x::«t:T») «k:where» {«t:T»} = x @@ -466,10 +466,10 @@ a -> «v:a» = «c:2», «c:3» «x:# ==============================» «s:""» -«s:"\""» +«s:"»«:font-lock-escape-face:\"»«s:"» «s:"foo bar"» -«s:"this is a \"string\"."» +«s:"this is a »«:font-lock-escape-face:\"»«s:string»«:font-lock-escape-face:\"»«s:."» «s:"""this is also a "string"."""» «v:band» = «s:"Interpol"» «s:"»«:julia-ts-string-interpolation-face:$»«D:band»«s: is a cool band"» @@ -483,19 +483,18 @@ a -> «v:a» = «c:2», «c:3» «s:`pwd`» «s:m`pwd`» «s:`cd »«:julia-ts-string-interpolation-face:$»«D:dir»«s:`» -«s:`echo \`cmd\``» +«s:`echo »«:font-lock-escape-face:\`»«s:cmd»«:font-lock-escape-face:\`»«s:`» «s:``` -echo "\033[31mred\033[m" +echo "»«:font-lock-escape-face:\033»«s:[31mred»«:font-lock-escape-face:\033»«s:[m" ```» «x:# ==============================» «x:# non-standard string literals» «x:# ==============================» -«x:# FIXME: \s shouldn't be an escape_sequence here» -«v:trailing_ws» = «s:r"\s+$"» +«v:trailing_ws» = «:font-lock-regexp-face:r"\s+$"» «v:version» = «s:v"1.0"» -«s:K"\\"» +«s:K"»«:font-lock-escape-face:\\»«s:"» «x:# ==============================» «x:# comments» @@ -519,9 +518,20 @@ nested #= comments =# =#» «v:a» = b a «f:..» b = a * b «v:tup» = «c:1», «c:2», «c:3» -«v:car», cdr... = list +«v:car», «v:cdr»... = list c &= d ÷= e +«x:# open tuple» +«v:a», «v:b» = «c:1», «c:2» +«x:# regular tuple» +(«v:a», «v:b») = («c:1», «c:2») +«x:# typed assignment» +«v:a»::«t:Int» = «c:1» +«x:# FQN» +Main.«v:a» = «c:12» +«x:# combination» +«v:a»::«t:Int», «v:b»::«t:Int»... = «c:1», «c:2», «c:3» + «x:# ==============================» «x:# binary arithmetic operators» «x:# ==============================» From 0be43a50905319067f8c66b39891ef78e961318e Mon Sep 17 00:00:00 2001 From: David Hanak Date: Wed, 22 Jan 2025 16:32:31 +0100 Subject: [PATCH 2/2] chore: Add GitHub CI workflow Also fix two elisp-check warnings in the package code. --- .github/workflows/CI.yml | 41 ++++++++++++++++++++++++++++++++++++++++ julia-ts-mode.el | 22 ++++++++++----------- test/test.el | 1 + 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/CI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..6725766 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,41 @@ +name: CI +on: + push: + branches: + - main + pull_request: + workflow_dispatch: +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + emacs-version: + - 29.1 + - 29.2 + - 29.3 + - 29.4 + steps: + - uses: actions/checkout@v4 + - name: Setup Emacs + uses: purcell/setup-emacs@master + with: + version: ${{ matrix.emacs-version }} + - name: Install prerequisites + shell: emacs --script {0} + run: | + (package-install 'julia-mode t) + (add-to-list 'load-path "${{ github.workspace }}") + (require 'julia-ts-mode) + (treesit-install-language-grammar 'julia) + - name: MELPA checks + uses: leotaku/elisp-check@master + with: + check: melpa + file: julia-ts-mode.el + - name: ERT checks + uses: leotaku/elisp-check@master + with: + check: ert + file: test/test.el diff --git a/julia-ts-mode.el b/julia-ts-mode.el index b8776bc..2bf78ed 100644 --- a/julia-ts-mode.el +++ b/julia-ts-mode.el @@ -31,7 +31,7 @@ ;; ;;; Commentary: ;; This major modes uses tree-sitter for font-lock, indentation, imenu, and -;; navigation. It is derived from `julia-mode'. +;; navigation. It is derived from `julia-mode'. ;;;; Code: @@ -54,6 +54,13 @@ ;; Notice that all custom variables and faces are automatically added to the ;; most recent group. +;; As of the grammar version 0.22, it uses the argument_list node for both +;; function definitions and calls. +(define-obsolete-variable-alias + 'julia-ts-align-parameter-list-to-first-sibling + 'julia-ts-align-argument-list-to-first-sibling + "0.3") + (defcustom julia-ts-align-argument-list-to-first-sibling nil "Align the argument list to the first sibling. @@ -86,13 +93,6 @@ Otherwise, the indentation is: :version "29.1" :type 'boolean) -;; As of the grammar version 0.22, it uses the argument_list node for both -;; function definitions and calls. -(define-obsolete-variable-alias - 'julia-ts-align-parameter-list-to-first-sibling - 'julia-ts-align-argument-list-to-first-sibling - "0.3") - (defcustom julia-ts-align-curly-brace-expressions-to-first-sibling nil "Align curly brace expressions to the first sibling. @@ -120,7 +120,7 @@ Otherwise, the indentation is: (defface julia-ts-quoted-symbol-face '((t :inherit font-lock-constant-face)) - "Face for quoted Julia symbols in `julia-ts-mode', e.g. :foo.") + "Face for quoted Julia symbols in `julia-ts-mode', e.g., :foo.") (defface julia-ts-keyword-argument-face '((t :inherit font-lock-constant-face)) @@ -128,11 +128,11 @@ Otherwise, the indentation is: (defface julia-ts-interpolation-expression-face '((t :inherit font-lock-constant-face)) - "Face for interpolation expressions in `julia-ts-mode', e.g. $foo.") + "Face for interpolation expressions in `julia-ts-mode', e.g., $foo.") (defface julia-ts-string-interpolation-face '((t :inherit font-lock-constant-face :weight bold)) - "Face for string interpolations in `julia-ts-mode', e.g. \"$foo\".") + "Face for string interpolations in `julia-ts-mode', e.g., \"$foo\".") (defvar julia-ts--keywords '("baremodule" "begin" "catch" "const" "do" "else" "elseif" "end" "export" diff --git a/test/test.el b/test/test.el index 358b49b..4f783b1 100644 --- a/test/test.el +++ b/test/test.el @@ -4,6 +4,7 @@ ;;; Code: (require 'faceup) +(require 'julia-ts-mode) (defvar julia-font-lock-test-dir (faceup-this-file-directory))