emacs/lisp/init-lang.el 5.1 K raw
1
;;; init-lang.el -*- lexical-binding: t; -*-
2
3
;;; Syntax highlighting: tree-sitter
4
(setopt treesit-enabled-modes t
5
        treesit-auto-install-grammar 'always
6
        treesit-font-lock-level 4)      ; 4 = everything (calls, properties, operators)
7
8
;;; LSP: eglot
9
(defvar my/lsp-servers
10
  '(((typescript-ts-mode tsx-ts-mode js-ts-mode) "vtsls" "--stdio")
11
    (rust-ts-mode       "rust-analyzer")
12
    ((go-ts-mode go-mod-ts-mode) "gopls")
13
    ((c-ts-mode c++-ts-mode) "clangd")
14
    (lua-ts-mode        "lua-language-server")
15
    ;;(css-ts-mode        "vscode-css-language-server" "--stdio")
16
    ((mhtml-ts-mode html-ts-mode) "vscode-html-language-server" "--stdio")
17
    (json-ts-mode       "vscode-json-language-server" "--stdio")
18
    (python-ts-mode     "pylsp")
19
    (python-ts-mode     "basedpyright-langserver" "--stdio") ; wins if both installed
20
    (bash-ts-mode       "bash-language-server" "start")
21
    (scheme-mode        "guile-lsp-server"))
22
  "Alist of (MODES SERVER ARGS...) used to auto-start eglot.")
23
24
(use-package eglot
25
  :ensure nil
26
  :defer t
27
  :custom
28
  (eglot-autoshutdown t)                ; kill server when last buffer closes
29
  (eglot-events-buffer-config '(:size 0 :format full)) ; no event logging
30
  (eglot-extend-to-xref t)              ; stay in eglot when jumping into deps
31
  :config
32
  (dolist (entry my/lsp-servers)
33
    (when (executable-find (cadr entry))
34
      (add-to-list 'eglot-server-programs
35
                   (cons (ensure-list (car entry)) (cdr entry))))))
36
37
;; Hooks go in now so eglot itself only loads when a matching buffer opens
38
(dolist (entry my/lsp-servers)
39
  (when (executable-find (cadr entry))
40
    (dolist (mode (ensure-list (car entry)))
41
      (add-hook (intern (format "%s-hook" mode)) #'eglot-ensure))))
42
43
;;; Indentation per language
44
(defvar my/indent-settings
45
  '(((typescript-ts-mode tsx-ts-mode) 2 nil typescript-ts-mode-indent-offset)
46
    (js-ts-mode                       2 nil js-indent-level)
47
    (rust-ts-mode                     4 nil rust-ts-mode-indent-offset)
48
    (go-ts-mode                       4 t   go-ts-mode-indent-offset)
49
    ((c-ts-mode c++-ts-mode)          4 nil c-ts-mode-indent-offset)
50
    (lua-ts-mode                      2 nil lua-ts-indent-offset)
51
    (css-ts-mode                      2 nil css-indent-offset)
52
    ((mhtml-ts-mode html-ts-mode)     2 nil html-ts-mode-indent-offset)
53
    (json-ts-mode                     2 nil json-ts-mode-indent-offset)
54
    (python-ts-mode                   4 nil python-indent-offset)
55
    (bash-ts-mode                     2 nil sh-basic-offset)
56
    ((emacs-lisp-mode scheme-mode)    2 nil nil))
57
  "List of (MODES WIDTH TABS OFFSET-VAR) applied in each mode's hook.")
58
59
(dolist (entry my/indent-settings)
60
  (pcase-let ((`(,modes ,width ,tabs ,var) entry))
61
    (dolist (mode (ensure-list modes))
62
      (add-hook (intern (format "%s-hook" mode))
63
                (lambda ()
64
                  (setq-local tab-width width
65
                              indent-tabs-mode tabs)
66
                  (when var (set (make-local-variable var) width)))))))
67
68
;;; Format on save
69
;; Uses eglot when the server can format, else re-indents lisp buffers.  Off for a
70
;; buffer with SPC c F, or per project via .dir-locals.el:
71
(defvar my/format-on-save t
72
  "Non-nil formats eglot-managed buffers before saving.")
73
(put 'my/format-on-save 'safe-local-variable #'booleanp)
74
75
(defvar my/reindent-modes '(emacs-lisp-mode scheme-mode)
76
  "Modes without an LSP formatter that format by re-indenting the buffer.")
77
78
(defun my/format-buffer ()
79
  "Format with eglot if the server can, else re-indent in `my/reindent-modes'."
80
  (interactive)
81
  (cond ((and (fboundp 'eglot-managed-p) (eglot-managed-p)
82
              (eglot-server-capable :documentFormattingProvider))
83
         (eglot-format-buffer))
84
        ((apply #'derived-mode-p my/reindent-modes)
85
         (indent-region (point-min) (point-max)))
86
        ((called-interactively-p 'any)
87
         (message "No formatter for %s" major-mode))))
88
89
(defun my/format-on-save ()
90
  (when my/format-on-save (my/format-buffer)))
91
92
(add-hook 'before-save-hook #'my/format-on-save)
93
94
(defun my/toggle-format-on-save ()
95
  "Toggle format on save for the current buffer."
96
  (interactive)
97
  (setq-local my/format-on-save (not my/format-on-save))
98
  (message "Format on save %s" (if my/format-on-save "on" "off")))
99
100
;; Diagnostics
101
(setq flymake-show-diagnostics-at-end-of-line 'short)
102
103
(with-eval-after-load 'general
104
  (my/leader
105
    "c"   '(:ignore t :wk "code")
106
    "ca"  '(eglot-code-actions :wk "actions")
107
    "cr"  '(eglot-rename :wk "rename")
108
    "cf"  '(my/format-buffer :wk "format")
109
    "cF"  '(my/toggle-format-on-save :wk "toggle format on save")
110
    "cd"  '(xref-find-definitions :wk "definition")
111
    "cR"  '(xref-find-references :wk "references")
112
    "ci"  '(eglot-find-implementation :wk "implementation")
113
    "ct"  '(eglot-find-typeDefinition :wk "type def")
114
    "ce"  '(flymake-show-buffer-diagnostics :wk "errors")
115
    "cE"  '(flymake-show-project-diagnostics :wk "project errors")
116
    "cn"  '(flymake-goto-next-error :wk "next error")
117
    "cp"  '(flymake-goto-prev-error :wk "prev error")
118
    "cl"  '(eglot :wk "start lsp")
119
    "cL"  '(eglot-shutdown :wk "stop lsp")))
120
121
(provide 'init-lang)