├── Comments.tmPreferences
├── Default.sublime-commands
├── README.md
├── example.v
├── example_test.v
├── img
├── assert-failures.png
├── command-palette.png
├── inline-errors.png
├── install-package.png
├── install-v.png
└── symbols.png
├── v.sublime-syntax
├── v.tmPreferences
└── vlang-sublime.py
/Comments.tmPreferences:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | scope
5 | source.v
6 | settings
7 |
8 | shellVariables
9 |
10 |
11 | name
12 | TM_COMMENT_START
13 | value
14 | //
15 |
16 |
17 | name
18 | TM_COMMENT_START_2
19 | value
20 | /*
21 |
22 |
23 | name
24 | TM_COMMENT_END_2
25 | value
26 | */
27 |
28 |
29 | name
30 | TM_COMMENT_DISABLE_INDENT_2
31 | value
32 | yes
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "V: Build",
4 | "command": "v",
5 | "args": {
6 | "cmd": "${file_path}",
7 | }
8 | },
9 | {
10 | "caption": "V: Build File",
11 | "command": "v",
12 | "args": {
13 | "cmd": "${file}",
14 | }
15 | },
16 | {
17 | "caption": "V: Format File",
18 | "command": "v",
19 | "args": {
20 | "cmd": "fmt -w ${file}",
21 | "output": false,
22 | }
23 | },
24 | {
25 | "caption": "V: Format Module",
26 | "command": "v",
27 | "args": {
28 | "cmd": "fmt -w ${file_path}",
29 | "output": false,
30 | }
31 | },
32 | {
33 | "caption": "V: Format Project",
34 | "command": "v",
35 | "args": {
36 | "cmd": "fmt -w ${folder}",
37 | "output": false,
38 | }
39 | },
40 | {
41 | "caption": "V: Run File",
42 | "command": "v",
43 | "args": {
44 | "cmd": "run ${file}",
45 | }
46 | },
47 | {
48 | "caption": "V: Run",
49 | "command": "v",
50 | "args": {
51 | "cmd": "run ${file_path}",
52 | }
53 | },
54 | {
55 | "caption": "V: Test",
56 | "command": "v",
57 | "args": {
58 | "cmd": "test ${file_path}",
59 | }
60 | },
61 | {
62 | "caption": "V: Test File",
63 | "command": "v",
64 | "args": {
65 | "cmd": "test ${file}",
66 | }
67 | },
68 | {
69 | "caption": "V: Update",
70 | "command": "v",
71 | "args": {
72 | "cmd": "up",
73 | }
74 | },
75 | ]
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sublime Text Support for the V Programming Language
2 |
3 | This bundle provides [V](https://vlang.io/) syntax highlighting for Sublime
4 | Text.
5 |
6 | ## Installation
7 |
8 | You can install the [V](https://packagecontrol.io/packages/V) package with
9 | [Package Control](https://packagecontrol.io):
10 |
11 | 
12 |
13 | 
14 |
15 | For code completion you can
16 | [install vlang/vls for Sublime Text 3 or 4](https://github.com/vlang/vls#sublime-text-3-and-4).
17 |
18 | ## Features
19 |
20 | Commands for building, running, testing, formatting and updating V itself:
21 |
22 | 
23 |
24 | Inline errors and warnings:
25 |
26 | 
27 |
28 | Quickly goto to any symbol:
29 |
30 | 
31 |
32 | Show assertion failures inline:
33 |
34 | 
35 |
36 | ## Command Palette
37 |
38 | Any commands that generate output will show in a new window called "V". You may
39 | leave this tab open an any future runs will be appended.
40 |
41 | ### V: Build
42 |
43 | Build the current module (directory).
44 |
45 | ### V: Build File
46 |
47 | Build the current file.
48 |
49 | ### V: Format File
50 |
51 | Format the current file.
52 |
53 | ### V: Format Module
54 |
55 | Format the current module.
56 |
57 | ### V: Format Project
58 |
59 | Format the current project.
60 |
61 | ### V: Run
62 |
63 | Execute `v run` on the current module (directory).
64 |
65 | ### V: Run Test
66 |
67 | Execute `v run` only on the current file.
68 |
69 | ### V: Test
70 |
71 | Execute `v test` only on the current module (directory).
72 |
73 | ### V: Test File
74 |
75 | Execute `v test` only on the current file.
76 |
77 | ### V: Update
78 |
79 | Update V to the latest version (`v up`).
80 |
81 | ## Custom Commands
82 |
83 | ```json
84 | {
85 | "caption": "Build Prod",
86 | "command": "v",
87 | "args": {
88 | "cmd": "-prod ${module}", // required (string)
89 | "output": true, // optional (boolean)
90 | }
91 | },
92 | ```
93 |
94 | - `cmd` is required and in the example above will construct and execute
95 | `v -prod "some/folder/path"`.
96 | - `output` is optional (defaults to `true`). If `false`, the output is not shown
97 | in the "V" window. This is useful for commands you intend to be silent (such as
98 | `v fmt`).
99 |
100 | See
101 | [Sublime Variables](https://www.sublimetext.com/docs/build_systems.html#variables)
102 | for full list, but some common ones are:
103 |
104 | - `${file}` for the current file path.
105 | - `${file_path}` for the directory the current file exists in (the module).
106 | - `${folder}` for the project directory.
107 |
108 | ## Contributing
109 |
110 | Note: Make sure you uninstall the package if it's already installed with
111 | "Package Control > Remove Package..."
112 |
113 | Now clone the package locally:
114 |
115 | ```sh
116 | cd ~/Library/Application\ Support/Sublime\ Text/Packages
117 | git clone https://github.com/elliotchance/vlang-sublime.git
118 | ```
119 |
--------------------------------------------------------------------------------
/example.v:
--------------------------------------------------------------------------------
1 | // This file is included as a way to test commands with a V file.
2 |
3 | module main
4 |
5 | import os
6 |
7 | #flag -lsqlite3
8 | #include "sqlite3.h"
9 |
10 | [unsafe]
11 | struct Foo {
12 | bar int
13 | baz string
14 | }
15 |
16 | pub interface Writer {
17 | write() ?f64
18 | }
19 |
20 | fn (foo Foo) write() ?f64 {
21 | return -1.23 * 0xAF
22 | }
23 |
24 | enum Color {
25 | red
26 | green
27 | blue
28 | }
29 |
30 | fn main() {
31 | mut a := 4 + 5
32 | b := 6
33 | println(a)
34 |
35 | foo := Writer(Foo{})
36 | if foo is Writer {
37 | println(foo)
38 | }
39 |
40 | $if windows {
41 | bar := Foo{}
42 | println(bar as Foo)
43 | println(@LINE)
44 | }
45 |
46 | mut color := Color.red
47 | color = .green
48 | println(color) // "green"
49 | match color {
50 | .red { println('the color was red') }
51 | .green { println('the color was green') }
52 | .blue { println('the color was blue') }
53 | }
54 | }
55 |
56 | fn add(a int, b int) int {
57 | return a + b
58 | }
59 |
--------------------------------------------------------------------------------
/example_test.v:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | fn test_something() {
4 | assert add(3, 5) == 7
5 | }
6 |
7 | fn test_interpolation() {
8 | one, two := 1, 2
9 | assert '1 + 2 = ${1 + 2}' == "1 + 2 = 4"
10 | assert "$one + $two = 3" == "1 + 2 = 3"
11 | }
12 |
13 | fn test_hi() {
14 | assert 1 == 1
15 | }
16 |
--------------------------------------------------------------------------------
/img/assert-failures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/assert-failures.png
--------------------------------------------------------------------------------
/img/command-palette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/command-palette.png
--------------------------------------------------------------------------------
/img/inline-errors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/inline-errors.png
--------------------------------------------------------------------------------
/img/install-package.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/install-package.png
--------------------------------------------------------------------------------
/img/install-v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/install-v.png
--------------------------------------------------------------------------------
/img/symbols.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotchance/vlang-sublime/6e457ca6e2506407c1cbcdcf2ac110b706b991c0/img/symbols.png
--------------------------------------------------------------------------------
/v.sublime-syntax:
--------------------------------------------------------------------------------
1 | %YAML 1.2
2 | ---
3 | name: V
4 | file_extensions:
5 | - v
6 | scope: source.v
7 |
8 | variables:
9 | ident: \b[A-Za-z_][A-Za-z_0-9]*\b
10 |
11 | contexts:
12 | main:
13 | # Most simple entities.
14 | - include: enum-value
15 | - include: comment-single-line
16 | - include: comment-multi-line
17 | - include: string-double
18 | - include: string-single
19 | - include: string-character
20 | - include: numbers
21 | - include: operator
22 | - include: attributes
23 | - include: punctuation
24 |
25 | # More complex constructs.
26 | - include: as-is
27 | - include: variable-definition
28 | - include: method
29 | - include: fn
30 | - include: interface
31 | - include: struct
32 | - include: type
33 | - include: compile-time
34 | - include: enum
35 | - include: flag
36 | - include: import
37 | - include: include
38 |
39 | # keywords needs to be a last resort as some of the specific keywords above
40 | # should try to match first.
41 | - include: keyword
42 |
43 | punctuation:
44 | - match: \.
45 | scope: punctuation.delimiter.period.dot.v
46 | - match: \,
47 | scope: punctuation.delimiter.comma.v
48 | - match: ':'
49 | scope: punctuation.separator.key-value.colon.v
50 | - match: \;
51 | scope: punctuation.definition.other.semicolon.v
52 | - match: \?
53 | scope: punctuation.definition.other.questionmark.v
54 |
55 | flag:
56 | - match: ^\s*(#flag)\s+(.*?)$
57 | captures:
58 | 0: meta.flag.v
59 | 1: keyword.flag.v
60 | 2: string.quoted.single.v
61 |
62 | include:
63 | - match: ^\s*(#include)\s+(.*?)$
64 | captures:
65 | 0: meta.include.v
66 | 1: keyword.include.v
67 | 2: string.quoted.double.v
68 |
69 | import:
70 | - match: ^\s*(import)\s+(.*?)$
71 | captures:
72 | 0: meta.import.v
73 | 1: keyword
74 | 2: entity.name.import.v
75 |
76 | compile-time:
77 | - match: (\$if|\$else|$for|\$embed_file|\$tmpl|\$env)
78 | captures:
79 | 1: keyword.control.v
80 | - match: (@FN|@METHOD|@MOD|@STRUCT|@FILE|@LINE|@COLUMN|@VEXE|@VEXEROOT|@VHASH|@VMOD_FILE|@VMODROOT)
81 | captures:
82 | 1: variable.language.v
83 |
84 | attributes:
85 | - match: ^\s*(\[(deprecated|unsafe_fn|typedef|live|inline|flag)\])
86 | captures:
87 | 1: meta.definition.attribute.v
88 |
89 | as-is:
90 | - match: \b(as|is)\s*({{ident}})\b
91 | captures:
92 | 1: keyword
93 | 2: meta.type.v
94 |
95 | numbers:
96 | - match: (?:(?:[-]?)(?:[0-9e]*)(?:[.]){1}(?:[0-9]+))
97 | scope: constant.numeric.float.decimal.v
98 | - match: \b(?:0[xX])(?:[0-9a-fA-F]+)
99 | scope: constant.numeric.hex.v
100 | - match: \b(?:[-]?)(?:[0-9]+)
101 | scope: constant.numeric.integer.decimal.v
102 |
103 | comment-single-line:
104 | - match: //
105 | scope: punctuation.definition.comment.v
106 | push:
107 | - meta_scope: comment.line.v
108 | - match: $\n?
109 | pop: true
110 |
111 | comment-multi-line:
112 | - match: /\*
113 | scope: punctuation.definition.comment.begin.v
114 | push:
115 | - meta_scope: comment.block.v
116 | - match: \*/
117 | scope: punctuation.definition.comment.end.v
118 | pop: true
119 | - match: ^\s*(\*)(?!/)
120 | captures:
121 | 1: punctuation.definition.comment.v
122 |
123 | variable-definition:
124 | - match: '(mut)\s+({{ident}})\s*(:=)'
125 | captures:
126 | 1: keyword
127 | 2: variable.other.readwrite
128 | 3: operator
129 | - match: '({{ident}})\s*(:=)'
130 | captures:
131 | 1: variable.other.constant
132 | 2: operator
133 |
134 | struct:
135 | - match: \bstruct\b
136 | captures:
137 | 0: keyword
138 | push:
139 | - match: '{{ident}}'
140 | # TODO(elliotchance): I don't know why the more appropriate
141 | # \"entity.name.struct.v\" does not work here? Tested with ST4.
142 | scope: entity.name.type.struct.v
143 | pop: true
144 |
145 | interface:
146 | - match: \binterface\b
147 | captures:
148 | 0: keyword
149 | push:
150 | - match: '{{ident}}'
151 | # TODO(elliotchance): I don't know why the more appropriate
152 | # \"entity.name.interface.v\" does not work here? Tested with ST4.
153 | scope: entity.name.type.interface.v
154 | pop: true
155 |
156 | enum:
157 | - match: \benum\b
158 | captures:
159 | 0: keyword
160 | push:
161 | - match: '{{ident}}'
162 | scope: entity.name.enum.v
163 | pop: true
164 |
165 | enum-value:
166 | - match: (\.{{ident}})
167 | captures:
168 | 1: entity.name.constant.v
169 |
170 | type:
171 | - match: \b(type)\b\s*({{ident}})
172 | captures:
173 | 1: keyword
174 | 2: entity.name.type.v
175 |
176 | # We list all keywords even through some other rules may have specific
177 | # handling if there is a syntax error we always want to highlight the keywords
178 | # themselves.
179 | #
180 | # https://github.com/vlang/v/blob/master/doc/docs.md#appendix-i-keywords
181 | # https://github.com/vlang/v/blob/master/doc/docs.md#v-types
182 | keyword:
183 | - match: \b(any|bool|byte|f32|f64|i128|i16|i64|i8|int|isize|rune|string|u128|u16|u32|u64|usize|voidptr|map)\b
184 | scope: storage.type.v
185 | - match: \b(true|false|none)\b
186 | scope: constant.language
187 | - match: \b(for|goto|go|lock|return|rlock|assert|break|continue|defer)\b
188 | scope: keyword.control.v
189 | - match: \b(if|else|or|select|match)\b
190 | scope: keyword.control.conditional.v
191 | - match: \bimport\b
192 | scope: keyword.control.import.v
193 | - match: \b(in|as|is|module)\b
194 | scope: keyword.operator.word.v
195 | - match: \bstruct\b
196 | scope: keyword.declaration.struct.v
197 | - match: \binterface\b
198 | scope: keyword.declaration.interface.v
199 | - match: \bfn\b
200 | scope: keyword.declaration.function.v
201 | - match: \b(mut|pub|shared|const|unsafe|volatile|static|atomic)\b
202 | scope: storage.modifier.v
203 | - match: \benum\b
204 | scope: keyword.declaration.enum.v
205 | - match: \btype\b
206 | scope: keyword.declaration.type.v
207 | - match: \bunion\b
208 | scope: keyword.declaration.union.v
209 | - match: \b(asm|embed|sizeof|typeof|__offsetof)\b
210 | scope: keyword.other.v
211 |
212 | operator:
213 | - match: '([+*/%-:&|^]|<<|>>>?)='
214 | scope: keyword.operator.assignment
215 | - match: '[+*/%-]'
216 | scope: keyword.operator.arithmetic
217 | - match: '!|&&|\|\|'
218 | scope: keyword.operator.logical
219 | - match: '[~&|^]|<<|>>>?'
220 | scope: keyword.operator.bitwise
221 |
222 | method:
223 | - match: (fn)\s*\((mut\s+)?{{ident}}\s+(({{ident}})\)\s*({{ident}}))
224 | captures:
225 | 1: keyword
226 | 2: keyword
227 | 3: meta.indexed-unit.v
228 | 4: meta.type.v
229 | 5: entity.name.function.v
230 |
231 | fn:
232 | - match: \bfn\b
233 | captures:
234 | 0: keyword
235 | push:
236 | # Ignore inline functions (unnamed).
237 | - match: \(
238 | pop: true
239 |
240 | - match: '{{ident}}'
241 | scope: entity.name.function.v
242 | pop: true
243 |
244 | string-interpolation:
245 | - match: '\$\{'
246 | scope: punctuation.definition.string.interpolated.element.begin.v
247 | push:
248 | - meta_scope: source.v.embedded
249 | - match: '\}'
250 | scope: punctuation.definition.string.interpolated.element.end.v
251 | pop: true
252 | - include: 'main'
253 | - match: '\$[A-Za-z_0-9.()]*'
254 | scope: source.v.embedded
255 | - match: \\([0-7]{3}|[\$abfnrtv\\'"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})
256 | scope: constant.character.escape.v
257 | - match: \\[^0-7\$xuUabfnrtv\'"]
258 | scope: invalid.illegal.unknown-escape.v
259 |
260 | string-single:
261 | - match: "'"
262 | push:
263 | - meta_scope: string.interpolated.v
264 | - include: string-interpolation
265 | - match: "'"
266 | pop: true
267 |
268 | string-double:
269 | - match: '"'
270 | push:
271 | - meta_scope: string.interpolated.v
272 | - include: string-interpolation
273 | - match: '"'
274 | pop: true
275 |
276 | string-character:
277 | - match: '`'
278 | push:
279 | - meta_scope: string.interpolated.v
280 | - include: string-interpolation
281 | - match: '`'
282 | pop: true
283 |
--------------------------------------------------------------------------------
/v.tmPreferences:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | scope
6 | source.v meta.indexed-unit.v
7 | settings
8 |
9 | showInIndexedSymbolList
10 | 1
11 | symbolIndexTransformation
12 |
13 | s/\)\s*/\./;
14 |
15 | showInSymbolList
16 | 1
17 | symbolTransformation
18 |
19 | s/mut\s+|\)\s*/\./;
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/vlang-sublime.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import os
4 | import threading
5 | import subprocess
6 | import re
7 |
8 | run_output = None
9 | test_failures = {}
10 | assert_failures = {}
11 |
12 | def setup_output(window):
13 | global run_output
14 |
15 | # To prevent a new tab every time, let's reuse the output window. The
16 | # buffer_id will be zero if it was closed.
17 | if run_output is None or run_output.buffer_id() == 0:
18 | run_output = window.new_file()
19 | run_output.set_scratch(True)
20 | run_output.set_name('V')
21 |
22 | window.focus_view(run_output)
23 |
24 | return run_output
25 |
26 | def get_test_name(line):
27 | matches = re.search(r"fn\s+(test_[a-zA-Z_0-9]+)", line)
28 | if matches is None:
29 | return None
30 |
31 | return matches.group(1)
32 |
33 | class EventListener(sublime_plugin.EventListener):
34 | def on_activated(self, view):
35 | self.refresh(view)
36 | self.on_modified(view)
37 |
38 | def on_post_save(self, view):
39 | self.refresh(view)
40 |
41 | def on_modified(self, view):
42 | file_path = view.file_name()
43 | if file_path is None:
44 | return
45 |
46 | global test_failures, assert_failures
47 |
48 | # If this is a test file, extract all the test names and refresh their
49 | # status.
50 | all_lines = sublime.Region(0, view.size())
51 | line_regions = view.split_by_newlines(all_lines)
52 | test_failure_regions = []
53 | assert_failure_regions = []
54 | line_number = 1
55 | view.erase_phantoms("assert_phantoms")
56 | for line_region in line_regions:
57 | line_content = view.substr(line_region)
58 | test_name = get_test_name(line_content)
59 | if test_name is not None:
60 | key = file_path + ':' + test_name
61 |
62 | if key in test_failures:
63 | test_failure_regions.append(line_region)
64 |
65 | key = file_path + ':' + str(line_number)
66 | if key in assert_failures:
67 | # Skip whitespace at the start of the line so its neater.
68 | prefix_whitespace = len(line_content) - len(line_content.lstrip())
69 | assert_failure_regions.append(sublime.Region(line_region.a + prefix_whitespace, line_region.b))
70 |
71 | raw = assert_failures[key]
72 | html = '
' + raw.replace('\n', '
').replace(' ', ' ') + '
'
73 | view.add_phantom("assert_phantoms", line_region, html, sublime.LAYOUT_BLOCK)
74 |
75 | line_number += 1
76 |
77 | view.add_regions("v_test_failures", test_failure_regions, "region.redish", "circle",
78 | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE)
79 |
80 | view.add_regions("v_assert_failures", assert_failure_regions, "region.redish", "",
81 | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_SQUIGGLY_UNDERLINE)
82 |
83 | def refresh(self, view):
84 | # Only applies to v files.
85 | file_path = view.file_name()
86 | if file_path is None or not file_path.endswith('.v'):
87 | return
88 |
89 | # We need to test the whole module, otherwise it might not know about
90 | # entities in other files of the same module.
91 | module = os.path.dirname(file_path)
92 |
93 | # "-shared" prevents it from complaining if this is not a main module.
94 | command = "v -check -nocolor -shared -message-limit -1 \"" + module + "\""
95 | proc = subprocess.Popen(
96 | [os.environ['SHELL'], '-c', command],
97 | shell=False,
98 | stdout=subprocess.PIPE,
99 | stderr=subprocess.STDOUT,
100 | universal_newlines=True,
101 | env=os.environ.copy()
102 | )
103 |
104 | full_output = ''
105 | while True:
106 | out = proc.stdout.read(1)
107 | if out == '' and proc.poll() != None:
108 | break
109 |
110 | full_output += out
111 |
112 | error_regions = []
113 | error_annotations = []
114 | warning_regions = []
115 | warning_annotations = []
116 | for line in full_output.split('\n'):
117 | matches = re.search(r"(\d+):(\d+): (.*)", line)
118 |
119 | # TODO(elliotchance): We are only showing the errors/warnings for
120 | # this view, but we should also apply any output to other open
121 | # files to prevent needing to rerun the checker whenever a tab
122 | # changes.
123 | if matches is None or file_path not in line:
124 | continue
125 |
126 | offset = view.text_point(int(matches.group(1))-1, 0) + int(matches.group(2)) - 1
127 |
128 | # The error message may give us a token which we can use to
129 | # determine the length. We *could* use the "~~~" that appears later
130 | # in the stream, but thats a bit fidely, so I'm just going to use
131 | # the token or goto the end of the line.
132 | symbol = re.search(r"`(.*?)`", line)
133 | if symbol is None:
134 | end = view.text_point(int(matches.group(1)), 0)-1
135 | else:
136 | end = offset + len(symbol.group(1))
137 |
138 | region = sublime.Region(offset, end)
139 |
140 | msg = ':'.join(line.split(':')[4:])
141 | if 'warning:' in line:
142 | warning_regions.append(region)
143 | warning_annotations.append(msg)
144 | else:
145 | error_regions.append(region)
146 | error_annotations.append(msg)
147 |
148 | view.add_regions("v_errors", error_regions, "region.redish", "dot",
149 | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_SQUIGGLY_UNDERLINE,
150 | error_annotations)
151 |
152 | view.add_regions("v_warnings", warning_regions, "region.yellowish", "dot",
153 | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_SQUIGGLY_UNDERLINE,
154 | warning_annotations, 'yellow')
155 |
156 | class VCommand(sublime_plugin.WindowCommand):
157 | def run(self, cmd, output=True):
158 | global test_failures, assert_failures
159 |
160 | # Make sure we get the file path before we open a new tab.
161 | cur_view = self.window.active_view()
162 | file = cur_view.file_name()
163 | module = os.path.dirname(cur_view.file_name())
164 |
165 | # Substitute variables. It would be nice to use
166 | # sublime.expand_variables() here instead, but that does not escape
167 | # spaces in paths.
168 | variables = sublime.active_window().extract_variables()
169 |
170 | # We have some extra variables we can use.
171 | sel = cur_view.sel()
172 | if len(sel) == 1:
173 | line = cur_view.substr(cur_view.full_line(sel[0]))
174 | matches = re.search(r"fn\s+(test_[a-zA-Z_0-9]+)", line)
175 | if matches is not None:
176 | variables['function_at_cursor'] = matches.group(1)
177 |
178 | look_for_failed_tests = False
179 | if cmd.startswith("test"):
180 | look_for_failed_tests = True
181 |
182 | # Clear any existing tests within this file.
183 | test_failures = {k: v[k] for k in test_failures if not k.startswith(file + ":")}
184 | assert_failures = {k: v[k] for k in assert_failures if not k.startswith(file + ":")}
185 |
186 | cmd = "v " + cmd
187 | for variable in variables:
188 | cmd = cmd.replace('${' + variable + '}', '"' + variables[variable] + '"')
189 |
190 | view = None
191 | if output:
192 | view = setup_output(self.window)
193 | view.run_command('insert_view', { 'string': '$ ' + cmd + '\n' })
194 |
195 | runner = Runner(cmd, os.environ['SHELL'], os.environ.copy(), view, output, look_for_failed_tests)
196 | runner.start()
197 |
198 | class InsertViewCommand(sublime_plugin.TextCommand):
199 | def run(self, edit, string=''):
200 | self.view.set_read_only(False)
201 | self.view.insert(edit, self.view.size(), string)
202 | self.view.set_read_only(True)
203 |
204 | class Runner(threading.Thread):
205 | def __init__(self, command, shell, env, view, output, look_for_failed_tests):
206 | self.stdout = None
207 | self.stderr = None
208 | self.command = command
209 | self.shell = shell
210 | self.env = env
211 | self.view = view
212 | self.output = output
213 | self.look_for_failed_tests = look_for_failed_tests
214 | threading.Thread.__init__(self)
215 |
216 | def run(self):
217 | global test_failures, assert_failures
218 |
219 | proc = subprocess.Popen(
220 | [self.shell, '-c', self.command],
221 | shell=False,
222 | stdout=subprocess.PIPE,
223 | stderr=subprocess.STDOUT,
224 | universal_newlines=True,
225 | env=self.env
226 | )
227 |
228 | full_output = ''
229 | while True:
230 | out = proc.stdout.read(1)
231 | if out == '' and proc.poll() != None:
232 | break
233 | if out != '':
234 | full_output += out
235 |
236 | if self.look_for_failed_tests:
237 | matches = re.findall(r"(.*_test\.v):(\d+): fn (test_.*)([\s\S]*?(?=\n{2,}))", full_output)
238 | for match in matches:
239 | test_failures[match[0] + ":" + match[2]] = True
240 | assert_failures[match[0] + ":" + match[1]] = match[3][1:] # Trim initial new line
241 |
242 | if self.output:
243 | self.view.run_command('insert_view', { 'string': full_output + '\n' })
244 |
--------------------------------------------------------------------------------