forked from dbp/sublime-rust
/
SyntaxCheckPlugin.py
122 lines (106 loc) · 4.69 KB
/
SyntaxCheckPlugin.py
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
import sublime, sublime_plugin
import subprocess
import os
import html
import json
class rustPluginSyntaxCheckEvent(sublime_plugin.EventListener):
def on_post_save_async(self, view):
# Are we in rust scope and is it switched on?
# We use phantoms which were added in 3118
if int(sublime.version()) < 3118:
return
settings = view.settings()
enabled = settings.get('rust_syntax_checking')
if enabled and "source.rust" in view.scope_name(0):
file_name = os.path.abspath(view.file_name())
file_dir = os.path.dirname(file_name)
os.chdir(file_dir)
# shell=True is needed to stop the window popping up, although it looks like this is needed:
# http://stackoverflow.com/questions/3390762/how-do-i-eliminate-windows-consoles-from-spawned-processes-in-python-2-7
# We only care about stderr
cargo_command = self.cargo_rustc_command(file_name, settings)
cargoRun = subprocess.Popen(cargo_command,
shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
universal_newlines = True
)
output = cargoRun.communicate()
if output[1].startswith('error'):
print(output[1])
return
for view in view.window().views():
view.erase_phantoms('buildErrorLine')
for line in output[1].split('\n'):
if line == '' or line[0] != '{':
continue
info = json.loads(line)
# Can't show without spans
if len(info['spans']) == 0:
continue
self.add_error_phantom(view.window(), info, settings)
# If the user has switched OFF the plugin, remove any phantom lines
elif not enabled:
for view in view.window().views():
view.erase_phantoms('buildErrorLine')
def cargo_rustc_command(self, file_name, settings):
command = 'cargo rustc {target} -- -Zno-trans -Zunstable-options --error-format=json'
target = ''
for project in settings.get('projects', {}).values():
src_root = os.path.join(project.get('root', ''), 'src')
if not file_name.startswith(src_root):
continue
targets = project.get('targets', {})
for tfile, tcmd in targets.items():
if file_name == os.path.join(src_root, tfile):
target = tcmd
break
else:
target = targets.get('_default', '')
return command.replace('{target}', target)
def add_error_phantom(self, window, info, settings):
msg = info['message']
error_colour = settings.get('rust_syntax_error_color')
warning_colour = settings.get('rust_syntax_warning_color')
if error_colour is None:
base_color = "var(--redish)" # Error color
else:
base_color = error_colour
if info['level'] != "error":
# Warning color
if warning_colour is None:
base_color = "var(--yellowish)"
else:
base_color = warning_colour
for span in info['spans']:
view = window.find_open_file(os.path.realpath(span['file_name']))
if not view:
continue
color = base_color
char = "^"
if not span['is_primary']:
# Non-primary spans are normally additional
# information to help understand the error.
color = "var(--foreground)"
char = "-"
# Sublime text is 0 based whilst the line/column info from
# rust is 1 based.
area = sublime.Region(
view.text_point(span['line_start'] - 1, span['column_start'] - 1),
view.text_point(span['line_end'] - 1, span['column_end'] - 1)
)
underline = char * (span['column_end'] - span['column_start'])
label = span['label']
if not label:
label = ''
view.add_phantom(
'buildErrorLine', area,
"<span style=\"color:{}\">{} {}</span>"
.format(color, underline, html.escape(label, quote=False)),
sublime.LAYOUT_BELOW
)
if span['is_primary']:
view.add_phantom(
'buildErrorLine', area,
"<span style=\"color:{}\">{}</span>"
.format(color, html.escape(msg, quote=False)),
sublime.LAYOUT_BELOW
)