TypeScript Editor

Topics: General
Nov 21, 2012 at 8:12 AM

Was playing around a bit with the TypeScript language services and wanted to know how difficult it is to actually use them. Turned out that to add things like code completion is not hard at all.

Of course some documentation would be welcome, but even without it it seems to be a straight forward API.

In case any one is interested, I put my TypeScript "Editor" (called tsedit) on GitHub:

https://github.com/jbaron/tsedit

Right now there is support for:

- simple code completion (type <ctrl><space> after a dot).

- syntax highlighting.

It should run on Linux, Windows and OS-X, but I only tested it on Linux. It uses (for now) node-webkit as the render engine and CodeMirror as the editor. But other than some re-factoring of TypeScript harness.ts, the actual glue to make this to work is only a few lines of code. 

P.S Still hoping that MS will release the source code for their playground editor, wink wink nudge nudge.

// Peter

 

Nov 21, 2012 at 11:32 PM

Fabulous!

Can you please elaborate, how did you need to refactor harness and any interesting gotchas on the way? 

Nov 23, 2012 at 9:17 AM

The re-factoring I did was just to use the real language services and not the Shim (a wrapper that wraps the data into a string) that is standard used. My main reason to do so was to better understand what the Language Service API exactly returns and how to use these values appropriate. Typed return types are easier to deal with than strings. Also there were some small issues with the shim (like it used single quotes for JSON strings causing parser to bark), that made it less pleasant to deal with. 

Overall my findings so far:

- The language services provide a simple API to get things like code completion etc working. It is clear that the TS team put some thought and effort in this. In case of more interest, just take a look at languageService.ts to see some of the nice and powerful methods of the ILangguageService interface.

- The real trick is that to make a real good browser-based editor that these things like code completion should run in a separate thread so you don't block the GUI event (right now not the case in my editor). So either use a web worker or perhaps just a server-side component in combination with AJAX. A server-side component (nodejs based of course) seems to be the nicest since than you can easily use all the file IO you want and the editor itself could run in any browser and have not a dependency on node-wekbit. And then a Shim based API is nice to communicate between the browser and the server component.

Since there is no documentation yet, one of the things I have not yet figured out is how to make TS load dependent files that are referred to in the source code (like <reference> and module(".."). 

P.S In case you are running Linux or OS-X and want an IDE with TS support, you could also have a look at the EAP of WebStorm 6.0. I just gave it a try and although not perfect (sometimes it flags things as wrong when they are not), it is for sure pretty impressive what they did in such short time period. But I think in the long term an TS editor written in TS would be nicer.

// Peter

Nov 23, 2012 at 2:28 PM
Edited Nov 23, 2012 at 2:29 PM

About loading dependent files: as far as I can tell, the only code for that is in the compiler, not the harness or language services. Since the compiler source includes direct function calls, we cannot simply import it either, so until that is refactored out, we need to copy some code (CommandLineHost and something like resolve from BatchCompiler). That code then invokes the real resolver (CodeResolver in referenceResolution.ts). Then, since compilation and language service have separate code collections, we need to copy from one to the other after resolution is done.

Good idea to whittle down the harness to essentials, btw (having an interface/type-checking compiler is helpful for that kind of surgery).

If you can wait a few days: I'm working on my own language service client at the moment. It is a simple stdin to stdout server (commands in, info out), so it can easily be integrated into editors (my interest is Vim, but anything scriptable that can cooperate with an asynchronous subprocess should work). This also has the advantage that the expensive stuff (initial load and dependency tracking can take over 5 seconds when using a language service client on itself) only needs to be run once, and subsequent queries (for types and completions etc) are quick.

So far, I'm using getSymbolAtPosition, getTypeAtPosition, getDefinitionAtPosition, getCompletionsAtPosition. For the latter, I seem to get too many completions in non-member mode (not after a dot) - any ideas about that?

Apart from the varying standards of code quality and commenting in the TS sources, the object-oriented patterns make some code look more complex than it really is. For instance, instead of a simple callback, there is a callback class, or instead of a higher-order traversal, there is a visitor pattern, etc. I have to keep reminding myself "this is really simple, just look a little harder"..

Nov 23, 2012 at 3:46 PM

Many many thanks, Claus. I'm going to use that helpful stuff in my investigation.

 

Right now I am trying to put together a simplified unit testing library.

Having a parser a test runner can infer tests from source rather than require people to call some clunky API. And given that pretty much any existing JavaScript is TypeScript, that principle can be applied to run both TS and JS tests. 

Nov 23, 2012 at 9:49 PM

Slightly off-topic, but can anyone tell me how to interpret the result of 'getDefinitionAtPosition', specifically the 'minChar' and 'limChar' properties?

Since the 'unitIndex' seems to point to the source of the definition, I was hoping to get the location of the definition in the source, but I don't yet see how to do that..

Translating via the corresponding 'lineMap' seems to make no sense whatsoever. Is there an API function I'm missing, to support 'jump to definition'?

Nov 23, 2012 at 10:09 PM

Claus, I guess you are right. Than for now the easy solution is just to complete load all ts files in project directory before running the language services. Then I don't have to worry about references being resolved or not.

You server sounds interesting. What are you using as a protocol (I guess JSON is not trivial from Vim)?

Regarding the too many completions, I also noticed that. But when I checked the lib.d.ts file, they were actually defined there. Not sure why the TS team put them in there, but just before the IE10 DOM API section there is a very long list of variables and functions that causes this behavior. Like for all the event handlers like "onplay" there is also defined a variable. If you comment out that list, at least it gets better.

// Peter

Nov 24, 2012 at 4:53 PM

Peter, yes, resolve-then-services is what I'm doing.

Protocol, like everything else, is still in flux, but JSON is the current choice. That should help other clients, and for Vim, I already have to rely on Python for communicating with the TypeScript server as an asynchronous subprocess, so I can check the JSON there, too.

I've put up a current snapshot of my sources as typescript-tools . Lots of work still to do, but the basic pieces are there (see README).

If I'd had this (especially showing types and jumping to definition) when I started, writing it would have been much easier!-)

PS. Completions are on the todo list (I guess those extras in lib.d.ts really exist on the global window, but I'd expect non-member completions to respect the prefix before the cursor position - probably I was not using the API correctly); my problem with 'getDefinitionAtPosition' was just using the wrong lineMap..

Nov 28, 2012 at 5:55 PM

PS. Completions are on the todo list (I guess those extras in lib.d.ts really exist on the global window, but I'd expect non-member completions to respect the prefix before the cursor position - probably I was not using the API correctly); my problem with 'getDefinitionAtPosition' was just using the wrong lineMap..

I've now added completion support, both member and non-member. The trick was to give the TSS server an updated copy of the in-memory source code, when there are unsaved changes (as there will be while one is in the process of completing a chain of property accesses).

I've also tried to use JSON returns more consistently.

So, if you're using Vim, this is already quite useable, and if you're using node-webkit-based CodeMirror or Emacs or whatever, you should be able to communicate with the TSS server as an asynchronous subprocess, similar to what I do for Vim:-)