├── .gitignore
├── Default.sublime-keymap
├── README.md
├── core
├── __init__.py
├── install.py
└── project.py
├── src
└── ts
│ ├── compilerservice.ts
│ └── main.ts
├── test
├── test_code.ts
├── test_code_2.ts
└── test_dep.ts
├── typescript.py
├── typescript.sublime-settings
└── typescript.tmLanguage
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/*.js
2 | src/*.js
3 | core/settings.py
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/Default.sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "command": "typescript_complete",
4 | "args": {"characters": "."},
5 | "keys": ["."],
6 | "context": [ {"key": "typescript"} ]
7 | },
8 | {
9 | "command": "typescript_complete",
10 | "args": {"characters": ""},
11 | "keys": ["ctrl+space"],
12 | "context": [ {"key": "typescript"} ]
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WARNING
2 | -------
3 |
4 | UPDATE : A new project, [T3S](https://github.com/Railk/T3S), has risen, and does all this project does apparently, and more, so give it a try !
5 |
6 | THIS PLUGIN IS NOT MAINTAINED ANYMORE, AND WILL NOT BE.
7 | IF YOU NEED IMPROVEMENTS/BUG FIXES YOU WILL HAVE TO DO THEM YOURSELF.
8 | IF YOU WANT TO TAKE OWNERSHIP OF THE PROJECT, JUST TELL ME AND I WILL BE HAPPY TO GIVE IT TO YOU.
9 |
10 | Sublime-Typescript
11 | ==================
12 |
13 | A Sublime Text plugin for the Typescript language
14 |
15 | Installation
16 | ------------
17 |
18 | You need to have node.js installed before anything.
19 |
20 | Clone the repository in your sublime "Packages" directory.
21 | You also need to ensure that the node executable is on your path, or that the "node_path" key is set somewhere in a
22 | typescript.sublime-settings settings file that sublime text can reach.
23 |
24 | ~~~sh
25 | git clone https://github.com/raph-amiard/sublime-typescript
26 | ~~~
27 |
28 | After that you're set and you can use the plugin !
29 | First run might take long to set up, and it will need an internet connection, because the plugin is actually :
30 | - Getting the typescript sources online.
31 | - Compiling it's JS part the first time you will use it.
32 |
33 | After that you can use the plugin offline.
34 | If you don't have an internet connection, getting typescript sources and putting them in the lib/typescript directory will work too.
35 |
36 | Usage
37 | -----
38 |
39 | For the moment the functionnality is very basic :
40 | - Errors get highlighted and the errors messages shows in the status bar
41 | - Autocompletion works (quite well thanks to the TypeScript language service)
42 |
43 | 
44 |
45 | ### Settings
46 |
47 | All the settings discussed here can be set either in the typescript.sublime-settings file of the plugin folder, or in your own typescript.sublime-settings, as is usual with sublime text configuration
48 |
49 | #### Node path
50 |
51 | If node isn't on your path, or you want to set the node executable path manually, you can set the "node_path" key to refer to the node executable path, **including the executable name**.
52 |
53 | ~~~json
54 | {
55 | "node_path":"/my/path/to/node/node"
56 | }
57 | ~~~
58 |
59 | ### Projects
60 |
61 | By default, a new instance of the plugin server is created for every file.
62 | The TypeScript language service has an odd behaviour, as in, every file you add to the service will be considered to
63 | be in the same compilation unit as the others.
64 | If you want to specify to the plugin that some files are part of the same project, put a .sublimets file in the folder.
65 | If you don't do that, every file will be opened in a separate plugin instance
66 |
--------------------------------------------------------------------------------
/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raph-amiard/sublime-typescript/92185c71bcdb551b32e07e334ca4954c82df7c19/core/__init__.py
--------------------------------------------------------------------------------
/core/install.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import shutil
3 | import subprocess
4 | from os import path
5 | import os
6 |
7 | REPO_NAME = "sublime-typescript"
8 | PLUGIN_FILE_NAME = "typescript.py"
9 | TYPESCRIPT_SOURCE_LINK = "http://download-codeplex.sec.s-msft.com/Download/SourceControlFileDownload.ashx?ProjectName=typescript&changeSetId=6c2e2c092ba8"
10 |
11 | ts_settings = sublime.load_settings("typescript.sublime-settings")
12 | node_path = "node"
13 |
14 | startupinfo = None
15 | if os.name == 'nt':
16 | startupinfo = subprocess.STARTUPINFO()
17 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
18 |
19 | if ts_settings.has("node_path"):
20 | node_path = ts_settings.get("node_path")
21 | def get_node_path():
22 | return node_path
23 |
24 | def check_for_node():
25 | node_path = get_node_path()
26 | try:
27 | subprocess.call([node_path, "--help"], startupinfo = startupinfo)
28 | except Exception, e:
29 | sublime.error_message("The node executable hasn't been found, you might want to set it in your typescript settings by adding the \"node_path\" key")
30 | raise e
31 |
32 | def check_plugin_path():
33 | if ts_settings.has("plugin_path") and path.isdir(ts_settings.get("plugin_path")):
34 | return
35 |
36 | # Find the plugin path (hackish way because i have no other way)
37 | packages_path = sublime.packages_path()
38 | plugin_path = path.join(packages_path, "sublime-typescript")
39 | if not path.isdir(plugin_path):
40 | plugin_path = None
41 | for d in os.listdir(packages_path):
42 | if path.isdir(d):
43 | for f in os.listdir(d):
44 | if f == PLUGIN_FILE_NAME:
45 | plugin_path = path.join(packages_path, d)
46 | if not plugin_path:
47 | sublime.error_message("Plugin is not in the expected directory")
48 | plugin_path = path.abspath(plugin_path)
49 |
50 | # Write the plugin path into the settings file
51 | ts_settings.set("plugin_path", plugin_path)
52 | sublime.save_settings("typescript.sublime-settings")
53 |
54 | def needs_to_compile_plugin():
55 | return not path.exists(path.join(ts_settings.get("plugin_path"), "bin/main.js"))
56 |
57 | def compile_plugin(plugin_path):
58 | def plugin_file(f):
59 | return path.join(plugin_path, f)
60 |
61 | # Check if we got typescript in there
62 | typescript_dir = plugin_file("lib/typescript")
63 |
64 | if len(os.listdir(typescript_dir)) == 0:
65 | # We need to get typescript and unzip it
66 | import urllib
67 | import zipfile
68 | zf_path = plugin_file("lib/typescript/ts.zip")
69 | urllib.urlretrieve(TYPESCRIPT_SOURCE_LINK, zf_path)
70 | zipf = zipfile.ZipFile(zf_path)
71 | zipf.extractall(path=plugin_file("lib/typescript/"))
72 | zipf.close()
73 | os.remove(zf_path)
74 |
75 | # Compile the plugin
76 | bindir = plugin_file("bin")
77 | if not path.exists(bindir):
78 | os.makedirs(bindir)
79 |
80 | subprocess.call([get_node_path(),
81 | plugin_file("lib/typescript/bin/tsc.js"),
82 | plugin_file("src/ts/main.ts"),
83 | "--out", plugin_file("bin/main.js")],
84 | startupinfo = startupinfo)
85 |
86 | # Copy needed files to bin directory
87 | shutil.copyfile(plugin_file("lib/typescript/bin/typescript.js"),
88 | plugin_file("bin/typescript.js"))
89 | shutil.copyfile(plugin_file("lib/typescript/bin/typescriptServices.js"),
90 | plugin_file("bin/typescriptServices.js"))
91 | shutil.copyfile(plugin_file("lib/typescript/bin/lib.d.ts"),
92 | plugin_file("bin/lib.d.ts"))
93 |
--------------------------------------------------------------------------------
/core/project.py:
--------------------------------------------------------------------------------
1 | from os import path
2 | import os
3 |
4 | def find_project_file(file_path):
5 | a, b = path.split(file_path)
6 | while b != "":
7 | files = os.listdir(a)
8 | if ".sublimets" in files:
9 | print "FOUND project file", path.join(a, ".sublimets"), "for ts file ", file_path
10 | return path.join(a, ".sublimets")
11 | a, b = path.split(a)
12 |
13 | return file_path
14 |
--------------------------------------------------------------------------------
/src/ts/compilerservice.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 |
6 | declare var it;
7 | declare var describe;
8 | declare var run;
9 | declare var IO: IIO;
10 | declare var __dirname; // Node-specific
11 |
12 | var global = Function("return this").call(null);
13 |
14 | function switchToForwardSlashes(path: string) {
15 | return path.replace(/\\/g, "/");
16 | }
17 |
18 | function filePath(fullPath: string) {
19 | fullPath = switchToForwardSlashes(fullPath);
20 | var components = fullPath.split("/");
21 | var path: string[] = components.slice(0, components.length - 1);
22 | return path.join("/") + "/";
23 | }
24 |
25 | module CService {
26 |
27 | export var userSpecifiedroot = "";
28 | export function readFile(path: string) {
29 | return IO.readFile(userSpecifiedroot + path);
30 | }
31 |
32 | export module Compiler {
33 | // Aggregate various writes into a single array of lines. Useful for passing to the
34 | // TypeScript compiler to fill with source code or errors.
35 | export class WriterAggregator implements ITextWriter {
36 | public lines: string[] = [];
37 | public currentLine = "";
38 |
39 | public Write(str) {
40 | this.currentLine += str;
41 | }
42 |
43 | public WriteLine(str) {
44 | this.lines.push(this.currentLine + str);
45 | this.currentLine = "";
46 | }
47 |
48 | public Close() {
49 | this.lines.push(this.currentLine);
50 | this.currentLine = "";
51 | }
52 |
53 | public reset() {
54 | this.lines = [];
55 | this.currentLine = "";
56 | }
57 | }
58 |
59 | var libFolder: string = global['WScript'] ? TypeScript.filePath(global['WScript'].ScriptFullName) : (__dirname + '/');
60 | export var libText = IO ? IO.readFile(libFolder + "lib.d.ts") : '';
61 |
62 | var stdout = new WriterAggregator();
63 | var stderr = new WriterAggregator();
64 | var currentUnit = 0;
65 | var maxUnit = 0;
66 |
67 | export var compiler: TypeScript.TypeScriptCompiler;
68 | recreate();
69 |
70 | // Types
71 | export class Type {
72 | constructor (public type, public code, public identifier) { }
73 |
74 | public normalizeToArray(arg: any) {
75 | if ((Array.isArray && Array.isArray(arg)) || arg instanceof Array)
76 | return arg;
77 |
78 | return [arg];
79 | }
80 |
81 | public compilesOk(testCode): bool {
82 | var errors = null;
83 | compileString(testCode, 'test.ts', function (compilerResult) {
84 | errors = compilerResult.errors;
85 | })
86 |
87 | return errors.length === 0;
88 | }
89 |
90 | public isSubtypeOf(other: Type) {
91 | var testCode = 'class __test1__ {\n';
92 | testCode += ' public test() {\n';
93 | testCode += ' ' + other.code + ';\n';
94 | testCode += ' return ' + other.identifier + ';\n';
95 | testCode += ' }\n';
96 | testCode += '}\n';
97 | testCode += 'class __test2__ extends __test1__ {\n';
98 | testCode += ' public test() {\n';
99 | testCode += ' ' + this.code + ';\n';
100 | testCode += ' return ' + other.identifier + ';\n';
101 | testCode += ' }\n';
102 | testCode += '}\n';
103 |
104 | return this.compilesOk(testCode);
105 | }
106 |
107 | // TODO: Find an implementation of isIdenticalTo that works.
108 | public isIdenticalTo(other: Type) {
109 | var testCode = 'module __test1__ {\n';
110 | testCode += ' ' + this.code + ';\n';
111 | testCode += ' export var __val__ = ' + this.identifier + ';\n';
112 | testCode += '}\n';
113 | testCode += 'var __test1__val__ = __test1__.__val__;\n';
114 |
115 | testCode += 'module __test2__ {\n';
116 | testCode += ' ' + other.code + ';\n';
117 | testCode += ' export var __val__ = ' + other.identifier + ';\n';
118 | testCode += '}\n';
119 | testCode += 'var __test2__val__ = __test2__.__val__;\n';
120 |
121 | testCode += 'function __test__function__() { if(true) { return __test1__val__ }; return __test2__val__; }';
122 |
123 | return this.compilesOk(testCode);
124 | }
125 |
126 | public assertSubtypeOf(others: any) {
127 | others = this.normalizeToArray(others);
128 |
129 | for (var i = 0; i < others.length; i++) {
130 | if (!this.isSubtypeOf(others[i])) {
131 | throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type);
132 | }
133 | }
134 | }
135 |
136 | public assertNotSubtypeOf(others: any) {
137 | others = this.normalizeToArray(others);
138 |
139 | for (var i = 0; i < others.length; i++) {
140 | if (this.isSubtypeOf(others[i])) {
141 | throw new Error("Expected " + this.type + " to be a subtype of " + others[i].type);
142 | }
143 | }
144 | }
145 |
146 | public assertIdenticalTo(other: Type) {
147 | if (!this.isIdenticalTo(other)) {
148 | throw new Error("Expected " + this.type + " to be identical to " + other.type);
149 | }
150 | }
151 |
152 | public assertNotIdenticalTo(other: Type) {
153 | if (!this.isIdenticalTo(other)) {
154 | throw new Error("Expected " + this.type + " to not be identical to " + other.type);
155 | }
156 | }
157 |
158 | public isAssignmentCompatibleWith(other: Type) {
159 | var testCode = 'module __test1__ {\n';
160 | testCode += ' ' + this.code + ';\n';
161 | testCode += ' export var __val__ = ' + this.identifier + ';\n';
162 | testCode += '}\n';
163 | testCode += 'var __test1__val__ = __test1__.__val__;\n';
164 |
165 | testCode += 'module __test2__ {\n';
166 | testCode += ' export ' + other.code + ';\n';
167 | testCode += ' export var __val__ = ' + other.identifier + ';\n';
168 | testCode += '}\n';
169 | testCode += 'var __test2__val__ = __test2__.__val__;\n';
170 |
171 | testCode += '__test2__val__ = __test1__val__;';
172 |
173 | return this.compilesOk(testCode);
174 | }
175 |
176 | public assertAssignmentCompatibleWith(others: any) {
177 | others = this.normalizeToArray(others);
178 |
179 | for (var i = 0; i < others.length; i++) {
180 | var other = others[i];
181 |
182 | if (!this.isAssignmentCompatibleWith(other)) {
183 | throw new Error("Expected " + this.type + " to be assignment compatible with " + other.type);
184 | }
185 | }
186 | }
187 |
188 | public assertNotAssignmentCompatibleWith(others: any) {
189 | others = this.normalizeToArray(others);
190 |
191 | for (var i = 0; i < others.length; i++) {
192 | var other = others[i];
193 |
194 | if (this.isAssignmentCompatibleWith(other)) {
195 | throw new Error("Expected " + this.type + " to not be assignment compatible with " + other.type);
196 | }
197 | }
198 | }
199 | }
200 |
201 | export class TypeFactory {
202 | public any: Type;
203 | public number: Type;
204 | public string: Type;
205 | public bool: Type;
206 |
207 | constructor () {
208 | this.any = this.get('var x : any', 'x');
209 | this.number = this.get('var x : number', 'x');
210 | this.string = this.get('var x : string', 'x');
211 | this.bool = this.get('var x : bool', 'x');
212 | }
213 |
214 | public get(code: string, identifier: string) {
215 | var errors = null;
216 | compileString(code, 'test.ts', function (compilerResult) {
217 | errors = compilerResult.errors;
218 | })
219 |
220 | if (errors.length > 0)
221 | throw new Error("Type definition contains errors: " + errors.join(","));
222 |
223 | // REVIEW: For a multi-file test, this won't work
224 | var script = compiler.scripts.members[1];
225 | var enclosingScopeContext = TypeScript.findEnclosingScopeAt(new TypeScript.NullLogger(), script, new TypeScript.StringSourceText(code), 0, false);
226 | var entries = new TypeScript.ScopeTraversal(compiler).getScopeEntries(enclosingScopeContext);
227 | for (var i = 0; i < entries.length; i++) {
228 | if (entries[i].name === identifier) {
229 | return new Type(entries[i].type, code, identifier);
230 | }
231 | }
232 | }
233 |
234 | }
235 |
236 | export function generateDeclFile(code: string, verifyNoDeclFile: bool): string {
237 | reset();
238 |
239 | compiler.settings.generateDeclarationFiles = true;
240 | var oldOutputMany = compiler.settings.outputMany;
241 | try {
242 | addUnit(code);
243 | compiler.reTypeCheck();
244 |
245 | var outputs = {};
246 |
247 | compiler.settings.outputMany = true;
248 | compiler.emitDeclarationFile((fn: string) => {
249 | outputs[fn] = new Compiler.WriterAggregator();
250 | return outputs[fn];
251 | });
252 |
253 | for (var fn in outputs) {
254 | if (fn.indexOf('.d.ts') >= 0) {
255 | var writer = outputs[fn];
256 | writer.Close();
257 | if (verifyNoDeclFile) {
258 | throw new Error('Compilation should not produce ' + fn);
259 | }
260 | return writer.lines.join('\n');
261 | }
262 | }
263 |
264 | if (!verifyNoDeclFile) {
265 | throw new Error('Compilation did not produced .d.ts files');
266 | }
267 | } finally {
268 | compiler.settings.generateDeclarationFiles = false;
269 | compiler.settings.outputMany = oldOutputMany;
270 | }
271 |
272 | return '';
273 | }
274 |
275 | // Contains the code and errors of a compilation and some helper methods to check its status.
276 | export class CompilerResult {
277 | public code: string;
278 | public errors: CompilerError[];
279 |
280 | constructor (codeLines: string[], errorLines: string[], public scripts: TypeScript.Script[]) {
281 | this.code = codeLines.join("\n")
282 | this.errors = [];
283 |
284 | for (var i = 0; i < errorLines.length; i++) {
285 | var match = errorLines[i].match(/([^\(]*)\((\d+),(\d+)\):\s+((.*[\s\r\n]*.*)+)\s*$/);
286 | if (match) {
287 | this.errors.push(new CompilerError(match[1], parseFloat(match[2]), parseFloat(match[3]), match[4]));
288 | }
289 | else {
290 | WScript.Echo("non-match on: " + errorLines[i]);
291 | }
292 | }
293 | }
294 |
295 | public isErrorAt(line: number, column: number, message: string) {
296 | for (var i = 0; i < this.errors.length; i++) {
297 | if (this.errors[i].line === line && this.errors[i].column === column && this.errors[i].message === message)
298 | return true;
299 | }
300 |
301 | return false;
302 | }
303 | }
304 |
305 | // Compiler Error.
306 | export class CompilerError {
307 | constructor (public file: string,
308 | public line: number,
309 | public column: number,
310 | public message: string) { }
311 |
312 | public toString() {
313 | return this.file + "(" + this.line + "," + this.column + "): " + this.message;
314 | }
315 | }
316 |
317 | export function recreate() {
318 | compiler = new TypeScript.TypeScriptCompiler(stderr);
319 | compiler.parser.errorRecovery = true;
320 | compiler.settings.codeGenTarget = TypeScript.CodeGenTarget.ES5;
321 | compiler.settings.controlFlow = true;
322 | compiler.settings.controlFlowUseDef = true;
323 | TypeScript.moduleGenTarget = TypeScript.ModuleGenTarget.Synchronous;
324 | compiler.addUnit(libText, 'lib.d.ts', true);
325 | compiler.typeCheck();
326 | currentUnit = 0;
327 | maxUnit = 0;
328 | }
329 | export function reset() {
330 | stdout.reset();
331 | stderr.reset();
332 |
333 | for (var i = 0; i < currentUnit; i++) {
334 | compiler.updateUnit('', i + '.ts', false/*setRecovery*/);
335 | }
336 |
337 | compiler.errorReporter.hasErrors = false;
338 | currentUnit = 0;
339 | }
340 |
341 | export function addUnit(code: string, isResident?: bool, isDeclareFile?: bool) {
342 | var script: TypeScript.Script = null;
343 | if (currentUnit >= maxUnit) {
344 | script = compiler.addUnit(code, currentUnit++ + (isDeclareFile ? '.d.ts' : '.ts'), isResident);
345 | maxUnit++;
346 | } else {
347 | var filename = currentUnit + (isDeclareFile ? '.d.ts' : '.ts');
348 | compiler.updateUnit(code, filename, false/*setRecovery*/);
349 |
350 | for (var i = 0; i < compiler.units.length; i++) {
351 | if (compiler.units[i].filename === filename)
352 | script = compiler.scripts.members[i];
353 | }
354 |
355 | currentUnit++;
356 | }
357 |
358 | return script;
359 | }
360 |
361 | export function compileUnit(path: string, callback: (res: CompilerResult) => void , settingsCallback?: () => void ) {
362 | if (settingsCallback) {
363 | settingsCallback();
364 | }
365 | path = switchToForwardSlashes(path);
366 | compileString(readFile(path), path.match(/[^\/]*$/)[0], callback);
367 | }
368 | export function compileUnits(callback: (res: Compiler.CompilerResult) => void, settingsCallback?: () => void ) {
369 | reset();
370 | if (settingsCallback) {
371 | settingsCallback();
372 | }
373 |
374 | compiler.reTypeCheck();
375 | compiler.emitToOutfile(stdout);
376 |
377 | callback(new CompilerResult(stdout.lines, stderr.lines, []));
378 |
379 | recreate();
380 | reset();
381 | }
382 | export function compileString(code: string, unitName: string, callback: (res: Compiler.CompilerResult) => void , refreshUnitsForLSTests? = false) {
383 | var scripts: TypeScript.Script[] = [];
384 |
385 | // TODO: How to overload?
386 | if (typeof unitName === 'function') {
387 | callback = <(res: CompilerResult) => void >(unitName);
388 | unitName = 'test.ts';
389 | }
390 |
391 | reset();
392 |
393 | // Some command-line tests may pollute the global namespace, which could interfere with
394 | // with language service testing.
395 | // In the case of LS tests, make sure that we refresh the first unit, and not try to update it
396 | if (refreshUnitsForLSTests) {
397 | maxUnit = 0;
398 | }
399 |
400 | scripts.push(addUnit(code));
401 | compiler.reTypeCheck();
402 | compiler.emitToOutfile(stdout);
403 |
404 | callback(new CompilerResult(stdout.lines, stderr.lines, scripts));
405 | }
406 | }
407 |
408 | export class ScriptInfo {
409 | public version: number;
410 | public editRanges: { length: number; editRange: TypeScript.ScriptEditRange; }[] = [];
411 |
412 | constructor (public name: string, public content: string, public isResident: bool, public maxScriptVersions: number) {
413 | this.version = 1;
414 | }
415 |
416 | public updateContent(content: string, isResident: bool) {
417 | this.editRanges = [];
418 | this.content = content;
419 | this.isResident = isResident;
420 | this.version++;
421 | }
422 |
423 | public editContent(minChar: number, limChar: number, newText: string) {
424 | // Apply edits
425 | var prefix = this.content.substring(0, minChar);
426 | var middle = newText;
427 | var suffix = this.content.substring(limChar);
428 | this.content = prefix + middle + suffix;
429 |
430 | // Store edit range + new length of script
431 | this.editRanges.push({
432 | length: this.content.length,
433 | editRange: new TypeScript.ScriptEditRange(minChar, limChar, (limChar - minChar) + newText.length)
434 | });
435 |
436 | if (this.editRanges.length > this.maxScriptVersions) {
437 | this.editRanges.splice(0, this.maxScriptVersions - this.editRanges.length);
438 | }
439 |
440 | // Update version #
441 | this.version++;
442 | }
443 |
444 | public getEditRangeSinceVersion(version: number): TypeScript.ScriptEditRange {
445 | if (this.version == version) {
446 | // No edits!
447 | return null;
448 | }
449 |
450 | var initialEditRangeIndex = this.editRanges.length - (this.version - version);
451 | if (initialEditRangeIndex < 0 || initialEditRangeIndex >= this.editRanges.length) {
452 | // Too far away from what we know
453 | return TypeScript.ScriptEditRange.unknown();
454 | }
455 |
456 | var entries = this.editRanges.slice(initialEditRangeIndex);
457 |
458 | var minDistFromStart = entries.map(x => x.editRange.minChar).reduce((prev, current) => Math.min(prev, current));
459 | var minDistFromEnd = entries.map(x => x.length - x.editRange.limChar).reduce((prev, current) => Math.min(prev, current));
460 | var aggDelta = entries.map(x => x.editRange.delta).reduce((prev, current) => prev + current);
461 |
462 | return new TypeScript.ScriptEditRange(minDistFromStart, entries[0].length - minDistFromEnd, aggDelta);
463 | }
464 | }
465 |
466 | export class TypeScriptLS implements Services.ILanguageServiceShimHost {
467 | private ls: Services.ILanguageServiceShim = null;
468 |
469 | public scripts: ScriptInfo[] = [];
470 | public maxScriptVersions = 100;
471 |
472 | public addDefaultLibrary() {
473 | this.addScript("lib.d.ts", Compiler.libText, true);
474 | }
475 |
476 | public addFile(name: string, isResident = false) {
477 | var code: string = readFile(name);
478 | this.addScript(name, code, isResident);
479 | }
480 |
481 | public addScript(name: string, content: string, isResident = false) {
482 | var script = new ScriptInfo(name, content, isResident, this.maxScriptVersions);
483 | this.scripts.push(script);
484 | }
485 |
486 | public updateScript(name: string, content: string, isResident = false) {
487 | for (var i = 0; i < this.scripts.length; i++) {
488 | if (this.scripts[i].name == name) {
489 | this.scripts[i].updateContent(content, isResident);
490 | return;
491 | }
492 | }
493 |
494 | this.addScript(name, content, isResident);
495 | }
496 |
497 | public editScript(name: string, minChar: number, limChar: number, newText: string) {
498 | for (var i = 0; i < this.scripts.length; i++) {
499 | if (this.scripts[i].name == name) {
500 | this.scripts[i].editContent(minChar, limChar, newText);
501 | return;
502 | }
503 | }
504 |
505 | throw new Error("No script with name '" + name + "'");
506 | }
507 |
508 | public getScriptContent(scriptIndex: number): string {
509 | return this.scripts[scriptIndex].content;
510 | }
511 |
512 | //////////////////////////////////////////////////////////////////////
513 | // ILogger implementation
514 | //
515 | public information(): bool { return false; }
516 | public debug(): bool { return true; }
517 | public warning(): bool { return true; }
518 | public error(): bool { return true; }
519 | public fatal(): bool { return true; }
520 |
521 | public log(s: string): void {
522 | // For debugging...
523 | //IO.printLine("TypeScriptLS:" + s);
524 | }
525 |
526 | //////////////////////////////////////////////////////////////////////
527 | // ILanguageServiceShimHost implementation
528 | //
529 |
530 | public getCompilationSettings(): string/*json for Tools.CompilationSettings*/ {
531 | return ""; // i.e. default settings
532 | }
533 |
534 | public getScriptCount(): number {
535 | return this.scripts.length;
536 | }
537 |
538 | public getScriptSourceText(scriptIndex: number, start: number, end: number): string {
539 | return this.scripts[scriptIndex].content.substring(start, end);
540 | }
541 |
542 | public getScriptSourceLength(scriptIndex: number): number {
543 | return this.scripts[scriptIndex].content.length;
544 | }
545 |
546 | public getScriptId(scriptIndex: number): string {
547 | return this.scripts[scriptIndex].name;
548 | }
549 |
550 | public getScriptIsResident(scriptIndex: number): bool {
551 | return this.scripts[scriptIndex].isResident;
552 | }
553 |
554 | public getScriptVersion(scriptIndex: number): number {
555 | return this.scripts[scriptIndex].version;
556 | }
557 |
558 | public getScriptEditRangeSinceVersion(scriptIndex: number, scriptVersion: number): string {
559 | var range = this.scripts[scriptIndex].getEditRangeSinceVersion(scriptVersion);
560 | var result = (range.minChar + "," + range.limChar + "," + range.delta);
561 | return result;
562 | }
563 |
564 | //
565 | // Return a new instance of the language service shim, up-to-date wrt to typecheck.
566 | // To access the non-shim (i.e. actual) language service, use the "ls.languageService" property.
567 | //
568 | public getLanguageService(): Services.ILanguageServiceShim {
569 | var ls = new Services.TypeScriptServicesFactory().createLanguageServiceShim(this);
570 | ls.refresh(true);
571 | this.ls = ls;
572 | return ls;
573 | }
574 |
575 | //
576 | // Parse file given its source text
577 | //
578 | public parseSourceText(fileName: string, sourceText: TypeScript.ISourceText): TypeScript.Script {
579 | var parser = new TypeScript.Parser();
580 | parser.setErrorRecovery(null);
581 | parser.errorCallback = (a, b, c, d) => { };
582 |
583 | var script = parser.parse(sourceText, fileName, 0);
584 | return script;
585 | }
586 |
587 | //
588 | // Parse a file on disk given its filename
589 | //
590 | public parseFile(fileName: string) {
591 | var sourceText = new TypeScript.StringSourceText(IO.readFile(fileName))
592 | return this.parseSourceText(fileName, sourceText);
593 | }
594 |
595 | //
596 | // line and column are 1-based
597 | //
598 | public lineColToPosition(fileName: string, line: number, col: number): number {
599 | var script = this.ls.languageService.getScriptAST(fileName);
600 | //assert.notNull(script);
601 | //assert.is(line >= 1);
602 | //assert.is(col >= 1);
603 | //assert.is(line < script.locationInfo.lineMap.length);
604 |
605 | return TypeScript.getPositionFromLineColumn(script, line, col);
606 | }
607 |
608 | //
609 | // line and column are 1-based
610 | //
611 | public positionToLineCol(fileName: string, position: number): TypeScript.ILineCol {
612 | var script = this.ls.languageService.getScriptAST(fileName);
613 | //assert.notNull(script);
614 |
615 | var result = TypeScript.getLineColumnFromPosition(script, position);
616 |
617 | //assert.is(result.line >= 1);
618 | //assert.is(result.col >= 1);
619 | return result;
620 | }
621 |
622 | //
623 | // Verify that applying edits to "sourceFileName" result in the content of the file
624 | // "baselineFileName"
625 | //
626 | public checkEdits(sourceFileName: string, baselineFileName: string, edits: Services.TextEdit[]) {
627 | var script = readFile(sourceFileName);
628 | var formattedScript = this.applyEdits(script, edits);
629 | var baseline = readFile(baselineFileName);
630 |
631 | //assert.noDiff(formattedScript, baseline);
632 | //assert.equal(formattedScript, baseline);
633 | }
634 |
635 |
636 | //
637 | // Apply an array of text edits to a string, and return the resulting string.
638 | //
639 | public applyEdits(content: string, edits: Services.TextEdit[]): string {
640 | var result = content;
641 | edits = this.normalizeEdits(edits);
642 |
643 | for (var i = edits.length - 1; i >= 0; i--) {
644 | var edit = edits[i];
645 | var prefix = result.substring(0, edit.minChar);
646 | var middle = edit.text;
647 | var suffix = result.substring(edit.limChar);
648 | result = prefix + middle + suffix;
649 | }
650 | return result;
651 | }
652 |
653 | //
654 | // Normalize an array of edits by removing overlapping entries and sorting
655 | // entries on the "minChar" position.
656 | //
657 | private normalizeEdits(edits: Services.TextEdit[]): Services.TextEdit[] {
658 | var result: Services.TextEdit[] = [];
659 |
660 | function mapEdits(edits: Services.TextEdit[]): { edit: Services.TextEdit; index: number; }[] {
661 | var result = [];
662 | for (var i = 0; i < edits.length; i++) {
663 | result.push({ edit: edits[i], index: i });
664 | }
665 | return result;
666 | }
667 |
668 | var temp = mapEdits(edits).sort(function (a, b) {
669 | var result = a.edit.minChar - b.edit.minChar;
670 | if (result == 0)
671 | result = a.index - b.index;
672 | return result;
673 | });
674 |
675 | var current = 0;
676 | var next = 1;
677 | while (current < temp.length) {
678 | var currentEdit = temp[current].edit;
679 |
680 | // Last edit
681 | if (next >= temp.length) {
682 | result.push(currentEdit);
683 | current++;
684 | continue;
685 | }
686 | var nextEdit = temp[next].edit;
687 |
688 | var gap = nextEdit.minChar - currentEdit.limChar;
689 |
690 | // non-overlapping edits
691 | if (gap >= 0) {
692 | result.push(currentEdit);
693 | current = next;
694 | next++;
695 | continue;
696 | }
697 |
698 | // overlapping edits: for now, we only support ignoring an next edit
699 | // entirely contained in the current edit.
700 | if (currentEdit.limChar >= nextEdit.limChar) {
701 | next++;
702 | continue;
703 | }
704 | else {
705 | throw new Error("Trying to apply overlapping edits");
706 | }
707 | }
708 |
709 | return result;
710 | }
711 |
712 | }
713 | }
714 |
--------------------------------------------------------------------------------
/src/ts/main.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | var readline = require('readline');
5 |
6 | /*
7 | var typescriptLS = new CService.TypeScriptLS();
8 | var file_name = 'bin/test_code.ts';
9 | typescriptLS.addFile(file_name);
10 | var ls = typescriptLS.getLanguageService();
11 |
12 | var pos = lineColToPosition(file_name,11, 3)
13 | var result = ls.languageService.getCompletionsAtPosition(file_name, pos, true);
14 | console.log(result);
15 |
16 | pos = lineColToPosition(file_name,36, 9)
17 | result = ls.languageService.getCompletionsAtPosition(file_name, pos, true);
18 | console.log(result);
19 |
20 | console.log("HO HAI");
21 | */
22 |
23 | var ts_ls : CService.TypeScriptLS = new CService.TypeScriptLS();
24 | var ls : Services.ILanguageServiceShim;
25 |
26 | function lineColToPosition(fileName: string, line: number, col: number): number {
27 | var script = ls.languageService.getScriptAST(fileName);
28 | var lineMap = script.locationInfo.lineMap;
29 | var offset = lineMap[line] + (col - 1);
30 | return offset;
31 | }
32 |
33 | function repl(prompt : string, callback : (string) => void) {
34 | var rl = readline.createInterface(process.stdin, process.stdout);
35 | rl.setPrompt(prompt);
36 | rl.prompt();
37 | rl.on('line', function (line) { callback(line); rl.prompt(); });
38 | }
39 |
40 |
41 | var repl_actions = {
42 | // Set the root of the project
43 | // root_path : the root path relative to which
44 | // file paths will be resolved
45 | "set_root" : function (root_path) {
46 | CService.userSpecifiedroot = root_path;
47 | return {status: "OK"};
48 | },
49 |
50 | // Add a file to the list of tracked scripts
51 | // file_path : the path of the file to complete
52 | "add_file" : function (file_path) {
53 | ts_ls.addFile(file_path);
54 | ls = ts_ls.getLanguageService();
55 | return {status: "OK"};
56 | },
57 |
58 | // Initiate a completion request
59 | // file_path : the path of the file to complete
60 | // pos : either a number (absolute pos in the file), or a couple [line, col]
61 | // is_member : wether the completion is a member completion (eg a.***)
62 | "complete" : function (file_path, pos, is_member) {
63 | var ipos : number = (pos instanceof Array) ?
64 | lineColToPosition(file_path, pos[0], pos[1]) : pos;
65 |
66 | return {status: "OK",
67 | result: ls.languageService
68 | .getCompletionsAtPosition(file_path, ipos, is_member)};
69 | },
70 |
71 | "edit_script": function (file_path, min_char, lim_char, new_text) {
72 | ts_ls.editScript(file_path, min_char, lim_char, new_text);
73 | return {status: "OK"};
74 | },
75 |
76 | "update_script": function (file_path, content) {
77 | ts_ls.updateScript(file_path, content);
78 | return {status: "OK"};
79 | },
80 |
81 | "get_errors": function(file_path) {
82 | return {status: "OK",
83 | result: ls.languageService
84 | .getScriptErrors(file_path, 100)};
85 | },
86 |
87 | "dummy": function () {
88 | return {status: "OK", data:"dummy"};
89 | }
90 | }
91 |
92 | repl("", (line) => {
93 | var json_data = JSON.parse(line);
94 | var result;
95 | try {
96 | result = repl_actions[json_data[0]].apply(null, json_data.slice(1));
97 | console.error(result);
98 | } catch (err) {
99 | console.error(err);
100 | result = {status: "ERROR", error:err};
101 | }
102 | console.log(JSON.stringify(result));
103 | })
104 |
--------------------------------------------------------------------------------
/test/test_code.ts:
--------------------------------------------------------------------------------
1 | declare var document;
2 | declare var alert;
3 | module Foo {
4 | var testing = "";
5 | }
6 |
7 | class C1 {
8 | public pubMeth() {this.pubMeth();} // test on 'this.'
9 | private privMeth() {}
10 | public pubProp = 0;
11 | private privProp = 0;
12 | public testMeth() {
13 | this.pubMeth()
14 | return this;
15 | }
16 | }
17 |
18 | var f = new C1();
19 | f.pubMeth(); // test on F.
20 |
21 | module M {
22 | export class C {
23 | public pub = 0;
24 | private priv = 1;
25 | public test = 123;
26 | }
27 | export var V = 0;
28 | }
29 |
30 |
31 | var c = new M.C();
32 | c.test;
33 |
34 | class Greeter {
35 | greeting: string;
36 | constructor (message: string) {
37 | this.greeting = message;
38 | }
39 | greet() {
40 | return "Hello, " + this.greeting;
41 | }
42 |
43 | public test(a : string, b : string) : string {
44 | return a + " " + b;
45 | }
46 | }
47 |
48 | var greeter = new Greeter("world");
49 | greeter.greet() // test on greeter.
50 | greeter.test("lol", "hh");
51 | var gr2 : Greeter = new Greeter("haha");
52 |
53 | var button = document.createElement('button')
54 | button.innerText = "Say Hello"
55 | button.onclick = function() {
56 | alert(greeter.greet())
57 | }
58 |
59 | document.body.appendChild(button)
60 |
--------------------------------------------------------------------------------
/test/test_code_2.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare var console;
4 |
5 | class Test {
6 | public test () {
7 | console.log("oh hai");
8 | }
9 | public test_2(a : number, b : number) : number {
10 | return a + b;
11 | }
12 | }
13 |
14 | var v : C1 = new C1();
15 | var a : number = v.testMeth2(12, 15);
16 | var t : Test = new Test();
17 | v.pubMeth();
18 | var v2 : C1 = new C1();
19 |
--------------------------------------------------------------------------------
/test/test_dep.ts:
--------------------------------------------------------------------------------
1 |
2 | class C1 {
3 | public pubMeth() {this.pubMeth();} // test on 'this.'
4 | private privMeth() {}
5 | public pubProp = 0;
6 | private privProp = 0;
7 | public testMeth() {
8 | this.pubMeth()
9 | return this;
10 | }
11 | public testMeth2(a : number, b : number) {
12 | return a - b;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/typescript.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sublime, sublime_plugin
4 | from subprocess import Popen, PIPE
5 | import subprocess
6 | import json
7 | from os import path
8 | import os
9 | from threading import Thread, RLock, Semaphore
10 | from time import sleep, time
11 | import re
12 | import difflib
13 | from core import project, install
14 | from itertools import cycle, chain
15 | from collections import defaultdict
16 |
17 | class AtomicValue:
18 | def __init__(self):
19 | self.val = 0
20 | self.lock = RLock()
21 |
22 | def inc(self):
23 | self.lock.acquire()
24 | self.val += 1
25 | self.lock.release()
26 |
27 | def dec(self):
28 | self.lock.acquire()
29 | self.val -= 1
30 | self.lock.release()
31 |
32 | loading_files = AtomicValue()
33 |
34 | ts_settings = sublime.load_settings("typescript.sublime-settings")
35 |
36 | install.check_for_node()
37 | do_compile = install.check_plugin_path()
38 | plugin_path = ts_settings.get("plugin_path")
39 | def install_helper():
40 | loading_files.inc()
41 | install.compile_plugin(plugin_path)
42 | loading_files.dec()
43 | thread_install = None
44 | if install.needs_to_compile_plugin():
45 | thread_install = Thread(target=install_helper)
46 | thread_install.start()
47 |
48 | # ========================== GENERAL HELPERS ======================= #
49 |
50 | # === Helpers for async API calls
51 | global async_api_result
52 | async_api_result = None
53 | async_call_lock = Semaphore()
54 | def async_api_call(func, *args, **kwargs):
55 |
56 | def timeout_func():
57 | global async_api_result
58 | async_api_result = func(*args, **kwargs)
59 | async_call_lock.release()
60 |
61 | async_call_lock.acquire()
62 | sublime.set_timeout(timeout_func, 0)
63 | async_call_lock.acquire()
64 | async_call_lock.release()
65 | return async_api_result
66 |
67 | def is_ts(view):
68 | return view.file_name() and view.file_name().endswith(".ts")
69 |
70 | def get_all_text(view):
71 | return view.substr(sublime.Region(0, view.size()))
72 |
73 | def get_file_view(filename):
74 | for w in sublime.windows():
75 | for v in w.views():
76 | if v.file_name() == filename:
77 | return v
78 | return None
79 |
80 | def get_dep_text(filename):
81 | view = get_file_view(filename)
82 | if view:
83 | return get_all_text(view)
84 | else:
85 | f = open(filename)
86 | ct = f.read()
87 | f.close()
88 | return ct
89 |
90 | def format_diffs(old_content, new_content):
91 | seqmatcher = difflib.SequenceMatcher(None, old_content, new_content)
92 | return [(oc[1], oc[2], new_content[oc[3]:oc[4]] if oc[0] in ['insert', 'replace'] else "")
93 | for oc in seqmatcher.get_opcodes()
94 | if oc[0] in ['insert', 'delete', 'replace']]
95 |
96 | prefixes = {
97 | "method": u"◉",
98 | "property": u"●",
99 | "class":u"◆",
100 | "interface":u"◇",
101 | "keyword":u"∆",
102 | "variable": u"∨",
103 | }
104 |
105 | js_id_re = re.compile(
106 | ur'^[_$a-zA-Z\u00FF-\uFFFF][_$a-zA-Z0-9\u00FF-\uFFFF]*'
107 | )
108 |
109 | def is_member_completion(line):
110 | def partial_completion():
111 | sp = line.split(".")
112 | if len(sp) > 1:
113 | return js_id_re.match(sp[-1]) is not None
114 | return False
115 | return line.endswith(".") or partial_completion()
116 |
117 | def format_completion_entry(c_entry):
118 | prefix = prefixes.get(c_entry["kind"], u"-")
119 | prefix += " "
120 | middle = c_entry["name"]
121 | suffix = "\t" + c_entry["type"]
122 | return prefix + middle + suffix
123 |
124 | def partition_by(lst, disc):
125 | partitions = defaultdict(list)
126 | for el in lst:
127 | partitions[disc(el)].append(el)
128 | return partitions.values()
129 |
130 | def sort_completions(entries):
131 | return [(format_completion_entry(item), item["name"])
132 | for sublist in partition_by(entries, lambda entry: entry["kind"])
133 | for item in sorted(sublist, key=lambda entry: entry["name"])]
134 |
135 | def completions_ts_to_sublime(json_completions):
136 | return sort_completions(json_completions["entries"])
137 |
138 | def ts_errors_to_regions(ts_errors):
139 | return [sublime.Region(e["minChar"], e["limChar"]) for e in ts_errors]
140 |
141 | def text_from_diff(old_content, minChar, limChar, new_text):
142 | prefix = old_content[0:minChar]
143 | suffix = old_content[limChar:]
144 | return (prefix + new_text + suffix)
145 |
146 | def get_pos(view):
147 | return view.sel()[0].begin()
148 |
149 | def get_plugin_path():
150 | return plugin_path
151 |
152 | def plugin_file(file_path):
153 | return path.join(get_plugin_path(), file_path)
154 |
155 | node_path = "node"
156 | if ts_settings.has("node_path"):
157 | node_path = ts_settings.get("node_path")
158 |
159 | def get_node_path():
160 | return node_path
161 |
162 | # ================ SERVER AND COMMUNICATION HELPERS =============== #
163 |
164 | class PluginInstance(object):
165 | def __init__(self):
166 | print "PLUGIN_FILE ", plugin_file("bin/main.js")
167 | self.open_files = set()
168 | self.views_text = {}
169 | self.errors_intervals = {}
170 | self.init_sem = Semaphore()
171 |
172 | def init_async():
173 | if thread_install:
174 | thread_install.join()
175 | loading_files.inc()
176 | kwargs = {}
177 | errorlog = None
178 | if os.name == 'nt':
179 | errorlog = open(os.devnull, 'w')
180 | startupinfo = subprocess.STARTUPINFO()
181 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
182 | kwargs = {"stderr":errorlog, "startupinfo":startupinfo}
183 | self.p = Popen([get_node_path(), plugin_file("bin/main.js")], stdin=PIPE, stdout=PIPE, **kwargs)
184 |
185 | if errorlog:
186 | errorlog.close()
187 |
188 | self.serv_add_file(plugin_file("bin/lib.d.ts"))
189 | loading_files.dec()
190 | print "OUT OF INIT ASYNC"
191 | self.init_sem.release()
192 |
193 | self.init_sem.acquire()
194 | Thread(target=init_async).start()
195 |
196 | def close_process(self):
197 | self.p.terminate()
198 |
199 | def msg(self, *args):
200 | res = None
201 | message = json.dumps(args) + "\n"
202 | t = time()
203 | self.p.stdin.write(message)
204 | msg_content = self.p.stdout.readline()
205 | res = json.loads(msg_content)
206 | return res
207 |
208 | def serv_add_file(self, file_name):
209 | resp = self.msg("add_file", file_name)
210 |
211 | def serv_update_file(self, file_name, content):
212 | resp = self.msg("update_script", file_name, content)
213 |
214 | def serv_edit_file(self, file_name, min_char, new_char, new_text):
215 | resp = self.msg("edit_script", file_name, min_char, new_char, new_text)
216 |
217 | def serv_get_completions(self, file_name, pos, is_member):
218 | resp = self.msg("complete", file_name, pos, is_member)
219 | return resp["result"]
220 |
221 | def serv_get_errors(self, file_name):
222 | resp = self.msg("get_errors", file_name)
223 | return resp["result"]
224 |
225 |
226 | def init_file(self, filename):
227 | is_open = filename in self.open_files
228 | content = async_api_call(get_dep_text, filename)
229 | if not is_open:
230 | deps = [ref[1:-1] for ref in
231 | re.findall("/// *= l and pos <= h:
292 | return error
293 | return None
294 |
295 | def set_error_status(self, view):
296 | error = self.get_error_for_pos(get_pos(view))
297 | if error:
298 | sublime.status_message(error)
299 | else:
300 | sublime.status_message("")
301 |
302 |
303 | # ========================= STATUS MESSAGE MANAGEMENT ============= #
304 | def status_msg_setter(text):
305 | def set_status_msg():
306 | sublime.status_message(text)
307 | return set_status_msg
308 |
309 | def loading_status_msg():
310 | msg_base = "Loading typescript plugin"
311 | is_loading = False
312 | for el in cycle("|/-\\"):
313 | if loading_files.val > 0:
314 | is_loading = True
315 | msg = msg_base + " " + el
316 | sublime.set_timeout(status_msg_setter(msg), 0)
317 | elif is_loading == True:
318 | is_loading = False
319 | sublime.set_timeout(status_msg_setter(""), 0)
320 | sleep(0.1)
321 |
322 | Thread(target=loading_status_msg).start()
323 |
324 | # ========================= INITIALIZATION ======================== #
325 |
326 | # Iterate on every open view, add file to server if needed
327 | # for window in sublime.windows():
328 | # for view in window.views():
329 | # init_view(view)
330 |
331 | plugin_instances = {}
332 | project_files = {}
333 |
334 | def init_view(view):
335 | project_file = get_project_file(view)
336 | if project_file not in plugin_instances:
337 | plugin_instances[project_file] = PluginInstance()
338 | plugin_instances[project_file].views_text[view.file_name()] = get_all_text(view)
339 | plugin_instances[project_file].init_view(view)
340 |
341 | def close_view(view):
342 | project_file = get_project_file(view)
343 | if project_file in plugin_instances:
344 | plugin_instances[project_file].views_text.pop(view.file_name(), None)
345 | if len(plugin_instances[project_file].views_text) == 0:
346 | plugin_instances[project_file].close_process()
347 | plugin_instances.pop(project_file, None)
348 |
349 | def get_project_file(view):
350 | filename = view.file_name()
351 | if filename in project_files:
352 | return project_files[filename]
353 | else:
354 | pfile = project.find_project_file(filename)
355 | project_files[filename] = pfile
356 | return pfile
357 |
358 | def get_plugin(view):
359 | return plugin_instances[get_project_file(view)]
360 |
361 | # ========================= EVENT HANDLERS ======================== #
362 |
363 | class TypescriptComplete(sublime_plugin.TextCommand):
364 |
365 | def run(self, edit, characters):
366 | # Insert the autocomplete char
367 | for region in self.view.sel():
368 | self.view.insert(edit, region.end(), characters)
369 | # Update the code on the server side for the current file
370 | get_plugin(self.view).update_server_code(self.view.file_name(), get_all_text(self.view))
371 | self.view.run_command("auto_complete")
372 |
373 | class AsyncWorker(object):
374 |
375 | def __init__(self, view):
376 | self.view = view
377 | self.plugin = get_plugin(view)
378 | self.content = view.substr(sublime.Region(0, view.size()))
379 | self.filename = view.file_name()
380 | self.view_id = view.buffer_id()
381 | self.errors = None
382 | self.sem = Semaphore()
383 | self.sem.acquire()
384 | self.has_round_queued = False
385 |
386 | def do_more_work(self):
387 | self.content = self.view.substr(sublime.Region(0, self.view.size()))
388 | if not self.has_round_queued:
389 | self.sem.release()
390 | self.has_round_queued = True
391 |
392 | def final(self):
393 | self.plugin.handle_errors(self.view, self.errors)
394 | self.plugin.set_error_status(self.view)
395 | self.has_round_queued = False
396 |
397 | def work(self):
398 | while True:
399 | # Wait on semaphore
400 | self.sem.acquire()
401 | # Update the script
402 | self.plugin.update_server_code(self.filename, self.content)
403 | # Get errors
404 | self.errors = self.plugin.serv_get_errors(self.filename)
405 | sublime.set_timeout(self.final, 1)
406 | self.content = self.plugin.views_text[self.filename]
407 | sleep(1.3)
408 |
409 |
410 | class TestEvent(sublime_plugin.EventListener):
411 |
412 | workers = {}
413 |
414 | def get_worker_thread(self, view):
415 | bid = view.buffer_id()
416 | if not bid in self.workers:
417 | worker = AsyncWorker(view)
418 | Thread(target=worker.work).start()
419 | self.workers[bid] = worker
420 | return self.workers[bid]
421 |
422 | def on_load(self, view):
423 | print "IN ON LOAD FOR VIEW : ", view.file_name()
424 | if is_ts(view):
425 | init_view(view)
426 |
427 | def on_close(self, view):
428 | if is_ts(view):
429 | close_view(view)
430 |
431 | def on_modified(self, view):
432 | if view.is_loading(): return
433 | if is_ts(view):
434 | t = self.get_worker_thread(view)
435 | t.do_more_work()
436 |
437 | def on_selection_modified(self, view):
438 | if is_ts(view):
439 | get_plugin(view).set_error_status(view)
440 |
441 | def on_query_completions(self, view, prefix, locations):
442 | if is_ts(view):
443 | # Get the position of the cursor (first one in case of multiple sels)
444 | pos = view.sel()[0].begin()
445 | line = view.substr(sublime.Region(view.line(pos-1).a, pos))
446 | bword_pos = sublime.Region(view.word(pos).a, pos)
447 | word = view.substr(bword_pos)
448 | print "WORD : ", word
449 | completions_json = get_plugin(view).serv_get_completions(
450 | view.file_name(), bword_pos.a, is_member_completion(line)
451 | )
452 | get_plugin(view).set_error_status(view)
453 | return completions_ts_to_sublime(completions_json)
454 |
455 |
456 | def on_query_context(self, view, key, operator, operand, match_all):
457 | if key == "typescript":
458 | view = sublime.active_window().active_view()
459 | return is_ts(view)
460 |
461 |
462 | # msg("add_file", "bin/test_code.ts")
463 |
--------------------------------------------------------------------------------
/typescript.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | "extensions": ["ts", "str"],
3 | "auto_complete": false
4 | }
5 |
--------------------------------------------------------------------------------
/typescript.tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | comment
6 | TypeScript Syntax: version 1.0
7 | fileTypes
8 |
9 | ts
10 | str
11 |
12 | name
13 | TypeScript
14 | patterns
15 |
16 |
17 | captures
18 |
19 | 1
20 |
21 | name
22 | keyword.operator.ts
23 |
24 | 2
25 |
26 | name
27 | variable.parameter.function.ts
28 |
29 |
30 | comment
31 | Match stuff like: module name {...}
32 | match
33 | \b(module)\s*(\s*[a-zA-Z0-9_?.$][\w?.$]*)\s*
34 | name
35 | meta.function.ts
36 |
37 |
38 | comment
39 | Match variable type keywords
40 | match
41 | \b(string|bool|number)\b
42 | name
43 | storage.type.variable.ts
44 |
45 |
46 | captures
47 |
48 | 1
49 |
50 | name
51 | storage.type.variable.ts
52 |
53 |
54 | comment
55 | Match this.
56 | match
57 | \b(this)\.
58 | name
59 |
60 |
61 |
62 | comment
63 | Match stuff like: constructor
64 | match
65 | \b(constructor|declare|interface|as|AS)\b
66 | name
67 | keyword.operator.ts
68 |
69 |
70 | captures
71 |
72 | 1
73 |
74 | name
75 | storage.type.variable.ts
76 |
77 |
78 | comment
79 | Match stuff like: super(argument, list)
80 | match
81 | (super)\s*\(([a-zA-Z0-9,_?.$\s]+\s*)\)
82 | name
83 | keyword.other.ts
84 |
85 |
86 | captures
87 |
88 | 1
89 |
90 | name
91 | entity.name.function.ts
92 |
93 |
94 | comment
95 | Match stuff like: function() {...}
96 | match
97 | ([a-zA-Z_?.$][\w?.$]*)\(\) \{
98 | name
99 | meta.function.ts
100 |
101 |
102 | captures
103 |
104 | 1
105 |
106 | name
107 | variable.parameter.function.ts
108 |
109 | 2
110 |
111 | name
112 | variable.parameter.function.ts
113 |
114 |
115 | comment
116 | Match stuff like: (function: return type)
117 | match
118 | ([a-zA-Z0-9_?.$][\w?.$]*)\s*:\s*([a-zA-Z0-9_?.$][\w?.$]*)
119 | name
120 | meta.function.ts
121 |
122 |
123 | include
124 | source.js
125 |
126 |
127 | scopeName
128 | source.ts
129 | uuid
130 | 21e323af-f665-4161-96e7-5087d262557e
131 |
132 |
133 |
--------------------------------------------------------------------------------