├── .no-sublime-package
├── .gitignore
├── demo.gif
├── Default (Linux).sublime-keymap
├── Default (OSX).sublime-keymap
├── Default (Windows).sublime-keymap
├── LanguageTool.sublime-settings
├── LTServer.py
├── LanguageList.py
├── Main.sublime-menu
├── LanguageTool.sublime-commands
├── README.md
└── LanguageTool.py
/.no-sublime-package:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
3 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtarawneh/languagetool-sublime/HEAD/demo.gif
--------------------------------------------------------------------------------
/Default (Linux).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+c"], "command": "language_tool" },
3 | { "keys": ["alt+down"], "command": "goto_next_language_problem", "args": { "jump_forward": true } },
4 | { "keys": ["alt+up"], "command": "goto_next_language_problem", "args": { "jump_forward": false } },
5 | { "keys": ["alt+shift+f"], "command": "mark_language_problem_solved", "args": { "apply_fix": true } },
6 | { "keys": ["alt+d"], "command": "mark_language_problem_solved", "args": { "apply_fix": false } }
7 | ]
8 |
--------------------------------------------------------------------------------
/Default (OSX).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+c"], "command": "language_tool" },
3 | { "keys": ["alt+down"], "command": "goto_next_language_problem", "args": { "jump_forward": true } },
4 | { "keys": ["alt+up"], "command": "goto_next_language_problem", "args": { "jump_forward": false } },
5 | { "keys": ["alt+shift+f"], "command": "mark_language_problem_solved", "args": { "apply_fix": true } },
6 | { "keys": ["alt+d"], "command": "mark_language_problem_solved", "args": { "apply_fix": false } }
7 | ]
8 |
--------------------------------------------------------------------------------
/Default (Windows).sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | { "keys": ["ctrl+shift+c"], "command": "language_tool" },
3 | { "keys": ["alt+down"], "command": "goto_next_language_problem", "args": { "jump_forward": true } },
4 | { "keys": ["alt+up"], "command": "goto_next_language_problem", "args": { "jump_forward": false } },
5 | { "keys": ["alt+shift+f"], "command": "mark_language_problem_solved", "args": { "apply_fix": true } },
6 | { "keys": ["alt+d"], "command": "mark_language_problem_solved", "args": { "apply_fix": false } }
7 | ]
8 |
--------------------------------------------------------------------------------
/LanguageTool.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | "languagetool_server_remote": "https://api.languagetool.org/v2/check",
3 | "languagetool_server_local": "http://localhost:8081/v2/check",
4 | "default_server": "remote",
5 | "display_mode": "panel",
6 | "languagetool_jar": "C:\\bin\\LanguageTool-3.5\\languagetool.jar",
7 | "highlight-scope": "comment",
8 | "ignored-scopes": [
9 | "support.function.*.latex",
10 | "meta.*.latex",
11 | "comment.*.tex",
12 | "keyword.control.tex"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LTServer.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import json
3 |
4 | def _is_ST2():
5 | return (int(sublime.version()) < 3000)
6 |
7 | if _is_ST2():
8 | from urllib import urlencode
9 | from urllib import urlopen
10 | else:
11 | try:
12 | from urlparse import urlencode
13 | from urllib2 import urlopen
14 | except ImportError:
15 | from urllib.parse import urlencode
16 | from urllib.request import urlopen
17 |
18 | def getResponse(server, text, language, disabledRules):
19 | payload = {
20 | 'language': language,
21 | 'text': text.encode('utf8'),
22 | 'User-Agent': 'sublime',
23 | 'disabledRules' : ','.join(disabledRules)
24 | }
25 | content = _post(server, payload)
26 | if content:
27 | j = json.loads(content.decode('utf-8'))
28 | return j['matches']
29 | else:
30 | return None
31 |
32 | # internal functions:
33 |
34 | def _post(server, payload):
35 | data = urlencode(payload).encode('utf8')
36 | try:
37 | content = urlopen(server, data).read()
38 | return content
39 | except IOError:
40 | return None
41 |
--------------------------------------------------------------------------------
/LanguageList.py:
--------------------------------------------------------------------------------
1 | # languages is an array of tuples in the format
2 | # (name, code)
3 |
4 | languages = [
5 | ("Autodetect Language", "auto"),
6 | ("Asturian", "ast"),
7 | ("Belarusian", "be"),
8 | ("Breton", "br"),
9 | ("Catalan", "ca"),
10 | ("ValencianCatalan", "ca"),
11 | ("Danish", "da"),
12 | ("German (Austria)", "de-AT"),
13 | ("German", "de"),
14 | ("German (Germany)", "de-DE"),
15 | ("German (Swiss)", "de-CH"),
16 | ("Greek", "el"),
17 | ("English (American)", "en-US"),
18 | ("English (Australian)", "en-AU"),
19 | ("English (GB)", "en-GB"),
20 | ("English (Canadian)", "en-CA"),
21 | ("English", "en"),
22 | ("English (New Zealand)", "en-NZ"),
23 | ("English (South African)", "en-ZA"),
24 | ("Esperanto", "eo"),
25 | ("Spanish", "es"),
26 | ("Persian", "fa"),
27 | ("French", "fr"),
28 | ("Galician", "gl"),
29 | ("Icelandic", "is"),
30 | ("Italian", "it"),
31 | ("Japanese", "ja"),
32 | ("Khmer", "km"),
33 | ("Lithuanian", "lt"),
34 | ("Malayalam", "ml"),
35 | ("Dutch", "nl"),
36 | ("Polish", "pl"),
37 | ("Portuguese (Brazil)", "pt-BR"),
38 | ("Portuguese (Portugal)", "pt-PT"),
39 | ("Portuguese", "pt"),
40 | ("Romanian", "ro"),
41 | ("Russian", "ru"),
42 | ("Slovak", "sk"),
43 | ("Slovenian", "sl"),
44 | ("Swedish", "sv"),
45 | ("Tamil", "ta"),
46 | ("Tagalog", "tl"),
47 | ("Ukrainian", "uk"),
48 | ("Chinese", "zh"),
49 | ];
50 |
51 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "mnemonic": "n",
4 | "caption": "Preferences",
5 | "id": "preferences",
6 | "children": [
7 | {
8 | "mnemonic": "P",
9 | "caption": "Package Settings",
10 | "id": "package-settings",
11 | "children": [
12 | {
13 | "caption": "LanguageTool",
14 | "children": [
15 | {
16 | "caption": "Settings – Default",
17 | "args": {
18 | "file": "${packages}/LanguageTool/LanguageTool.sublime-settings"
19 | },
20 | "command": "open_file"
21 | },
22 | {
23 | "caption": "Settings – User",
24 | "args": {
25 | "file": "${packages}/User/LanguageTool.sublime-settings"
26 | },
27 | "command": "open_file"
28 | },
29 | {
30 | "caption": "-"
31 | }
32 | ]
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | ]
--------------------------------------------------------------------------------
/LanguageTool.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "LanguageTool: Check Text",
4 | "command": "language_tool"
5 | },
6 | {
7 | "caption": "LanguageTool: Check Text (Local Server)",
8 | "command": "language_tool",
9 | "args": {"force_server": "local"}
10 | },
11 | {
12 | "caption": "LanguageTool: Check Text (Remote Server)",
13 | "command": "language_tool",
14 | "args": {"force_server": "remote"}
15 | },
16 | {
17 | "caption": "LanguageTool: Next Problem",
18 | "command": "goto_next_language_problem",
19 | "args": {"jump_forward": true}
20 | },
21 | {
22 | "caption": "LanguageTool: Previous Problem",
23 | "command": "goto_next_language_problem",
24 | "args": {"jump_forward": false}
25 | },
26 | {
27 | "caption": "LanguageTool: Apply Suggested Correction(s)",
28 | "command": "mark_language_problem_solved",
29 | "args": {"apply_fix": true}
30 | },
31 | {
32 | "caption": "LanguageTool: Ignore Problem",
33 | "command": "mark_language_problem_solved",
34 | "args": {"apply_fix": false}
35 | },
36 | {
37 | "caption": "LanguageTool: Clear Problems",
38 | "command": "clear_language_problems"
39 | },
40 | {
41 | "caption": "LanguageTool: Change Language",
42 | "command": "change_language_tool_language"
43 | },
44 | {
45 | "caption": "LanguageTool: Deactivate Rule",
46 | "command": "deactivate_rule"
47 | },
48 | {
49 | "caption": "LanguageTool: Activate Rule",
50 | "command": "activate_rule"
51 | },
52 | {
53 | "caption": "LanguageTool: Start Local Server",
54 | "command": "start_language_tool_server"
55 | }
56 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### LanguageTool for Sublime Text 2/3
2 |
3 | #### Overview
4 |
5 | This is a simple adapter to integrate
6 | [LanguageTool](https://languagetool.org/) (an open source proof-reading
7 | program) into Sublime Text 2/3.
8 |
9 | From :
10 |
11 | > LanguageTool is an Open Source proofreading program for English, French,
12 | > German, Polish, and more than 20 other languages. It finds many errors that
13 | > a simple spell checker cannot detect and several grammar problems.
14 |
15 | 
16 |
17 | #### Installation
18 |
19 | If you're using Package Control then open up the command palette
20 | (ctrl+shift+p), type `install`, press Enter then type
21 | `languagetool` and press Enter again.
22 |
23 | To get the latest updates before they get released, install via `Package
24 | Control: Add Repository`. This will update your plugin with new commits as
25 | they are being pushed to the repo.
26 |
27 | #### Usage
28 |
29 | Open the file you want to proof-read then:
30 |
31 | 1. Run a language check (ctrl+shift+c). Any problems identified by LanguageTool will be highlighted.
32 | 2. Move between the problems using alt+down (next) and alt+up (previous).
33 | 3. A panel at the bottom will display a brief description of the highlighted problem and suggest corrections if available.
34 | 4. Begin typing to correct the selected problem or press alt+shift+f to apply the suggested correction.
35 | 5. To ignore a problem, press alt+d.
36 | 6. Auto-correcting a problem or ignoring it will move focus to the next problem.
37 |
38 | All commands and their keyboard shortcuts are in the command palette with the
39 | prefix `LanguageTool:`.
40 |
41 | #### Configuration
42 |
43 | The settings file for the plugin can be opened from the `Preferences` menu
44 | (`Preferences` → `Package Settings` → `LanguageTool` →
45 | `Settings - User`). Default settings are in the corresponding submenu item
46 | `Settings - Default`. Note that default settings are provided for reference
47 | and should not be edited as they may be overwritten when the plugin is updated
48 | or reinstalled. Instead, copy and modify any settings you wish to override to
49 | `Settings - User`.
50 |
51 | #### Local vs. Remote Checking
52 |
53 | The adapter supports local and remote LanguageTool servers. Remote checking is
54 | the default and works by submitting text over https to an api endpoint on
55 | (this can be changed in plugin settings). This public
56 | service is subject to usage constraints including:
57 |
58 | 1. 20 requests per IP per minute (this is supposed to be a peak value — don’t
59 | constantly send this many requests or LanguageTool would have to block you)
60 | 2. 75KB text per IP per minute
61 | 3. 20KB text per request
62 | 4. Only up to 30 misspelled words will have suggestions.
63 | 5. No guarantees about performance or availability.
64 | The limits may change at any time.
65 |
66 | (See for full details.)
67 |
68 | Instead of using the public (remote) LanguageTool service, text can be checked
69 | using a local LanguageTool Installation. A local LanguageTool server can be
70 | started by the plugin itself using the command `LanguageTool: Start Local
71 | Server` (this requires the settings entry `languagetool_jar` to point to the
72 | local languagetool JAR file), or from the command line following the
73 | instructions in .
74 |
75 | The settings file contains remote and local server URL entries. A third option
76 | `default_server` indicates which of these is used when the command
77 | `LanguageTool: Check Text` is ran. As an added convenience, two more commands:
78 |
79 | * `LanguageTool: Check Text (Local Server)`
80 | * `LanguageTool: Check Text (Remote Server)`
81 |
82 | are provided, which can be used to check text using the local/remote servers
83 | regardless of `default_server`. This can be used for one-off checks when it's
84 | desirable to use a particular server with certain pieces of text.
85 |
86 | #### License
87 |
88 | This plugin is freely available under
89 | [GPLv2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) or later.
90 |
91 | #### Contributing
92 |
93 | Feel free to fork and improve. All contributions are welcome.
94 |
--------------------------------------------------------------------------------
/LanguageTool.py:
--------------------------------------------------------------------------------
1 | """
2 | LanguageTool.py
3 |
4 | This is a simple Sublime Text plugin for checking grammar. It passes buffer
5 | content to LanguageTool (via http) and highlights reported problems.
6 | """
7 |
8 | import sublime
9 | import sublime_plugin
10 | import subprocess
11 | import os.path
12 | import fnmatch
13 | import itertools
14 |
15 |
16 | def _is_ST2():
17 | return (int(sublime.version()) < 3000)
18 |
19 |
20 | if _is_ST2():
21 | import LTServer
22 | import LanguageList
23 | else:
24 | from . import LTServer
25 | from . import LanguageList
26 |
27 |
28 | def move_caret(view, i, j):
29 | """Select character range [i, j] in view."""
30 | target = view.text_point(0, i)
31 | view.sel().clear()
32 | view.sel().add(sublime.Region(target, target + j - i))
33 |
34 |
35 | def set_status_bar(message):
36 | """Change status bar message."""
37 | sublime.status_message(message)
38 |
39 |
40 | def select_problem(view, problem):
41 | reg = view.get_regions(problem['regionKey'])[0]
42 | move_caret(view, reg.a, reg.b)
43 | view.show_at_center(reg)
44 | show_problem(problem)
45 |
46 |
47 | def is_problem_solved(view, problem):
48 | """Return True iff a language problem has been resolved.
49 |
50 | A problem is considered resolved if either:
51 |
52 | 1. its region has zero length, or
53 | 2. its contents have been changed.
54 | """
55 | rl = view.get_regions(problem['regionKey'])
56 | assert len(rl) > 0, 'tried to find non-existing region'
57 | region = rl[0]
58 | return region.empty() or (view.substr(region) != problem['orgContent'])
59 |
60 |
61 | def show_problem(p):
62 | """Show problem description and suggestions."""
63 |
64 | def show_problem_panel(p):
65 | msg = p['message']
66 | if p['replacements']:
67 | msg += '\n\nSuggestion(s): ' + ', '.join(p['replacements'])
68 | if p['urls']:
69 | msg += '\n\nMore Info: ' + '\n'.join(p['urls'])
70 | show_panel_text(msg)
71 |
72 | def show_problem_status_bar(p):
73 | if p['replacements']:
74 | msg = u"{0} ({1})".format(p['message'], p['replacements'])
75 | else:
76 | msg = p['message']
77 | sublime.status_message(msg)
78 |
79 | # call appropriate show_problem function
80 |
81 | use_panel = get_settings().get('display_mode') == 'panel'
82 | show_fun = show_problem_panel if use_panel else show_problem_status_bar
83 | show_fun(p)
84 |
85 |
86 | def show_panel_text(text):
87 | window = sublime.active_window()
88 | if _is_ST2():
89 | pt = window.get_output_panel("languagetool")
90 | pt.set_read_only(False)
91 | edit = pt.begin_edit()
92 | pt.insert(edit, pt.size(), text)
93 | window.run_command("show_panel", {"panel": "output.languagetool"})
94 | else:
95 | window.run_command('set_language_tool_panel_text', {'str': text})
96 |
97 |
98 | class setLanguageToolPanelTextCommand(sublime_plugin.TextCommand):
99 | def run(self, edit, str):
100 | window = sublime.active_window()
101 | pt = window.get_output_panel("languagetool")
102 | pt.settings().set("wrap_width", 0)
103 | pt.settings().set("word_wrap", True)
104 | pt.set_read_only(False)
105 | pt.run_command('insert', {'characters': str})
106 | window.run_command("show_panel", {"panel": "output.languagetool"})
107 |
108 |
109 | class gotoNextLanguageProblemCommand(sublime_plugin.TextCommand):
110 | def run(self, edit, jump_forward=True):
111 | v = self.view
112 | problems = v.__dict__.get("problems", [])
113 | if len(problems) > 0:
114 | sel = v.sel()[0]
115 | if jump_forward:
116 | for p in problems:
117 | r = v.get_regions(p['regionKey'])[0]
118 | if (not is_problem_solved(v, p)) and (sel.begin() < r.a):
119 | select_problem(v, p)
120 | return
121 | else:
122 | for p in reversed(problems):
123 | r = v.get_regions(p['regionKey'])[0]
124 | if (not is_problem_solved(v, p)) and (r.a < sel.begin()):
125 | select_problem(v, p)
126 | return
127 | set_status_bar("no further language problems to fix")
128 | sublime.active_window().run_command("hide_panel", {
129 | "panel": "output.languagetool"
130 | })
131 |
132 |
133 | class clearLanguageProblemsCommand(sublime_plugin.TextCommand):
134 | def run(self, edit):
135 | v = self.view
136 | problems = v.__dict__.get("problems", [])
137 | for p in problems:
138 | v.erase_regions(p['regionKey'])
139 | problems = []
140 | recompute_highlights(v)
141 | caretPos = self.view.sel()[0].end()
142 | v.sel().clear()
143 | sublime.active_window().run_command("hide_panel", {
144 | "panel": "output.languagetool"
145 | })
146 | move_caret(v, caretPos, caretPos)
147 |
148 |
149 | class markLanguageProblemSolvedCommand(sublime_plugin.TextCommand):
150 | def run(self, edit, apply_fix):
151 |
152 | v = self.view
153 |
154 | problems = v.__dict__.get("problems", [])
155 | selected_region = v.sel()[0]
156 |
157 | # Find problem corresponding to selection
158 | for problem in problems:
159 | problem_region = v.get_regions(problem['regionKey'])[0]
160 | if problem_region == selected_region:
161 | break
162 | else:
163 | set_status_bar('no language problem selected')
164 | return
165 |
166 | next_caret_pos = problem_region.a
167 | replacements = problem['replacements']
168 |
169 | if apply_fix and replacements:
170 | # fix selected problem:
171 | correct_problem(self.view, edit, problem, replacements)
172 |
173 | else:
174 | # ignore problem:
175 | equal_problems = get_equal_problems(problems, problem)
176 | for p2 in equal_problems:
177 | ignore_problem(p2, v, edit)
178 | # After ignoring problem:
179 | move_caret(v, next_caret_pos, next_caret_pos) # advance caret
180 | v.run_command("goto_next_language_problem")
181 |
182 | def choose_suggestion(view, p, replacements, choice):
183 | """Handle suggestion list selection."""
184 | problems = view.__dict__.get("problems", [])
185 | if choice != -1:
186 | r = view.get_regions(p['regionKey'])[0]
187 | view.run_command('insert', {'characters': replacements[choice]})
188 | c = r.a + len(replacements[choice])
189 | move_caret(view, c, c) # move caret to end of region
190 | view.run_command("goto_next_language_problem")
191 | else:
192 | select_problem(view, p)
193 |
194 |
195 | def get_equal_problems(problems, x):
196 | """Find problems with same category and content as a given problem.
197 |
198 | Args:
199 | problems (list): list of problems to compare.
200 | x (dict): problem object to compare with.
201 |
202 | Returns:
203 | list: list of problems equal to x.
204 |
205 | """
206 |
207 | def is_equal(prob1, prob2):
208 | same_category = prob1['category'] == prob2['category']
209 | same_content = prob1['orgContent'] == prob2['orgContent']
210 | return same_category and same_content
211 |
212 | return [problem for problem in problems if is_equal(problem, x)]
213 |
214 |
215 | def get_settings():
216 | return sublime.load_settings('LanguageTool.sublime-settings')
217 |
218 |
219 | class startLanguageToolServerCommand(sublime_plugin.TextCommand):
220 | """Launch local LanguageTool Server."""
221 |
222 | def run(self, edit):
223 |
224 | jar_path = get_settings().get('languagetool_jar')
225 |
226 | if not jar_path:
227 | show_panel_text("Setting languagetool_jar is undefined")
228 | return
229 |
230 | if not os.path.isfile(jar_path):
231 | show_panel_text(
232 | 'Error, could not find LanguageTool\'s JAR file (%s)'
233 | '\n\n'
234 | 'Please install LT in this directory'
235 | ' or modify the `languagetool_jar` setting.' % jar_path)
236 | return
237 |
238 | sublime.status_message('Starting local LanguageTool server ...')
239 |
240 | cmd = ['java', '-cp', jar_path, 'org.languagetool.server.HTTPServer', '--port', '8081']
241 |
242 | if sublime.platform() == "windows":
243 | p = subprocess.Popen(
244 | cmd,
245 | stdin=subprocess.PIPE,
246 | stdout=subprocess.PIPE,
247 | stderr=subprocess.PIPE,
248 | shell=True,
249 | creationflags=subprocess.SW_HIDE)
250 | else:
251 | p = subprocess.Popen(
252 | cmd,
253 | stdin=subprocess.PIPE,
254 | stdout=subprocess.PIPE,
255 | stderr=subprocess.PIPE)
256 |
257 |
258 | class changeLanguageToolLanguageCommand(sublime_plugin.TextCommand):
259 | def run(self, edit):
260 | self.view.languages = LanguageList.languages
261 | languageNames = [x[0] for x in self.view.languages]
262 | handler = lambda ind: handle_language_selection(ind, self.view)
263 | self.view.window().show_quick_panel(languageNames, handler)
264 |
265 |
266 | def handle_language_selection(ind, view):
267 | key = 'language_tool_language'
268 | if ind == 0:
269 | view.settings().erase(key)
270 | else:
271 | selected_language = view.languages[ind][1]
272 | view.settings().set(key, selected_language)
273 |
274 |
275 | def correct_problem(view, edit, problem, replacements):
276 |
277 | def clear_and_advance():
278 | clear_region(view, problem['regionKey'])
279 | move_caret(view, next_caret_pos, next_caret_pos) # advance caret
280 | view.run_command("goto_next_language_problem")
281 |
282 | if len(replacements) > 1:
283 | def callback_fun(i):
284 | choose_suggestion(view, problem, replacements, i)
285 | clear_and_advance()
286 | view.window().show_quick_panel(replacements, callback_fun)
287 |
288 | else:
289 | region = view.get_regions(problem['regionKey'])[0]
290 | view.replace(edit, region, replacements[0])
291 | next_caret_pos = region.a + len(replacements[0])
292 | clear_and_advance()
293 |
294 |
295 | def clear_region(view, region_key):
296 | r = view.get_regions(region_key)[0]
297 | dummyRg = sublime.Region(r.a, r.a)
298 | hscope = get_settings().get("highlight-scope", "comment")
299 | view.add_regions(region_key, [dummyRg], hscope, "", sublime.DRAW_OUTLINED)
300 |
301 |
302 | def ignore_problem(p, v, edit):
303 | clear_region(v, p['regionKey'])
304 | v.insert(edit, v.size(), "") # dummy edit to enable undoing ignore
305 |
306 |
307 | def load_ignored_rules():
308 | ignored_rules_file = 'LanguageToolUser.sublime-settings'
309 | settings = sublime.load_settings(ignored_rules_file)
310 | return settings.get('ignored', [])
311 |
312 |
313 | def save_ignored_rules(ignored):
314 | ignored_rules_file = 'LanguageToolUser.sublime-settings'
315 | settings = sublime.load_settings(ignored_rules_file)
316 | settings.set('ignored', ignored)
317 | sublime.save_settings(ignored_rules_file)
318 |
319 |
320 | def get_server_url(settings, force_server):
321 | """Return LT server url based on settings.
322 |
323 | The returned url is for either the local or remote servers, defined by the
324 | settings entries:
325 |
326 | - language_server_local
327 | - language_server_remote
328 |
329 | The choice between the above is made based on the settings value
330 | 'default_server'. If not None, `force_server` will override this setting.
331 |
332 | """
333 | server_setting = force_server or settings.get('default_server')
334 | setting_name = 'languagetool_server_%s' % server_setting
335 | server = settings.get(setting_name)
336 | return server
337 |
338 |
339 | class LanguageToolCommand(sublime_plugin.TextCommand):
340 | def run(self, edit, force_server=None):
341 |
342 | settings = get_settings()
343 | server_url = get_server_url(settings, force_server)
344 | ignored_scopes = settings.get('ignored-scopes')
345 | highlight_scope = settings.get('highlight-scope')
346 |
347 | selection = self.view.sel()[0] # first selection (ignore rest)
348 | everything = sublime.Region(0, self.view.size())
349 | check_region = everything if selection.empty() else selection
350 | check_text = self.view.substr(check_region)
351 |
352 | self.view.run_command("clear_language_problems")
353 |
354 | language = self.view.settings().get('language_tool_language', 'auto')
355 | ignored_ids = [rule['id'] for rule in load_ignored_rules()]
356 |
357 | matches = LTServer.getResponse(server_url, check_text, language,
358 | ignored_ids)
359 |
360 | if matches == None:
361 | set_status_bar('could not parse server response (may be due to'
362 | ' quota if using https://languagetool.org)')
363 | return
364 |
365 | def get_region(problem):
366 | """Return a Region object corresponding to problem text."""
367 | length = problem['length']
368 | offset = problem['offset']
369 | return sublime.Region(offset, offset + length)
370 |
371 | def inside(problem):
372 | """Return True iff problem text is inside check_region."""
373 | region = get_region(problem)
374 | return check_region.contains(region)
375 |
376 | def is_ignored(problem):
377 | """Return True iff any problem scope is ignored."""
378 | scope_string = self.view.scope_name(problem['offset'])
379 | scopes = scope_string.split()
380 | return cross_match(scopes, ignored_scopes, fnmatch.fnmatch)
381 |
382 | def add_highlight_region(region_key, problem):
383 | region = get_region(problem)
384 | problem['orgContent'] = self.view.substr(region)
385 | problem['regionKey'] = region_key
386 | self.view.add_regions(region_key, [region], highlight_scope, "",
387 | sublime.DRAW_OUTLINED)
388 |
389 |
390 | shifter = lambda problem: shift_offset(problem, check_region.a)
391 |
392 | get_problem = compose(shifter, parse_match)
393 |
394 | problems = [problem for problem in map(get_problem, matches)
395 | if inside(problem) and not is_ignored(problem)]
396 |
397 | for index, problem in enumerate(problems):
398 | add_highlight_region(str(index), problem)
399 |
400 | if problems:
401 | select_problem(self.view, problems[0])
402 | else:
403 | set_status_bar("no language problems were found :-)")
404 |
405 | self.view.problems = problems
406 |
407 |
408 | def compose(f1, f2):
409 | """Compose two functions."""
410 | def inner(*args, **kwargs):
411 | return f1(f2(*args, **kwargs))
412 | return inner
413 |
414 |
415 | def cross_match(list1, list2, predicate):
416 | """Cross match items from two lists using a predicate.
417 |
418 | Args:
419 | list1 (list): list 1.
420 | list2 (list): list 2.
421 |
422 | Returns:
423 | True iff predicate(x, y) is True for any x in list1 and y in list2,
424 | False otherwise.
425 |
426 | """
427 | return any(predicate(x, y) for x, y in itertools.product(list1, list2))
428 |
429 |
430 | def shift_offset(problem, shift):
431 | """Shift problem offset by `shift`."""
432 |
433 | problem['offset'] += shift
434 | return problem
435 |
436 |
437 | def parse_match(match):
438 | """Parse a match object.
439 |
440 | Args:
441 | match (dict): match object returned by LanguageTool Server.
442 |
443 | Returns:
444 | dict: problem object.
445 |
446 | """
447 |
448 | problem = {
449 | 'category': match['rule']['category']['name'],
450 | 'message': match['message'],
451 | 'replacements': [r['value'] for r in match['replacements']],
452 | 'rule': match['rule']['id'],
453 | 'urls': [w['value'] for w in match['rule'].get('urls', [])],
454 | 'offset': match['offset'],
455 | 'length': match['length']
456 | }
457 |
458 | return problem
459 |
460 |
461 | class DeactivateRuleCommand(sublime_plugin.TextCommand):
462 | def run(self, edit):
463 | ignored = load_ignored_rules()
464 | v = self.view
465 | problems = v.__dict__.get("problems", [])
466 | sel = v.sel()[0]
467 | selected = [
468 | p for p in problems
469 | if sel.contains(v.get_regions(p['regionKey'])[0])
470 | ]
471 | if not selected:
472 | set_status_bar('select a problem to deactivate its rule')
473 | elif len(selected) == 1:
474 | rule = {
475 | "id": selected[0]['rule'],
476 | "description": selected[0]['message']
477 | }
478 | ignored.append(rule)
479 | ignoredProblems = [p for p in problems if p['rule'] == rule['id']]
480 | for p in ignoredProblems:
481 | ignore_problem(p, v, edit)
482 | problems = [p for p in problems if p['rule'] != rule['id']]
483 | v.run_command("goto_next_language_problem")
484 | save_ignored_rules(ignored)
485 | set_status_bar('deactivated rule %s' % rule)
486 | else:
487 | set_status_bar('there are multiple selected problems;'
488 | ' select only one to deactivate')
489 |
490 |
491 | class ActivateRuleCommand(sublime_plugin.TextCommand):
492 | def run(self, edit):
493 | ignored = load_ignored_rules()
494 | if ignored:
495 | activate_callback_wrapper = lambda i: self.activate_callback(i)
496 | ruleList = [[rule['id'], rule['description']] for rule in ignored]
497 | self.view.window().show_quick_panel(ruleList,
498 | activate_callback_wrapper)
499 | else:
500 | set_status_bar('there are no ignored rules')
501 |
502 | def activate_callback(self, i):
503 | ignored = load_ignored_rules()
504 | if i != -1:
505 | activate_rule = ignored[i]
506 | ignored.remove(activate_rule)
507 | save_ignored_rules(ignored)
508 | set_status_bar('activated rule %s' % activate_rule['id'])
509 |
510 |
511 | class LanguageToolListener(sublime_plugin.EventListener):
512 | def on_modified(self, view):
513 | # buffer text was changed, recompute region highlights
514 | recompute_highlights(view)
515 |
516 |
517 | def recompute_highlights(view):
518 | problems = view.__dict__.get("problems", {})
519 | hscope = get_settings().get("highlight-scope", "comment")
520 | for p in problems:
521 | rL = view.get_regions(p['regionKey'])
522 | if rL:
523 | regionScope = "" if is_problem_solved(view, p) else hscope
524 | view.add_regions(p['regionKey'], rL, regionScope, "",
525 | sublime.DRAW_OUTLINED)
526 |
--------------------------------------------------------------------------------