Skip to content

Commit 1552e11

Browse files
authored
Merge pull request #26 from abougouffa/feat/code-actions
Implement code actions for "Add to dictionary", "Disable rule" and "Hide false positive"
2 parents 5c26bae + cbf746e commit 1552e11

File tree

2 files changed

+213
-42
lines changed

2 files changed

+213
-42
lines changed

README.md

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,36 @@ section of `use-package`.
4848
`lsp-ltex` supports following configuration. Each configuration is described in
4949
detail in [LTEX Settings](https://valentjn.github.io/vscode-ltex/docs/settings.html).
5050

51-
* `ltex.enabled` via `lsp-ltex-enabled`
52-
* `ltex.language` via `lsp-ltex-language`
53-
* `ltex.dictionary` via `lsp-ltex-dictionary`
54-
* `ltex.disabledRules` via `lsp-ltex-disabled-rules`
55-
* `ltex.enabledRules` via `lsp-ltex-enabled-rules`
56-
* `ltex.hiddenFalsePositives` via `lsp-ltex-hidden-false-positives`
57-
* `ltex.bibtex.fields` via `lsp-ltex-bibtex-fields`
58-
* `ltex.latex.commands` via `lsp-ltex-latex-commands`
59-
* `ltex.latex.environments` via `lsp-ltex-latex-environments`
60-
* `ltex.markdown-nodes` via `lsp-ltex-markdown-nodes`
61-
* `ltex.additionalRules.enablePickyRules` via `lsp-ltex-additional-rules-enable-picky-rules`
62-
* `ltex.additionalRules.motherTongue` via `lsp-ltex-mother-tongue`
63-
* `ltex.additionalRules.languageModel` via `lsp-ltex-additional-rules-language-model`
64-
* `ltex.additionalRules.neuralNetworkModel` via `lsp-ltex-additional-rules-neural-network-model`
65-
* `ltex.additionalRules.word2VecModel` via `lsp-ltex-additional-rules-word-2-vec-model`
66-
* `ltex.ltex-ls.languageToolHttpServerUri` via `lsp-ltex-languagetool-http-server-uri`
67-
* `ltex.ltex-ls.logLevel` via `lsp-ltex-log-level`
68-
* `ltex.java.path` via `lsp-ltex-java-path`
69-
* `ltex.java.forceTrySystemWide` via `lsp-ltex-java-force-try-system-wide`
70-
* `ltex.java.initialHeapSize` via `lsp-ltex-java-initial-heap-size`
71-
* `ltex.java.maximumHeapSize` via `lsp-ltex-java-maximum-heap-size`
72-
* `ltex.sentenceCacheSize` via `lsp-ltex-sentence-cache-size`
73-
* `ltex.diagnosticSeverity` via `lsp-ltex-diagnostic-severity`
74-
* `ltex.checkFrequency` via `lsp-ltex-check-frequency`
75-
* `ltex.clearDiagnosticsWhenClosingFile` via `lsp-ltex-clear-diagnostics-when-closing-file`
76-
* `ltex.trace.server` via `lsp-ltex-trace-server`
51+
* [`ltex.enabled`](https://valentjn.github.io/ltex/settings.html#ltexenabled) via `lsp-ltex-enabled`
52+
* [`ltex.language`](https://valentjn.github.io/ltex/settings.html#ltexlanguage) via `lsp-ltex-language`
53+
* [`ltex.dictionary`](https://valentjn.github.io/ltex/settings.html#ltexdictionary) via `lsp-ltex-dictionary`
54+
* [`ltex.disabledRules`](https://valentjn.github.io/ltex/settings.html#ltexdisabledrules) via `lsp-ltex-disabled-rules`
55+
* [`ltex.enabledRules`](https://valentjn.github.io/ltex/settings.html#ltexenabledrules) via `lsp-ltex-enabled-rules`
56+
* [`ltex.hiddenFalsePositives`](https://valentjn.github.io/ltex/settings.html#ltexhiddenfalsepositives) via `lsp-ltex-hidden-false-positives`
57+
* [`ltex.bibtex.fields`](https://valentjn.github.io/ltex/settings.html#ltexbibtexfields) via `lsp-ltex-bibtex-fields`
58+
* [`ltex.latex.commands`](https://valentjn.github.io/ltex/settings.html#ltexlatexcommands) via `lsp-ltex-latex-commands`
59+
* [`ltex.latex.environments`](https://valentjn.github.io/ltex/settings.html#ltexlatexenvironments) via `lsp-ltex-latex-environments`
60+
* [`ltex.markdown.nodes`](https://valentjn.github.io/ltex/settings.html#ltexmarkdownnodes) via `lsp-ltex-markdown-nodes`
61+
* [`ltex.additionalRules.enablePickyRules`](https://valentjn.github.io/ltex/settings.html#ltexadditionalrulesenablepickyrules) via `lsp-ltex-additional-rules-enable-picky-rules`
62+
* [`ltex.additionalRules.motherTongue`](https://valentjn.github.io/ltex/settings.html#ltexadditionalrulesmothertongue) via `lsp-ltex-mother-tongue`
63+
* [`ltex.additionalRules.languageModel`](https://valentjn.github.io/ltex/settings.html#ltexadditionalruleslanguagemodel) via `lsp-ltex-additional-rules-language-model`
64+
* [`ltex.additionalRules.neuralNetworkModel`](https://valentjn.github.io/ltex/settings.html#ltexadditionalrulesneuralnetworkmodel) via `lsp-ltex-additional-rules-neural-network-model`
65+
* [`ltex.additionalRules.word2VecModel`](https://valentjn.github.io/ltex/settings.html#ltexadditionalrulesword2vecmodel) via `lsp-ltex-additional-rules-word-2-vec-model`
66+
* [`ltex.languageToolHttpServerUri`](https://valentjn.github.io/ltex/settings.html#ltexlanguagetoolhttpserveruri) via `lsp-ltex-languagetool-http-server-uri`
67+
* [`ltex.languageToolOrg.username`](https://valentjn.github.io/ltex/settings.html#ltexlanguagetoolorgusername) via `lsp-ltex-languagetool-org-username`
68+
* [`ltex.languageToolOrg.apiKey`](https://valentjn.github.io/ltex/settings.html#ltexlanguagetoolorgapikey) via `lsp-ltex-languagetool-org-api-key`
69+
* [`ltex.ltex-ls.path`](https://valentjn.github.io/ltex/settings.html#ltexltex-lspath) via `lsp-ltex-ls-path`
70+
* [`ltex.ltex-ls.logLevel`](https://valentjn.github.io/ltex/settings.html#ltexltex-lsloglevel) via `lsp-ltex-log-level`
71+
* [`ltex.java.path`](https://valentjn.github.io/ltex/settings.html#ltexjavapath) via `lsp-ltex-java-path`
72+
* [`ltex.java.initialHeapSize`](https://valentjn.github.io/ltex/settings.html#ltexjavainitialheapsize) via `lsp-ltex-java-initial-heap-size`
73+
* [`ltex.java.maximumHeapSize`](https://valentjn.github.io/ltex/settings.html#ltexjavamaximumheapsize) via `lsp-ltex-java-maximum-heap-size`
74+
* [`ltex.sentenceCacheSize`](https://valentjn.github.io/ltex/settings.html#ltexsentencecachesize) via `lsp-ltex-sentence-cache-size`
75+
* [`ltex.completionEnabled`](https://valentjn.github.io/ltex/settings.html#ltexcompletionenabled) via `lsp-ltex-completion-enabled` (currently, not implemented)
76+
* [`ltex.diagnosticSeverity`](https://valentjn.github.io/ltex/settings.html#ltexdiagnosticseverity) via `lsp-ltex-diagnostic-severity`
77+
* [`ltex.checkFrequency`](https://valentjn.github.io/ltex/settings.html#ltexcheckfrequency) via `lsp-ltex-check-frequency`
78+
* [`ltex.clearDiagnosticsWhenClosingFile`](https://valentjn.github.io/ltex/settings.html#ltexcleardiagnosticswhenclosingfile) via `lsp-ltex-clear-diagnostics-when-closing-file`
79+
* [`ltex.statusBarItem`](https://valentjn.github.io/ltex/settings.html#ltexstatusbaritem) via `lsp-ltex-status-bar-item` (currently, not implemented)
80+
* [`ltex.trace.server`](https://valentjn.github.io/ltex/settings.html#ltextraceserver) via `lsp-ltex-trace-server`
7781

7882
## Contribute
7983

lsp-ltex.el

Lines changed: 183 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ https://github.com/valentjn/ltex-ls"
6464
:type 'file
6565
:group 'lsp-ltex)
6666

67+
(defcustom lsp-ltex-user-rules-path
68+
(let ((path (expand-file-name "lsp-ltex" user-emacs-directory)))
69+
(unless (and (file-exists-p path) (file-directory-p path))
70+
(mkdir path t))
71+
path)
72+
"The path to the directory where `lsp-ltex' will store user rules."
73+
:type 'directory
74+
:group 'lsp-ltex)
75+
6776
(defcustom lsp-ltex-enabled nil
6877
"Controls whether the extension is enabled."
6978
:type '(choice (const :tag "None" nil)
@@ -152,6 +161,26 @@ external LanguageTool HTTP server."
152161
:type 'string
153162
:group 'lsp-ltex)
154163

164+
(defcustom lsp-ltex-languagetool-org-username ""
165+
"Username/email as used to log in at languagetool.org for Premium API access.
166+
Only relevant if `lsp-ltex-languagetool-http-server-uri' is set."
167+
:type 'string
168+
:group 'lsp-ltex)
169+
170+
(defcustom lsp-ltex-languagetool-org-api-key ""
171+
"API key for Premium API access.
172+
Only relevant if `lsp-ltex-languagetool-http-server-uri' is set."
173+
:type 'string
174+
:group 'lsp-ltex)
175+
176+
(defcustom lsp-ltex-ls-path ""
177+
"If set to an empty string, LTEX automatically downloads ltex-ls from GitHub.
178+
It stores it in the folder of the extension, and uses it for the checking
179+
process. You can point this setting to an ltex-ls release you downloaded by
180+
yourself."
181+
:type 'directory
182+
:group 'lsp-ltex)
183+
155184
(defcustom lsp-ltex-log-level "fine"
156185
"Logging level (verbosity) of the ltex-ls server log."
157186
:type '(choice (const "severe")
@@ -172,12 +201,6 @@ installation instead."
172201
:type 'string
173202
:group 'lsp-ltex)
174203

175-
(defcustom lsp-ltex-java-force-try-system-wide nil
176-
"If non-nil, always try to use a system-wide Java installation before
177-
trying to use an automatically downloaded Java distribution."
178-
:type 'boolean
179-
:group 'lsp-ltex)
180-
181204
(defcustom lsp-ltex-java-initial-heap-size 64
182205
"Initial size of the Java heap memory in megabytes.
183206
Corresponds to Java's -Xmx option, this must be a positive integer"
@@ -196,6 +219,10 @@ This must be a positive integer."
196219
:type 'integer
197220
:group 'lsp-ltex)
198221

222+
(defcustom lsp-ltex-completion-enabled nil ;; TODO: Add proper implementation
223+
"If this this is enabled, auto-completion list for the current word is sent.
224+
The editor need to send a completion request.")
225+
199226
(defcustom lsp-ltex-diagnostic-severity "information"
200227
"Severity of the diagnostics corresponding to the grammar and spelling errors."
201228
:type '(choice (const "error")
@@ -216,6 +243,11 @@ This must be a positive integer."
216243
:type 'boolean
217244
:group 'lsp-ltex)
218245

246+
(defcustom lsp-ltex-status-bar-item nil ;; TODO: Add proper implementation
247+
"If non-nil, the status of LTEX is shown permanently in the status bar."
248+
:type 'boolean
249+
:group 'lsp-ltex)
250+
219251
(defcustom lsp-ltex-trace-server "off"
220252
"Debug setting to log the communication between language client and server."
221253
:type '(choice (const "off")
@@ -236,7 +268,7 @@ This must be a positive integer."
236268
;;
237269

238270
(defun lsp-ltex--s-replace (old new s)
239-
"Replaces OLD with NEW in S."
271+
"Replace OLD with NEW in S."
240272
(replace-regexp-in-string (regexp-quote old) new s t t))
241273

242274
(defun lsp-ltex--execute (cmd &rest args)
@@ -248,6 +280,60 @@ This must be a positive integer."
248280
(cl-remove-if #'null args)
249281
" ")))))))
250282

283+
(defun lsp-ltex--serialize-symbol (sym dir)
284+
"Serialize SYM to DIR.
285+
Return the written file name, or nil if SYM is not bound."
286+
(when (boundp sym)
287+
(let ((out-file (expand-file-name
288+
(lsp-ltex--s-replace "lsp-ltex--" ""
289+
(symbol-name sym))
290+
dir)))
291+
(lsp-message "[INFO] Saving `%s' to file \"%s\"" (symbol-name sym) out-file)
292+
(with-temp-buffer
293+
(prin1 (eval sym) (current-buffer))
294+
(write-file out-file))
295+
out-file)))
296+
297+
(defun lsp-ltex--deserialize-symbol (sym dir &optional mutate)
298+
"Deserialize SYM from DIR, if MUTATE is non-nil, assign the object to SYM.
299+
Return the deserialized object, or nil if the SYM.el file dont exist."
300+
(let ((in-file (expand-file-name
301+
(lsp-ltex--s-replace "lsp-ltex--" ""
302+
(symbol-name sym))
303+
dir))
304+
res)
305+
(when (file-exists-p in-file)
306+
(lsp-message "[INFO] Loading `%s' from file \"%s\"" (symbol-name sym) in-file)
307+
(with-temp-buffer
308+
(insert-file-contents in-file)
309+
(goto-char (point-min))
310+
(ignore-errors (setq res (read (current-buffer)))))
311+
(when mutate (set sym res)))
312+
res))
313+
314+
(defun lsp-ltex--add-rule (lang rule rules-plist)
315+
"Add RULE of language LANG to the plist named RULES-PLIST (symbol)."
316+
(let ((lang-key (intern (concat ":" lang))))
317+
(when (null (eval rules-plist))
318+
(set rules-plist (list lang-key [])))
319+
(plist-put (eval rules-plist) lang-key
320+
(vconcat (list rule) (plist-get (eval rules-plist) lang-key)))
321+
(when-let (out-file (lsp-ltex--serialize-symbol rules-plist lsp-ltex-user-rules-path))
322+
(lsp-message "[INFO] Rule for language %s saved to file \"%s\"" lang out-file))))
323+
324+
(defun lsp-ltex-combine-plists (&rest plists)
325+
"Create a single property list from all plists in PLISTS.
326+
Modified from `org-combine-plists'. This supposes the values to be vectors,
327+
and concatenate them."
328+
(let ((res (copy-sequence (pop plists)))
329+
prop val plist)
330+
(while plists
331+
(setq plist (pop plists))
332+
(while plist
333+
(setq prop (pop plist) val (pop plist))
334+
(setq res (plist-put res prop (vconcat val (plist-get res prop))))))
335+
res))
336+
251337
;;
252338
;; (@* "Installation and Upgrade" )
253339
;;
@@ -348,6 +434,34 @@ If current server not found, install it then."
348434
;; (@* "Activation" )
349435
;;
350436

437+
(defvar lsp-ltex--stored-dictionary
438+
(lsp-ltex--deserialize-symbol 'lsp-ltex--stored-dictionary
439+
lsp-ltex-user-rules-path)
440+
"The dictionary created from interactively added words.")
441+
442+
(defvar lsp-ltex--stored-disabled-rules
443+
(lsp-ltex--deserialize-symbol 'lsp-ltex--stored-disabled-rules
444+
lsp-ltex-user-rules-path)
445+
"The rules created from interactively added words.")
446+
447+
(defvar lsp-ltex--stored-hidden-false-positives
448+
(lsp-ltex--deserialize-symbol 'lsp-ltex--stored-hidden-false-positives
449+
lsp-ltex-user-rules-path)
450+
"The rules created from interactively added words.")
451+
452+
(defvar lsp-ltex--combined-dictionary
453+
(lsp-ltex-combine-plists lsp-ltex-dictionary lsp-ltex--stored-dictionary)
454+
"The combined `lsp-ltex-dictionary' and interactively added words.")
455+
456+
(defvar lsp-ltex--combined-disabled-rules
457+
(lsp-ltex-combine-plists lsp-ltex-disabled-rules lsp-ltex--stored-disabled-rules)
458+
"The combined `lsp-ltex-disabled-rules' and interactively added rules.")
459+
460+
(defvar lsp-ltex--combined-hidden-false-positives
461+
(lsp-ltex-combine-plists lsp-ltex-hidden-false-positives
462+
lsp-ltex--stored-hidden-false-positives)
463+
"The combined `lsp-ltex-hidden-false-positives' and interactively added rules.")
464+
351465
(defun lsp-ltex--server-entry ()
352466
"Return the server entry file.
353467
@@ -367,33 +481,80 @@ This file is use to activate the language server."
367481
(lsp-register-custom-settings
368482
'(("ltex.enabled" lsp-ltex-enabled)
369483
("ltex.language" lsp-ltex-language)
370-
("ltex.dictionary" lsp-ltex-dictionary)
371-
("ltex.disabledRules" lsp-ltex-disabled-rules)
484+
("ltex.dictionary" lsp-ltex--combined-dictionary)
485+
("ltex.disabledRules" lsp-ltex--combined-disabled-rules)
372486
("ltex.enabledRules" lsp-ltex-enabled-rules)
373-
("ltex.hiddenFalsePositives" lsp-ltex-hidden-false-positives)
487+
("ltex.hiddenFalsePositives" lsp-ltex--combined-hidden-false-positives)
374488
("ltex.bibtex.fields" lsp-ltex-bibtex-fields)
375489
("ltex.latex.commands" lsp-ltex-latex-commands)
376490
("ltex.latex.environments" lsp-ltex-latex-environments)
377-
("ltex.markdown-nodes" lsp-ltex-markdown-nodes)
378-
("ltex.additionalRules.enablePickyRules" lsp-ltex-additional-rules-enable-picky-rules)
491+
("ltex.markdown.nodes" lsp-ltex-markdown-nodes)
492+
("ltex.additionalRules.enablePickyRules" lsp-ltex-additional-rules-enable-picky-rules t)
379493
("ltex.additionalRules.motherTongue" lsp-ltex-mother-tongue)
380494
("ltex.additionalRules.languageModel" lsp-ltex-additional-rules-language-model)
381495
("ltex.additionalRules.neuralNetworkModel" lsp-ltex-additional-rules-neural-network-model)
382496
("ltex.additionalRules.word2VecModel" lsp-ltex-additional-rules-word-2-vec-model)
383-
("ltex.ltex-ls.languageToolHttpServerUri" lsp-ltex-languagetool-http-server-uri)
497+
("ltex.languageToolHttpServerUri" lsp-ltex-languagetool-http-server-uri)
498+
("ltex.languageToolOrg.username" lsp-ltex-languagetool-org-username)
499+
("ltex.languageToolOrg.apiKey" lsp-ltex-languagetool-org-api-key)
500+
("ltex.ltex-ls.path" lsp-ltex-ls-path)
384501
("ltex.ltex-ls.logLevel" lsp-ltex-log-level)
385502
("ltex.java.path" lsp-ltex-java-path)
386-
("ltex.java.forceTrySystemWide" lsp-ltex-java-force-try-system-wide)
387503
("ltex.java.initialHeapSize" lsp-ltex-java-initial-heap-size)
388504
("ltex.java.maximumHeapSize" lsp-ltex-java-maximum-heap-size)
389505
("ltex.sentenceCacheSize" lsp-ltex-sentence-cache-size)
506+
("ltex.completionEnabled" lsp-ltex-completion-enabled t)
390507
("ltex.diagnosticSeverity" lsp-ltex-diagnostic-severity)
391508
("ltex.checkFrequency" lsp-ltex-check-frequency)
392-
("ltex.clearDiagnosticsWhenClosingFile" lsp-ltex-clear-diagnostics-when-closing-file)
509+
("ltex.clearDiagnosticsWhenClosingFile" lsp-ltex-clear-diagnostics-when-closing-file t)
510+
("ltex.statusBarItem" lsp-ltex-status-bar-item t)
393511
("ltex.trace.server" lsp-ltex-trace-server)))
394512

395513
(lsp-ltex--lsp-dependency)
396514

515+
(defun lsp-ltex--action-add-to-rules (action-ht key rules-plist &optional store)
516+
"Execute action ACTION-HT by getting KEY and storing it in the RULES-PLIST.
517+
When STORE is non-nil, this will also store the new plist in the directory
518+
`lsp-ltex-user-rules-path'."
519+
(let ((args-ht (gethash key action-ht)))
520+
(dolist (lang (hash-table-keys args-ht))
521+
(mapc (lambda (rule)
522+
(lsp-ltex--add-rule lang rule rules-plist)
523+
(when store
524+
(lsp-ltex--serialize-symbol rules-plist lsp-ltex-user-rules-path)))
525+
(gethash lang args-ht)))))
526+
527+
(lsp-defun lsp-ltex--code-action-add-to-dictionary ((&Command :arguments?))
528+
"Handle action for \"_ltex.addToDictionary\"."
529+
;; Add rule internally to the `lsp-ltex--stored-dictionary' plist and
530+
;; store it in the directory `lsp-ltex-user-rules-path'
531+
(lsp-ltex--action-add-to-rules
532+
(elt arguments? 0) "words" 'lsp-ltex--stored-dictionary t)
533+
;; Combine user configured words `lsp-ltex-dictionary' and the internal
534+
;; interactively generated `lsp-ltex--stored-dictionary', and store them in
535+
;; the internal `lsp-ltex--combined-dictionary', which is sent to ltex-ls
536+
(setq lsp-ltex--combined-dictionary
537+
(lsp-ltex-combine-plists lsp-ltex-dictionary lsp-ltex--stored-dictionary))
538+
(lsp-message "[INFO] Word added to dictionary."))
539+
540+
(lsp-defun lsp-ltex--code-action-hide-false-positives ((&Command :arguments?))
541+
"Handle action for \"_ltex.hideFalsePositives\"."
542+
(lsp-ltex--action-add-to-rules (elt arguments? 0) "falsePositives"
543+
'lsp-ltex--stored-hidden-false-positives t)
544+
(setq lsp-ltex--combined-hidden-false-positives
545+
(lsp-ltex-combine-plists lsp-ltex-hidden-false-positives
546+
lsp-ltex--stored-hidden-false-positives))
547+
(lsp-message "[INFO] Rule added to false positives."))
548+
549+
(lsp-defun lsp-ltex--code-action-disable-rules ((&Command :arguments?))
550+
"Handle action for \"_ltex.disableRules\"."
551+
(lsp-ltex--action-add-to-rules (elt arguments? 0) "ruleIds"
552+
'lsp-ltex--stored-disabled-rules t)
553+
(setq lsp-ltex--combined-disabled-rules
554+
(lsp-ltex-combine-plists lsp-ltex-disabled-rules
555+
lsp-ltex--stored-disabled-rules))
556+
(lsp-message "[INFO] Rule disabled."))
557+
397558
(lsp-register-client
398559
(make-lsp-client
399560
:new-connection (lsp-stdio-connection
@@ -402,7 +563,13 @@ This file is use to activate the language server."
402563
(and (file-exists-p entry)
403564
(not (file-directory-p entry))
404565
(file-executable-p entry)))))
405-
:activation-fn (lambda (&rest _) (apply #'derived-mode-p lsp-ltex-active-modes))
566+
:activation-fn
567+
(lambda (&rest _) (apply #'derived-mode-p lsp-ltex-active-modes))
568+
:action-handlers
569+
(lsp-ht
570+
("_ltex.addToDictionary" #'lsp-ltex--code-action-add-to-dictionary)
571+
("_ltex.disableRules" #'lsp-ltex--code-action-disable-rules)
572+
("_ltex.hideFalsePositives" #'lsp-ltex--code-action-hide-false-positives))
406573
:priority -2
407574
:add-on? t
408575
:server-id 'ltex-ls

0 commit comments

Comments
 (0)