4
4
5
5
; ; Author: Troy Brown <[email protected] >
6
6
; ; Created: February 2023
7
- ; ; Version: 0.6.2
7
+ ; ; Version: 0.6.3
8
8
; ; Keywords: gpr gnat ada languages tree-sitter
9
9
; ; URL: https://github.com/brownts/gpr-ts-mode
10
10
; ; Package-Requires: ((emacs "29.1"))
@@ -289,6 +289,186 @@ SYMBOL, else the default value is updated instead."
289
289
(funcall (gpr-ts-mode--next-sibling-not-matching type) node parent bol)))
290
290
(car (treesit-simple-indent sibling-node parent (treesit-node-start sibling-node))))))
291
291
292
+ (defun gpr-ts-mode--prev-sibling (node parent bol &rest _ )
293
+ " Determine previous sibling in PARENT before this NODE or BOL."
294
+ (if node
295
+ (treesit-node-prev-sibling node)
296
+ (car
297
+ (reverse
298
+ (treesit-filter-child
299
+ parent
300
+ (lambda (n )
301
+ (< (treesit-node-start n) bol)))))))
302
+
303
+ (defun gpr-ts-mode--prev-sibling-matches-p (type )
304
+ " Check if previous sibling matches TYPE."
305
+ (lambda (node parent bol &rest _ )
306
+ (if-let ((prev (gpr-ts-mode--prev-sibling node parent bol)))
307
+ (string-equal (treesit-node-type prev) type))))
308
+
309
+ (defun gpr-ts-mode--prev-nonextra-sibling-matches-p (type )
310
+ " Check if previous non-extra sibling matches TYPE."
311
+ (lambda (node parent bol &rest _ )
312
+ (let ((prev (gpr-ts-mode--prev-sibling node parent bol)))
313
+ (while (and prev (treesit-node-check prev 'extra ))
314
+ (setq prev (treesit-node-prev-sibling prev)))
315
+ (when prev
316
+ (string-equal (treesit-node-type prev) type)))))
317
+
318
+ (defun gpr-ts-mode--indent-error-recovery (&optional op )
319
+ " Look for nearest indent error recovery point.
320
+ If OP is nil or \\= 'anchor\\= ', determine recovery anchor. If OP is
321
+ \\= 'offset\\= ', determine recovery offset."
322
+ (lambda (node parent bol &rest _ )
323
+ (let ((compound-alist
324
+ `((" when" . ( :compound-type " case_item" ))
325
+ (" case" . ( :compound-type " case_construction"
326
+ :offset gpr-ts-mode-indent-when-offset))
327
+ (" package" . ( :compound-type " package_declaration"
328
+ :offset
329
+ (lambda (_anchor )
330
+ (if (and , node (string-equal (treesit-node-type , node ) " end" ))
331
+ 0
332
+ gpr-ts-mode-indent-offset))))
333
+ (" project" . ( :compound-type " project_declaration"
334
+ :predicate
335
+ (lambda (n )
336
+ (let* ((next (treesit-node-next-sibling n))
337
+ (next-type (treesit-node-type next)))
338
+ (or (null next-type)
339
+ (not (string-equal next-type " '" )))))
340
+ ; ; Anchor at beginning of line to account
341
+ ; ; for possible qualifier prefixes (e.g.,
342
+ ; ; "abstract")
343
+ :offset
344
+ (lambda (_anchor )
345
+ (if (and , node (string-equal (treesit-node-type , node ) " end" ))
346
+ 0
347
+ gpr-ts-mode-indent-offset))
348
+ :anchor-bol t ))
349
+ (" (" . ( :compound-type (" attribute_reference"
350
+ " expression_list"
351
+ " typed_string_declaration"
352
+ " attribute_declaration" )
353
+ :predicate
354
+ (lambda (_n )
355
+ (or (null , node )
356
+ ; ; Recovery point.
357
+ (not (or
358
+ (gpr-ts-mode--declaration-p , node )
359
+ (member (treesit-node-type , node )
360
+ '(" ERROR" " when" " case" " package" " end" ))))))
361
+ :matching-pair " )"
362
+ :offset
363
+ (lambda (anchor )
364
+ (if (and , node (string-equal (treesit-node-type , node ) " )" ))
365
+ 0
366
+ (let ((anchor-column
367
+ (save-excursion
368
+ (goto-char (treesit-node-start anchor))
369
+ (current-column )))
370
+ (next anchor))
371
+ (while (and next
372
+ (or (treesit-node-eq next anchor)
373
+ ; ; skip comments
374
+ (treesit-node-check next 'extra )))
375
+ (save-excursion
376
+ (goto-char (treesit-node-end next))
377
+ (skip-chars-forward " \t\n " , bol )
378
+ (if (>= (point ) , bol )
379
+ (setq next nil )
380
+ (setq next (treesit-node-at (point ))))))
381
+ (if next
382
+ (let ((anchor-column
383
+ (save-excursion
384
+ (goto-char (treesit-node-start anchor))
385
+ (current-column ))))
386
+ (save-excursion
387
+ (goto-char (treesit-node-start next))
388
+ (- (current-column ) anchor-column)))
389
+ 1 ))))))))
390
+ (matches nil ))
391
+ (while (and parent (not matches))
392
+ (setq matches
393
+ (treesit-induce-sparse-tree
394
+ parent
395
+ (lambda (candidate )
396
+ (when (< (treesit-node-start candidate) bol)
397
+ (if-let* ((type (treesit-node-type candidate))
398
+ (entry (alist-get type compound-alist nil nil #'equal ))
399
+ ((let ((predicate (plist-get entry :predicate )))
400
+ (or (null predicate)
401
+ (funcall predicate candidate))))
402
+ (parent (treesit-node-parent candidate))
403
+ (parent-type (treesit-node-type parent))
404
+ (compound-type (ensure-list (plist-get entry :compound-type ))))
405
+ (let ((matching-pair (plist-get entry :matching-pair )))
406
+ (cond
407
+ ; ; intact compound, no matching pair
408
+ ((and (member parent-type compound-type)
409
+ (not matching-pair))
410
+ (treesit-node-enclosed-p (cons bol bol) parent t ))
411
+ ; ; broken compound, no matching pair
412
+ ((and (not (member parent-type compound-type))
413
+ (not matching-pair))
414
+ t )
415
+ ; ; matching pair, but not before BOL
416
+ ((null
417
+ (treesit-filter-child
418
+ parent
419
+ (lambda (n )
420
+ (and (string-equal (treesit-node-type n)
421
+ matching-pair)
422
+ (< (treesit-node-start candidate)
423
+ (treesit-node-start n)
424
+ bol)))))))))))
425
+ (pcase op
426
+ ('offset
427
+ (lambda (candidate )
428
+ (let* ((type (treesit-node-type candidate))
429
+ (entry (alist-get type compound-alist nil nil #'equal )))
430
+ (let ((offset (plist-get entry :offset )))
431
+ (pcase offset
432
+ ((pred null ) gpr-ts-mode-indent-offset)
433
+ ((pred functionp) (funcall offset candidate))
434
+ ((pred integerp) offset)
435
+ ((pred symbolp) (symbol-value offset))
436
+ (_ (error " Unknown offset: %s " offset)))))))
437
+ ((or 'anchor 'test (pred null ))
438
+ (lambda (candidate )
439
+ (let* ((type (treesit-node-type candidate))
440
+ (entry (alist-get type compound-alist nil nil #'equal ))
441
+ (anchor-bol (plist-get entry :anchor-bol )))
442
+ (if anchor-bol
443
+ (save-excursion
444
+ (goto-char (treesit-node-start candidate))
445
+ (forward-line 0 )
446
+ (treesit-node-at (point ))
447
+ (if (eq op 'test )
448
+ (treesit-node-at (point ))
449
+ (treesit-node-start (treesit-node-at (point )))))
450
+ (if (eq op 'test )
451
+ candidate
452
+ (treesit-node-start candidate))))))
453
+ (_ (error " Unknown operation: %s " op)))
454
+ nil ))
455
+ (setq parent (treesit-node-parent parent)))
456
+ ; ; Pick the match which is closest to point
457
+ (if (eq op 'test )
458
+ matches
459
+ (caar (reverse matches))))))
460
+
461
+ (defalias 'gpr-ts-mode--indent-error-recovery-exists-p
462
+ 'gpr-ts-mode--indent-error-recovery )
463
+
464
+ (defun gpr-ts-mode--anchor-of-indent-error-recovery ()
465
+ " Determine indentation anchor of error recovery point."
466
+ (gpr-ts-mode--indent-error-recovery 'anchor ))
467
+
468
+ (defun gpr-ts-mode--offset-of-indent-error-recovery ()
469
+ " Determine indentation offset of error recovery point."
470
+ (gpr-ts-mode--indent-error-recovery 'offset ))
471
+
292
472
(defun gpr-ts-mode--offset-of-next-sibling-not-matching (type )
293
473
" Determine indentation offset of next sibling not matching TYPE."
294
474
(lambda (node parent bol &rest _ )
@@ -392,11 +572,41 @@ Return nil if no child of that type is found."
392
572
393
573
(defvar gpr-ts-mode--indent-rules
394
574
`((gpr
575
+
576
+ ; ; Non-Parent-driven indentation
577
+
578
+ ; ; Indent empty lines immediately following a case_item as part
579
+ ; ; of the case_item. This allows additional lines to keep being
580
+ ; ; added to the case_item without causing indentation to jump
581
+ ; ; after each newline.
582
+ ((and no-node
583
+ (gpr-ts-mode--prev-nonextra-sibling-matches-p " case_item" ))
584
+ (gpr-ts-mode--anchor-first-sibling-matching " case_item" )
585
+ gpr-ts-mode-indent-offset)
586
+
587
+ ; ; Parent ERROR recovery rules.
588
+
589
+ ((and (or (parent-is " ERROR" )
590
+ (gpr-ts-mode--prev-sibling-matches-p " ERROR" ))
591
+ (gpr-ts-mode--indent-error-recovery-exists-p))
592
+ (gpr-ts-mode--anchor-of-indent-error-recovery)
593
+ (gpr-ts-mode--offset-of-indent-error-recovery))
594
+
595
+ ; ; When previous parent error recovery fails, likely a top-level
596
+ ; ; construct so anchor to the first column without an offset.
597
+ ; ; This is a catch-all for any remaining parent ERROR nodes as
598
+ ; ; many rules that follow assume a valid parent node and don't
599
+ ; ; explicitly check.
600
+ ((parent-is " ERROR" ) column-0 0 )
601
+
602
+ ; ; Normal indentation rules.
603
+
395
604
; ; top-level
396
605
((parent-is ,(rx bos " project" eos)) column-0 0 )
397
606
; ; with_declaration
398
607
((and (parent-is " with_declaration" )
399
608
(or (node-is " string_literal" )
609
+ no-node
400
610
(node-is " ," ))
401
611
(gpr-ts-mode--after-first-sibling-p " string_literal" ))
402
612
(gpr-ts-mode--anchor-first-sibling-matching " string_literal" )
@@ -408,6 +618,7 @@ Return nil if no child of that type is found."
408
618
; ; expression / expression_list
409
619
((and (parent-is " expression_list" )
410
620
(or (node-is ,(rx bos " expression" eos))
621
+ no-node
411
622
(node-is " ," ))
412
623
(gpr-ts-mode--after-first-sibling-p " expression" ))
413
624
(gpr-ts-mode--anchor-first-sibling-matching " expression" )
@@ -419,6 +630,10 @@ Return nil if no child of that type is found."
419
630
((parent-is ,(rx bos " expression" eos))
420
631
parent
421
632
gpr-ts-mode-indent-exp-item-offset)
633
+ ((or (parent-is ,(rx bos " project_reference" eos))
634
+ (parent-is ,(rx bos " variable_reference" eos)))
635
+ parent
636
+ 0 )
422
637
((node-is " expression_list" )
423
638
parent
424
639
gpr-ts-mode-indent-broken-offset)
0 commit comments