Skip to content
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
41 changes: 41 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -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
170 changes: 75 additions & 95 deletions julia-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -120,19 +120,19 @@ 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))
"Face for keyword argument names in `julia-ts-mode'.")

(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"
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand Down
2 changes: 1 addition & 1 deletion test/Downloads.jl.faceup
Original file line number Diff line number Diff line change
Expand Up @@ -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:"» :
Expand Down
1 change: 1 addition & 0 deletions test/test.el
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

;;; Code:
(require 'faceup)
(require 'julia-ts-mode)

(defvar julia-font-lock-test-dir (faceup-this-file-directory))

Expand Down
12 changes: 11 additions & 1 deletion test/tree-sitter-corpus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"\\"
Expand Down Expand Up @@ -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
# ==============================
Expand Down
28 changes: 19 additions & 9 deletions test/tree-sitter-corpus.jl.faceup
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"»
Expand All @@ -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»
Expand All @@ -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:# ==============================»
Expand Down