(require 'cl-lib)
(require 'map)
(defgroup splitrs nil
"Emacs integration for splitrs-lsp."
:group 'lsp
:group 'tools
:prefix "splitrs-"
:link '(url-link "https://github.com/cool-japan/splitrs"))
(defcustom splitrs-server-path "splitrs-lsp"
"Path to the splitrs-lsp binary.
Defaults to `splitrs-lsp' on $PATH (installed via `cargo install splitrs')."
:type 'string
:group 'splitrs)
(defcustom splitrs-enable-eglot t
"When non-nil, register splitrs-lsp with eglot if eglot is available."
:type 'boolean
:group 'splitrs)
(defcustom splitrs-enable-lsp-mode t
"When non-nil, register splitrs-lsp with lsp-mode if lsp-mode is available."
:type 'boolean
:group 'splitrs)
(defcustom splitrs-major-modes '(rust-mode rust-ts-mode)
"Major modes for which splitrs-lsp should be activated."
:type '(repeat symbol)
:group 'splitrs)
(defun splitrs--register-eglot ()
"Register splitrs-lsp with eglot for Rust major modes.
Uses `add-to-list' with APPEND=t so the entry goes at the end of
`eglot-server-programs'. eglot selects the first matching entry, so
rust-analyzer (if configured) remains the primary server; users who want
splitrs-lsp as the primary server should place it first manually.
Coexistence via multiple servers per buffer is documented at:
https://github.com/joaotavora/eglot#multiple-servers-per-buffer"
(when (and splitrs-enable-eglot (boundp 'eglot-server-programs))
(add-to-list
'eglot-server-programs
(cons splitrs-major-modes (list splitrs-server-path))
t )))
(with-eval-after-load 'eglot
(splitrs--register-eglot))
(defun splitrs--register-lsp-mode ()
"Register splitrs-lsp with lsp-mode.
Uses :add-on? t so splitrs-lsp runs alongside lsp-rust-analyzer without
displacing it. This is the critical flag for coexistence."
(when (and splitrs-enable-lsp-mode
(fboundp 'lsp-register-client)
(fboundp 'make-lsp-client))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection (lambda () splitrs-server-path))
:activation-fn (lsp-activate-on "rust")
:server-id 'splitrs-lsp
:priority -2 :add-on? t :notification-handlers
(ht ("window/showMessage"
(lambda (_workspace params)
(let ((type (gethash "type" params))
(msg (gethash "message" params "")))
(cond
((= type 1) (lsp--error "%s" msg))
((= type 2) (lsp--warn "%s" msg))
(t (lsp--info "%s" msg))))))
("window/logMessage"
(lambda (_workspace params)
(lsp--log-io-event 'notification "splitrs" params))))
:download-server-fn nil))))
(with-eval-after-load 'lsp-mode
(splitrs--register-lsp-mode))
(defun splitrs-refactor-current-file ()
"Invoke splitrs.split on the current buffer via workspace/executeCommand.
The server applies a WorkspaceEdit directly (via client.apply_edit), so no
client-side edit application is needed. Works with both eglot and lsp-mode."
(interactive)
(let ((file (buffer-file-name)))
(unless file
(user-error "splitrs: current buffer has no associated file"))
(let ((uri (concat "file://" (expand-file-name file))))
(cond
((and (featurep 'eglot) (fboundp 'eglot-current-server) (eglot-current-server))
(eglot-execute-command
(eglot-current-server)
"splitrs.split"
(vector (list :uri uri))))
((and (featurep 'lsp-mode) (fboundp 'lsp-send-execute-command))
(lsp-send-execute-command "splitrs.split" (list :uri uri)))
(t
(user-error "splitrs: no active LSP client found (start eglot or lsp-mode first)"))))))
(define-minor-mode splitrs-mode
"Minor mode that ensures splitrs-lsp is attached to the current buffer.
Enabling `splitrs-mode' in a Rust buffer starts splitrs-lsp (via eglot or
lsp-mode, whichever is active) if it is not already running."
:lighter " SplitRS"
:group 'splitrs
(when splitrs-mode
(cond
((and (featurep 'eglot) (fboundp 'eglot-ensure))
(eglot-ensure))
((and (featurep 'lsp-mode) (fboundp 'lsp-deferred))
(lsp-deferred)))))
(defun splitrs-setup ()
"Register splitrs-lsp with eglot and/or lsp-mode.
Call this after requiring the package if you prefer not to use the minor mode."
(splitrs--register-eglot)
(splitrs--register-lsp-mode))
(provide 'splitrs)