summaryrefslogtreecommitdiff
path: root/slang-lsp.el
blob: d71e6413244f824509108b3f91ba93424d8089d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
;;; -*- lexical- binding: t; -*-
;;; slang-lsp.el --- LSP mode for Slang shader files

;; Author: Michael Ghaben <michaelghaben@gmail.com>
;; Version: 0.0.2
;; Package-Requires: ((emacs "28.1") (cape "2.2.0") (slang-mode "0.0.1"))
;; Keywords: slang shaders vulkan opengl spir-v lsp lsp-server

;;; Code:
(require 'eglot)
(require 'slang-mode)
(require 'cape)

(defgroup slang-lsp nil
  "LSP integration for Slang mode."
  :group 'slang
  :prefix "slang-lsp-")

(defcustom slang-lsp-server-executable "slangd"
  "Path to the slangd language server executable.
Can be either a full path or just the executable name if it's in PATH."
  :type 'string
  :group 'slang-lsp)

(defcustom slang-lsp-server-args '()
  "Additional arguments to pass to the slangd server."
  :type '(repeat string)
  :group 'slang-lsp)

(defcustom slang-lsp-auto-enable t
  "Whether to automatically enable LSP when opening Slang files."
  :type 'boolean
  :group 'slang-lsp)

(defcustom slang-lsp-server-search-paths
  '("/usr/local/bin"
    "/usr/bin"
    "~/.local/bin"
    "~/.local/slang/bin"
    "~/bin"
    "/opt/slang/bin")
  "Paths to search for slangd executable."
  :type '(repeat directory)
  :group 'slang-lsp)

(defun slang-lsp--find-server-executable ()
  "Find the slangd executable in the system.
Returns the full path to slangd if found, nil otherwise."
  (or
   ;; Check if custom executable path is valid
   (when (and slang-lsp-server-executable
              (not (string= slang-lsp-server-executable "slangd"))
              (file-executable-p slang-lsp-server-executable))
     slang-lsp-server-executable)
   
   ;; Check if slangd is in PATH
   (executable-find "slangd")
   
   ;; Search in common installation paths
   (cl-some (lambda (path)
              (let ((full-path (expand-file-name "slangd" path)))
                (when (file-executable-p full-path)
                  full-path)))
            slang-lsp-server-search-paths)))

(defun slang-lsp-server-available-p ()
  "Check if slangd server is available on the system."
  (not (null (slang-lsp--find-server-executable))))

(defun slang-lsp-setup-server ()
  "Configure eglot to use slangd for Slang files."
  (let ((server-path (slang-lsp--find-server-executable)))
    (if server-path
        (progn
          (add-to-list 'eglot-server-programs
                       `((slang-mode) . (,server-path ,@slang-lsp-server-args)))
          (message "Slang LSP: configured slangd server at %s" server-path))
      (message "Slang LSP: slangd server not found. Please install Slang compiler or set slang-lsp-server-executable."))))

(defun slang-lsp-start ()
  "Start LSP server for current Slang buffer."
  (interactive)
  (if (slang-lsp-server-available-p)
      (eglot-ensure)
    (user-error "slangd server not available. Please install Slang compiler")))

(defun slang-lsp-restart ()
  "Restart LSP server for current Slang buffer."
  (interactive)
  (when (eglot-current-server)
    (eglot-shutdown (eglot-current-server)))
  (slang-lsp-start))

(defun slang-lsp-stop ()
  "Stop LSP server for current buffer."
  (interactive)
  (when (eglot-current-server)
    (eglot-shutdown (eglot-current-server))))

(defun slang-lsp-show-server-info ()
  "Display information about the current LSP server."
  (interactive)
  (if-let ((server (eglot-current-server)))
      (message "Slang LSP: Server running at %s (PID: %s)"
               (eglot--server-capable-or-lose server :serverInfo :name "Unknown")
               (process-id (eglot--process server)))
    (message "Slang LSP: No server running for this buffer")))

(defun slang-lsp-update-configuration ()
  "Send updated workspace configuration to the language server."
  (interactive)
  (if-let ((server (eglot-current-server)))
      (progn
        (slang-lsp--setup-workspace-configuration)
        (eglot-signal-didChangeConfiguration server)
        (message "Slang LSP: Configuration updated"))
    (message "Slang LSP: No server running for this buffer")))

;; Enhanced mode configuration
(defun slang-lsp-configure-buffer ()
  "Configure current buffer for optimal LSP experience."
  (when (derived-mode-p 'slang-mode)
    ;; Enable common LSP features
    (setq-local eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly)
    
    ;; Configure completion (use what's available)
    (if (fboundp 'cape-dabbrev)
        (setq-local completion-at-point-functions
                    (list #'eglot-completion-at-point
                          #'cape-dabbrev
                          #'cape-file))
      (setq-local completion-at-point-functions
                  (list #'eglot-completion-at-point
                        #'dabbrev-completion)))
    
    ;; Set up keybindings - only use capabilities that slangd actually supports
    (local-set-key (kbd "C-c l d") #'xref-find-definitions)  ; Uses definitionProvider
    ;; Note: slangd doesn't support referencesProvider, implementationProvider, or typeDefinitionProvider
    ;; so we don't bind those keys to avoid errors
    (local-set-key (kbd "C-c l n") #'eglot-rename)
    (local-set-key (kbd "C-c l f") #'eglot-format)
    (local-set-key (kbd "C-c l a") #'eglot-code-actions)
    (local-set-key (kbd "C-c l h") #'eldoc)
    (local-set-key (kbd "C-c l s") #'slang-lsp-show-server-info)
    (local-set-key (kbd "C-c l R") #'slang-lsp-restart)
    (local-set-key (kbd "C-c l c") #'slang-lsp-update-configuration)))

;; Auto-enable hook
(defun slang-lsp-auto-enable ()
  "Automatically enable LSP for Slang files if configured."
  (when (and slang-lsp-auto-enable
             (slang-lsp-server-available-p))
    (slang-lsp-start)
    (slang-lsp-configure-buffer)))

;; Initialization function
(defun slang-lsp-initialize ()
  "Initialize Slang LSP integration."
  (slang-lsp-setup-server)
  
  ;; Add hook for auto-enabling LSP
  (add-hook 'slang-mode-hook #'slang-lsp-auto-enable)
  
  ;; Configure eglot for better Slang support
  (with-eval-after-load 'eglot
    ;; Slang-specific server configuration
    (add-to-list 'eglot-stay-out-of 'flymake) ; Use eglot's diagnostics
    
    ;; Enhanced diagnostics display
    (setq eglot-events-buffer-size 0) ; Disable events buffer for performance
    (setq eglot-autoshutdown t) ; Auto-shutdown when last buffer is closed
    
    ;; Optional: Log when LSP connects successfully
    (add-hook 'eglot-managed-mode-hook
              (lambda ()
                (when (derived-mode-p 'slang-mode)
                  (message "Slang LSP: Connected successfully"))))
    
    ;; Define workspace configuration for Slang LSP
    (defun slang-lsp--workspace-configuration (server)
      "Return workspace configuration for Slang Language Server."
      (list :slang (list :completion (list :enableSnippets t
                                          :enableAutoImport t)
                        :diagnostics (list :enableExperimental t)
                        :formatting (list :enable t)
                        :hover (list :enable t)
                        :semanticTokens (list :enable t)
                        :inlayHints (list :enable t))))

    
    ;; Set up workspace configuration for slang-mode buffers
    (defun slang-lsp--setup-workspace-configuration ()
      "Set up workspace configuration for current slang buffer."
      (setq-local eglot-workspace-configuration
                  (slang-lsp--workspace-configuration nil)))
    
    ;; Hook to set up workspace configuration when LSP starts
    (add-hook 'eglot-managed-mode-hook
              (lambda ()
                (when (derived-mode-p 'slang-mode)
                  (slang-lsp--setup-workspace-configuration))))
    ))

;; Interactive commands for menu
(defun slang-lsp-install-guide ()
  "Show installation guide for slangd."
  (interactive)
  (message "To install slangd LSP server: 1) Download Slang from https://github.com/shader-slang/slang/releases 2) Extract slangd executable from bin/ directory 3) Add to PATH or set slang-lsp-server-executable 4) Restart Emacs. NOTE: The slangd from Vulkan SDK is NOT the LSP server!"))

(defun slang-lsp-check-server-type ()
  "Check if the current slangd is the correct LSP server."
  (interactive)
  (let ((server-path (slang-lsp--find-server-executable)))
    (if server-path
        (message "✓ Found slangd at: %s (with LSP symbols based on your analysis)" server-path)
      (message "No slangd executable found. Download from https://github.com/shader-slang/slang/releases"))))

(defun slang-lsp-test-server-manual ()
  "Manually test the slangd server connection."
  (interactive)
  (let ((server-path (slang-lsp--find-server-executable)))
    (if server-path
        (let* ((buffer-name "*slangd-test*")
               (process (start-process "slangd-test" buffer-name server-path)))
          (set-process-query-on-exit-flag process nil)
          (message "Started slangd test process. Check buffer: %s" buffer-name)
          ;; Send a simple LSP initialize request
          (run-with-timer 1 nil
            (lambda ()
              (when (process-live-p process)
                (process-send-string process 
                  "Content-Length: 122\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"capabilities\":{},\"processId\":null}}\n")
                (message "Sent initialize request to slangd"))))
          ;; Kill after 5 seconds
          (run-with-timer 5 nil
            (lambda ()
              (when (process-live-p process)
                (kill-process process)
                (message "Killed slangd test process")))))
      (message "No slangd executable found"))))

(defun slang-lsp-debug-capabilities ()
  "Debug function to show what capabilities the LSP server supports."
  (interactive)
  (if-let ((server (eglot-current-server)))
      (let ((caps (eglot--capabilities server)))
        (with-current-buffer (get-buffer-create "*Slang LSP Debug*")
          (erase-buffer)
          (insert "=== Slang LSP Server Capabilities ===\n\n")
          (insert (format "Full capabilities object:\n%S\n\n" caps))
          (insert "Specific capability checks:\n")
          (insert (format "definitionProvider: %S\n" (eglot-server-capable :definitionProvider)))
          (insert (format "referencesProvider: %S\n" (eglot-server-capable :referencesProvider)))
          (insert (format "implementationProvider: %S\n" (eglot-server-capable :implementationProvider)))
          (insert (format "typeDefinitionProvider: %S\n" (eglot-server-capable :typeDefinitionProvider)))
          (insert (format "declarationProvider: %S\n" (eglot-server-capable :declarationProvider)))
          (pop-to-buffer (current-buffer))))
    (message "No LSP server running")))

;; Add menu items to slang-mode
(with-eval-after-load 'slang-mode
  (easy-menu-add-item slang-mode-menu nil
    '("LSP"
      ["Start LSP" slang-lsp-start :enable (not (eglot-current-server))]
      ["Stop LSP" slang-lsp-stop :enable (eglot-current-server)]
      ["Restart LSP" slang-lsp-restart :enable (eglot-current-server)]
      "---"
      ["Find Definition" xref-find-definitions :enable (eglot-current-server)]
      ;; Note: References, Implementation, Type Definition not supported by this slangd version
      ["Rename Symbol" eglot-rename :enable (eglot-current-server)]
      ["Format Buffer" eglot-format :enable (eglot-current-server)]
      ["Code Actions" eglot-code-actions :enable (eglot-current-server)]
      "---"
      ["Update Configuration" slang-lsp-update-configuration :enable (eglot-current-server)]
      ["Server Info" slang-lsp-show-server-info :enable (eglot-current-server)]
      ["Debug Capabilities" slang-lsp-debug-capabilities :enable (eglot-current-server)]
      ["Check Server Type" slang-lsp-check-server-type t]
      ["Test Server Manual" slang-lsp-test-server-manual t]
      ["Installation Guide" slang-lsp-install-guide t])))

(provide 'slang-lsp)

;;; slang-lsp.el ends here