diff options
| author | Michael Ghaben <michaelghaben@gmail.com> | 2025-10-29 00:48:13 -0700 |
|---|---|---|
| committer | Michael Ghaben <michaelghaben@gmail.com> | 2025-10-29 00:48:13 -0700 |
| commit | 4e7de3567baef5e8bee140909be87f608b3db8b9 (patch) | |
| tree | ff2b7b6b4fd417b002f77dbc0de964781418f079 /slang-lsp.el | |
Initial commit
Diffstat (limited to 'slang-lsp.el')
| -rw-r--r-- | slang-lsp.el | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/slang-lsp.el b/slang-lsp.el new file mode 100644 index 0000000..b5edf80 --- /dev/null +++ b/slang-lsp.el | |||
| @@ -0,0 +1,277 @@ | |||
| 1 | ;;; -*- lexical- binding: t; -*- | ||
| 2 | ;;; slang-lsp.el --- LSP mode for Slang shader files | ||
| 3 | |||
| 4 | ;;; Code: | ||
| 5 | (require 'eglot) | ||
| 6 | (require 'slang-mode) | ||
| 7 | |||
| 8 | |||
| 9 | (defgroup slang-lsp nil | ||
| 10 | "LSP integration for Slang mode." | ||
| 11 | :group 'slang | ||
| 12 | :prefix "slang-lsp-") | ||
| 13 | |||
| 14 | (defcustom slang-lsp-server-executable "slangd" | ||
| 15 | "Path to the slangd language server executable. | ||
| 16 | Can be either a full path or just the executable name if it's in PATH." | ||
| 17 | :type 'string | ||
| 18 | :group 'slang-lsp) | ||
| 19 | |||
| 20 | (defcustom slang-lsp-server-args '() | ||
| 21 | "Additional arguments to pass to the slangd server." | ||
| 22 | :type '(repeat string) | ||
| 23 | :group 'slang-lsp) | ||
| 24 | |||
| 25 | (defcustom slang-lsp-auto-enable t | ||
| 26 | "Whether to automatically enable LSP when opening Slang files." | ||
| 27 | :type 'boolean | ||
| 28 | :group 'slang-lsp) | ||
| 29 | |||
| 30 | (defcustom slang-lsp-server-search-paths | ||
| 31 | '("/usr/local/bin" | ||
| 32 | "/usr/bin" | ||
| 33 | "~/.local/bin" | ||
| 34 | "~/bin" | ||
| 35 | "/opt/slang/bin") | ||
| 36 | "Paths to search for slangd executable." | ||
| 37 | :type '(repeat directory) | ||
| 38 | :group 'slang-lsp) | ||
| 39 | |||
| 40 | (defun slang-lsp--find-server-executable () | ||
| 41 | "Find the slangd executable in the system. | ||
| 42 | Returns the full path to slangd if found, nil otherwise." | ||
| 43 | (or | ||
| 44 | ;; Check if custom executable path is valid | ||
| 45 | (when (and slang-lsp-server-executable | ||
| 46 | (not (string= slang-lsp-server-executable "slangd")) | ||
| 47 | (file-executable-p slang-lsp-server-executable)) | ||
| 48 | slang-lsp-server-executable) | ||
| 49 | |||
| 50 | ;; Check if slangd is in PATH | ||
| 51 | (executable-find "slangd") | ||
| 52 | |||
| 53 | ;; Search in common installation paths | ||
| 54 | (cl-some (lambda (path) | ||
| 55 | (let ((full-path (expand-file-name "slangd" path))) | ||
| 56 | (when (file-executable-p full-path) | ||
| 57 | full-path))) | ||
| 58 | slang-lsp-server-search-paths))) | ||
| 59 | |||
| 60 | (defun slang-lsp-server-available-p () | ||
| 61 | "Check if slangd server is available on the system." | ||
| 62 | (not (null (slang-lsp--find-server-executable)))) | ||
| 63 | |||
| 64 | (defun slang-lsp-setup-server () | ||
| 65 | "Configure eglot to use slangd for Slang files." | ||
| 66 | (let ((server-path (slang-lsp--find-server-executable))) | ||
| 67 | (if server-path | ||
| 68 | (progn | ||
| 69 | (add-to-list 'eglot-server-programs | ||
| 70 | `((slang-mode) . (,server-path ,@slang-lsp-server-args))) | ||
| 71 | (message "Slang LSP: configured slangd server at %s" server-path)) | ||
| 72 | (message "Slang LSP: slangd server not found. Please install Slang compiler or set slang-lsp-server-executable.")))) | ||
| 73 | |||
| 74 | (defun slang-lsp-start () | ||
| 75 | "Start LSP server for current Slang buffer." | ||
| 76 | (interactive) | ||
| 77 | (if (slang-lsp-server-available-p) | ||
| 78 | (eglot-ensure) | ||
| 79 | (user-error "slangd server not available. Please install Slang compiler"))) | ||
| 80 | |||
| 81 | (defun slang-lsp-restart () | ||
| 82 | "Restart LSP server for current Slang buffer." | ||
| 83 | (interactive) | ||
| 84 | (when (eglot-current-server) | ||
| 85 | (eglot-shutdown (eglot-current-server))) | ||
| 86 | (slang-lsp-start)) | ||
| 87 | |||
| 88 | (defun slang-lsp-stop () | ||
| 89 | "Stop LSP server for current buffer." | ||
| 90 | (interactive) | ||
| 91 | (when (eglot-current-server) | ||
| 92 | (eglot-shutdown (eglot-current-server)))) | ||
| 93 | |||
| 94 | (defun slang-lsp-show-server-info () | ||
| 95 | "Display information about the current LSP server." | ||
| 96 | (interactive) | ||
| 97 | (if-let ((server (eglot-current-server))) | ||
| 98 | (message "Slang LSP: Server running at %s (PID: %s)" | ||
| 99 | (eglot--server-capable-or-lose server :serverInfo :name "Unknown") | ||
| 100 | (process-id (eglot--process server))) | ||
| 101 | (message "Slang LSP: No server running for this buffer"))) | ||
| 102 | |||
| 103 | (defun slang-lsp-update-configuration () | ||
| 104 | "Send updated workspace configuration to the language server." | ||
| 105 | (interactive) | ||
| 106 | (if-let ((server (eglot-current-server))) | ||
| 107 | (progn | ||
| 108 | (slang-lsp--setup-workspace-configuration) | ||
| 109 | (eglot-signal-didChangeConfiguration server) | ||
| 110 | (message "Slang LSP: Configuration updated")) | ||
| 111 | (message "Slang LSP: No server running for this buffer"))) | ||
| 112 | |||
| 113 | ;; Enhanced mode configuration | ||
| 114 | (defun slang-lsp-configure-buffer () | ||
| 115 | "Configure current buffer for optimal LSP experience." | ||
| 116 | (when (derived-mode-p 'slang-mode) | ||
| 117 | ;; Enable common LSP features | ||
| 118 | (setq-local eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly) | ||
| 119 | |||
| 120 | ;; Configure completion (use what's available) | ||
| 121 | (if (fboundp 'cape-dabbrev) | ||
| 122 | (setq-local completion-at-point-functions | ||
| 123 | (list #'eglot-completion-at-point | ||
| 124 | #'cape-dabbrev | ||
| 125 | #'cape-file)) | ||
| 126 | (setq-local completion-at-point-functions | ||
| 127 | (list #'eglot-completion-at-point | ||
| 128 | #'dabbrev-completion))) | ||
| 129 | |||
| 130 | ;; Set up keybindings - only use capabilities that slangd actually supports | ||
| 131 | (local-set-key (kbd "C-c l d") #'xref-find-definitions) ; Uses definitionProvider | ||
| 132 | ;; Note: slangd doesn't support referencesProvider, implementationProvider, or typeDefinitionProvider | ||
| 133 | ;; so we don't bind those keys to avoid errors | ||
| 134 | (local-set-key (kbd "C-c l n") #'eglot-rename) | ||
| 135 | (local-set-key (kbd "C-c l f") #'eglot-format) | ||
| 136 | (local-set-key (kbd "C-c l a") #'eglot-code-actions) | ||
| 137 | (local-set-key (kbd "C-c l h") #'eldoc) | ||
| 138 | (local-set-key (kbd "C-c l s") #'slang-lsp-show-server-info) | ||
| 139 | (local-set-key (kbd "C-c l R") #'slang-lsp-restart) | ||
| 140 | (local-set-key (kbd "C-c l c") #'slang-lsp-update-configuration))) | ||
| 141 | |||
| 142 | ;; Auto-enable hook | ||
| 143 | (defun slang-lsp-auto-enable () | ||
| 144 | "Automatically enable LSP for Slang files if configured." | ||
| 145 | (when (and slang-lsp-auto-enable | ||
| 146 | (slang-lsp-server-available-p)) | ||
| 147 | (slang-lsp-start) | ||
| 148 | (slang-lsp-configure-buffer))) | ||
| 149 | |||
| 150 | ;; Initialization function | ||
| 151 | (defun slang-lsp-initialize () | ||
| 152 | "Initialize Slang LSP integration." | ||
| 153 | (slang-lsp-setup-server) | ||
| 154 | |||
| 155 | ;; Add hook for auto-enabling LSP | ||
| 156 | (add-hook 'slang-mode-hook #'slang-lsp-auto-enable) | ||
| 157 | |||
| 158 | ;; Configure eglot for better Slang support | ||
| 159 | (with-eval-after-load 'eglot | ||
| 160 | ;; Slang-specific server configuration | ||
| 161 | (add-to-list 'eglot-stay-out-of 'flymake) ; Use eglot's diagnostics | ||
| 162 | |||
| 163 | ;; Enhanced diagnostics display | ||
| 164 | (setq eglot-events-buffer-size 0) ; Disable events buffer for performance | ||
| 165 | (setq eglot-autoshutdown t) ; Auto-shutdown when last buffer is closed | ||
| 166 | |||
| 167 | ;; Optional: Log when LSP connects successfully | ||
| 168 | (add-hook 'eglot-managed-mode-hook | ||
| 169 | (lambda () | ||
| 170 | (when (derived-mode-p 'slang-mode) | ||
| 171 | (message "Slang LSP: Connected successfully")))) | ||
| 172 | |||
| 173 | ;; Define workspace configuration for Slang LSP | ||
| 174 | (defun slang-lsp--workspace-configuration (server) | ||
| 175 | "Return workspace configuration for Slang Language Server." | ||
| 176 | (list :slang (list :completion (list :enableSnippets t | ||
| 177 | :enableAutoImport t) | ||
| 178 | :diagnostics (list :enableExperimental t) | ||
| 179 | :formatting (list :enable t) | ||
| 180 | :hover (list :enable t) | ||
| 181 | :semanticTokens (list :enable t) | ||
| 182 | :inlayHints (list :enable t)))) | ||
| 183 | |||
| 184 | |||
| 185 | ;; Set up workspace configuration for slang-mode buffers | ||
| 186 | (defun slang-lsp--setup-workspace-configuration () | ||
| 187 | "Set up workspace configuration for current slang buffer." | ||
| 188 | (setq-local eglot-workspace-configuration | ||
| 189 | (slang-lsp--workspace-configuration nil))) | ||
| 190 | |||
| 191 | ;; Hook to set up workspace configuration when LSP starts | ||
| 192 | (add-hook 'eglot-managed-mode-hook | ||
| 193 | (lambda () | ||
| 194 | (when (derived-mode-p 'slang-mode) | ||
| 195 | (slang-lsp--setup-workspace-configuration)))) | ||
| 196 | )) | ||
| 197 | |||
| 198 | ;; Interactive commands for menu | ||
| 199 | (defun slang-lsp-install-guide () | ||
| 200 | "Show installation guide for slangd." | ||
| 201 | (interactive) | ||
| 202 | (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!")) | ||
| 203 | |||
| 204 | (defun slang-lsp-check-server-type () | ||
| 205 | "Check if the current slangd is the correct LSP server." | ||
| 206 | (interactive) | ||
| 207 | (let ((server-path (slang-lsp--find-server-executable))) | ||
| 208 | (if server-path | ||
| 209 | (message "✓ Found slangd at: %s (with LSP symbols based on your analysis)" server-path) | ||
| 210 | (message "No slangd executable found. Download from https://github.com/shader-slang/slang/releases")))) | ||
| 211 | |||
| 212 | (defun slang-lsp-test-server-manual () | ||
| 213 | "Manually test the slangd server connection." | ||
| 214 | (interactive) | ||
| 215 | (let ((server-path (slang-lsp--find-server-executable))) | ||
| 216 | (if server-path | ||
| 217 | (let* ((buffer-name "*slangd-test*") | ||
| 218 | (process (start-process "slangd-test" buffer-name server-path))) | ||
| 219 | (set-process-query-on-exit-flag process nil) | ||
| 220 | (message "Started slangd test process. Check buffer: %s" buffer-name) | ||
| 221 | ;; Send a simple LSP initialize request | ||
| 222 | (run-with-timer 1 nil | ||
| 223 | (lambda () | ||
| 224 | (when (process-live-p process) | ||
| 225 | (process-send-string process | ||
| 226 | "Content-Length: 122\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"capabilities\":{},\"processId\":null}}\n") | ||
| 227 | (message "Sent initialize request to slangd")))) | ||
| 228 | ;; Kill after 5 seconds | ||
| 229 | (run-with-timer 5 nil | ||
| 230 | (lambda () | ||
| 231 | (when (process-live-p process) | ||
| 232 | (kill-process process) | ||
| 233 | (message "Killed slangd test process"))))) | ||
| 234 | (message "No slangd executable found")))) | ||
| 235 | |||
| 236 | (defun slang-lsp-debug-capabilities () | ||
| 237 | "Debug function to show what capabilities the LSP server supports." | ||
| 238 | (interactive) | ||
| 239 | (if-let ((server (eglot-current-server))) | ||
| 240 | (let ((caps (eglot--capabilities server))) | ||
| 241 | (with-current-buffer (get-buffer-create "*Slang LSP Debug*") | ||
| 242 | (erase-buffer) | ||
| 243 | (insert "=== Slang LSP Server Capabilities ===\n\n") | ||
| 244 | (insert (format "Full capabilities object:\n%S\n\n" caps)) | ||
| 245 | (insert "Specific capability checks:\n") | ||
| 246 | (insert (format "definitionProvider: %S\n" (eglot-server-capable :definitionProvider))) | ||
| 247 | (insert (format "referencesProvider: %S\n" (eglot-server-capable :referencesProvider))) | ||
| 248 | (insert (format "implementationProvider: %S\n" (eglot-server-capable :implementationProvider))) | ||
| 249 | (insert (format "typeDefinitionProvider: %S\n" (eglot-server-capable :typeDefinitionProvider))) | ||
| 250 | (insert (format "declarationProvider: %S\n" (eglot-server-capable :declarationProvider))) | ||
| 251 | (pop-to-buffer (current-buffer)))) | ||
| 252 | (message "No LSP server running"))) | ||
| 253 | |||
| 254 | ;; Add menu items to slang-mode | ||
| 255 | (with-eval-after-load 'slang-mode | ||
| 256 | (easy-menu-add-item slang-mode-menu nil | ||
| 257 | '("LSP" | ||
| 258 | ["Start LSP" slang-lsp-start :enable (not (eglot-current-server))] | ||
| 259 | ["Stop LSP" slang-lsp-stop :enable (eglot-current-server)] | ||
| 260 | ["Restart LSP" slang-lsp-restart :enable (eglot-current-server)] | ||
| 261 | "---" | ||
| 262 | ["Find Definition" xref-find-definitions :enable (eglot-current-server)] | ||
| 263 | ;; Note: References, Implementation, Type Definition not supported by this slangd version | ||
| 264 | ["Rename Symbol" eglot-rename :enable (eglot-current-server)] | ||
| 265 | ["Format Buffer" eglot-format :enable (eglot-current-server)] | ||
| 266 | ["Code Actions" eglot-code-actions :enable (eglot-current-server)] | ||
| 267 | "---" | ||
| 268 | ["Update Configuration" slang-lsp-update-configuration :enable (eglot-current-server)] | ||
| 269 | ["Server Info" slang-lsp-show-server-info :enable (eglot-current-server)] | ||
| 270 | ["Debug Capabilities" slang-lsp-debug-capabilities :enable (eglot-current-server)] | ||
| 271 | ["Check Server Type" slang-lsp-check-server-type t] | ||
| 272 | ["Test Server Manual" slang-lsp-test-server-manual t] | ||
| 273 | ["Installation Guide" slang-lsp-install-guide t]))) | ||
| 274 | |||
| 275 | (provide 'slang-lsp) | ||
| 276 | |||
| 277 | ;;; slang-lsp.el ends here | ||
