Просмотр исходного кода

Update haxe.display (#8610)

* [tests] generalize server tests a bit

* [std] steal some types from vshaxe

* [tests] start on server display tests

see #8451

* remove CompilerMetadata type

* bring back methods

* add haxe.display.Server too

* utilize methods for tests
Simon Krajewski 6 лет назад
Родитель
Сommit
8a603bafc0

+ 1 - 0
.gitignore

@@ -127,3 +127,4 @@ tests/benchs/dump/
 tests/display/.unittest/
 tests/unit/.unittest/
 tests/threads/export/
+tests/server/test.js.map

+ 504 - 0
std/haxe/display/Display.hx

@@ -0,0 +1,504 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package haxe.display;
+
+import haxe.display.JsonModuleTypes;
+import haxe.display.Protocol;
+import haxe.display.Position;
+
+/**
+	Methods of the JSON-RPC-based `--display` protocol in Haxe 4.
+	A lot of the methods are *inspired* by the Language Server Protocol, but there is **no** intention to be directly compatible with it.
+**/
+@:publicFields
+class DisplayMethods {
+	/**
+		The completion request is sent from the client to Haxe to request code completion.
+		Haxe automatically determines the type of completion to use based on the passed position, see `CompletionResultKind`.
+	**/
+	static inline var Completion = new HaxeRequestMethod<CompletionParams, CompletionResult>("display/completion");
+
+	/**
+		The request is sent from the client to Haxe to resolve additional information for a given completion item.
+	**/
+	static inline var CompletionItemResolve = new HaxeRequestMethod<CompletionItemResolveParams, CompletionItemResolveResult>("display/completionItem/resolve");
+
+	/**
+		The find references request is sent from the client to Haxe to find locations that reference the symbol at a given text document position.
+	**/
+	static inline var FindReferences = new HaxeRequestMethod<PositionParams, GotoDefinitionResult>("display/references");
+
+	/**
+		The goto definition request is sent from the client to Haxe to resolve the definition location(s) of a symbol at a given text document position.
+	**/
+	static inline var GotoDefinition = new HaxeRequestMethod<PositionParams, GotoDefinitionResult>("display/definition");
+
+	/**
+		The goto type definition request is sent from the client to Haxe to resolve the type definition location(s) of a symbol at a given text document position.
+	**/
+	static inline var GotoTypeDefinition = new HaxeRequestMethod<PositionParams, GotoTypeDefinitionResult>("display/typeDefinition");
+
+	/**
+		The hover request is sent from the client to Haxe to request hover information at a given text document position.
+	**/
+	static inline var Hover = new HaxeRequestMethod<PositionParams, HoverResult>("display/hover");
+
+	/**
+		This request is sent from the client to Haxe to determine the package for a given file, based on class paths configuration.
+	**/
+	static inline var DeterminePackage = new HaxeRequestMethod<FileParams, DeterminePackageResult>("display/package");
+
+	/**
+		The signature help request is sent from the client to Haxe to request signature information at a given cursor position.
+	**/
+	static inline var SignatureHelp = new HaxeRequestMethod<SignatureHelpParams, SignatureHelpResult>("display/signatureHelp");
+
+	/*
+		TODO:
+
+		- finish completion
+		- diagnostics
+		- codeLens
+		- workspaceSymbols ("project/symbol"?)
+	 */
+}
+
+/** Completion **/
+typedef CompletionParams = PositionParams & {
+	var wasAutoTriggered:Bool;
+
+	/** list of metas to include in responses **/
+	var ?meta:Array<String>;
+}
+
+typedef FieldResolution = {
+	/**
+		Whether it's valid to use the unqualified name of the field or not.
+		This is `false` if the identifier is shadowed.
+	**/
+	var isQualified:Bool;
+
+	/**
+		The qualifier that has to be inserted to use the field if `!isQualified`.
+		Can either be `this` or `super` for instance fields for the type name for `static` fields.
+	**/
+	var qualifier:String;
+}
+
+typedef DisplayLocal<T> = {
+	var id:Int;
+	var name:String;
+	var type:JsonType<T>;
+	var origin:LocalOrigin;
+	var capture:Bool;
+	var ?extra:{
+		var params:Array<JsonTypeParameter>;
+		var expr:JsonExpr;
+	};
+	var meta:JsonMetadata;
+	var pos:JsonPos;
+	var isInline:Bool;
+	var isFinal:Bool;
+}
+
+enum abstract LocalOrigin(Int) {
+	var LocalVariable;
+	var Argument;
+	var ForVariable;
+	var PatternVariable;
+	var CatchVariable;
+	var LocalFunction;
+}
+
+enum abstract ClassFieldOriginKind<T>(Int) {
+	/**
+		The field is declared on the current type itself.
+	**/
+	var Self:ClassFieldOriginKind<JsonModuleType<T>>;
+
+	/**
+		The field is a static field brought into context via a static import
+		(`import pack.Module.Type.field`).
+	**/
+	var StaticImport:ClassFieldOriginKind<JsonModuleType<T>>;
+
+	/**
+		The field is declared on a parent type, such as:
+		- a super class field that is not overriden
+		- a forwarded abstract field
+	**/
+	var Parent:ClassFieldOriginKind<JsonModuleType<T>>;
+
+	/**
+		The field is a static extension method brought
+		into context with the `using` keyword.
+	**/
+	var StaticExtension:ClassFieldOriginKind<JsonModuleType<T>>;
+
+	/**
+		This field doesn't belong to any named type, just an anonymous structure.
+	**/
+	var AnonymousStructure:ClassFieldOriginKind<JsonAnon>;
+
+	/**
+		Special fields built into the compiler, such as:
+		- `code` on single-character Strings
+		- `bind()` on functions.
+	**/
+	var BuiltIn:ClassFieldOriginKind<NoData>;
+
+	/**
+		The origin of this class field is unknown.
+	**/
+	var Unknown:ClassFieldOriginKind<NoData>;
+}
+
+typedef ClassFieldOrigin<T> = {
+	var kind:ClassFieldOriginKind<T>;
+	var ?args:T;
+}
+
+typedef ClassFieldOccurrence<T> = {
+	var field:JsonClassField;
+	var resolution:FieldResolution;
+	var ?origin:ClassFieldOrigin<T>;
+}
+
+enum abstract EnumFieldOriginKind<T>(Int) {
+	/**
+		The enum value is declared on the current type itself.
+	**/
+	var Self:EnumFieldOriginKind<JsonModuleType<T>>;
+
+	/**
+		The enum value is brought into context via a static import
+		(`import pack.Module.Enum.Value`).
+	**/
+	var StaticImport:EnumFieldOriginKind<JsonModuleType<T>>;
+}
+
+typedef EnumFieldOrigin<T> = {
+	var kind:EnumFieldOriginKind<T>;
+	var ?args:T;
+}
+
+typedef EnumFieldOccurrence<T> = {
+	var field:JsonEnumField;
+	var resolution:FieldResolution;
+	var ?origin:EnumFieldOrigin<T>;
+}
+
+enum abstract Literal(String) {
+	var Null = "null";
+	var True = "true";
+	var False = "false";
+	var This = "this";
+	var Trace = "trace";
+}
+
+enum abstract DisplayModuleTypeKind(Int) {
+	var Class;
+	var Interface;
+	var Enum;
+	var Abstract;
+	var EnumAbstract;
+
+	/** A `typedef` that is just an alias for another type. **/
+	var TypeAlias;
+
+	/** A `typedef` that is an alias for an anonymous structure. **/
+	var Struct;
+
+	/** A type name introduced by `import as` / `import in` **/
+	// var ImportAlias;
+}
+
+typedef DisplayModuleType = {
+	var path:JsonTypePath;
+	var pos:JsonPos;
+	var isPrivate:Bool;
+	var params:Array<DisplayModuleTypeParameter>;
+	var meta:JsonMetadata;
+	var doc:JsonDoc;
+	var isExtern:Bool;
+	var isFinal:Bool;
+	var kind:DisplayModuleTypeKind;
+}
+
+typedef DisplayModuleTypeParameter = {
+	var name:String;
+	var meta:JsonMetadata;
+	var constraints:Array<JsonType<Dynamic>>;
+}
+
+typedef DisplayLiteral<T> = {
+	var name:String;
+}
+
+enum abstract MetadataTarget(String) {
+	var Class = "TClass";
+	var ClassField = "TClassField";
+	var Abstract = "TAbstract";
+	var AbstractField = "TAbstractField";
+	var Enum = "TEnum";
+	var Typedef = "TTypedef";
+	var AnyField = "TAnyField";
+	var Expr = "TExpr";
+	var TypeParameter = "TTypeParameter";
+}
+
+enum abstract Platform(String) {
+	var Cross = "cross";
+	var Js = "js";
+	var Lua = "lua";
+	var Neko = "neko";
+	var Flash = "flash";
+	var Php = "php";
+	var Cpp = "cpp";
+	var Cs = "cs";
+	var Java = "java";
+	var Python = "python";
+	var Hl = "hl";
+	var Eval = "eval";
+}
+
+typedef Metadata = {
+	var name:String;
+	var doc:JsonDoc;
+	var parameters:Array<String>;
+	var platforms:Array<Platform>;
+	var targets:Array<MetadataTarget>;
+	var internal:Bool;
+}
+
+typedef Keyword = {
+	var name:KeywordKind;
+}
+
+enum abstract KeywordKind(String) to String {
+	var Implements = "implements";
+	var Extends = "extends";
+	var Function = "function";
+	var Var = "var";
+	var If = "if";
+	var Else = "else";
+	var While = "while";
+	var Do = "do";
+	var For = "for";
+	var Break = "break";
+	var Return = "return";
+	var Continue = "continue";
+	var Switch = "switch";
+	var Case = "case";
+	var Default = "default";
+	var Try = "try";
+	var Catch = "catch";
+	var New = "new";
+	var Throw = "throw";
+	var Untyped = "untyped";
+	var Cast = "cast";
+	var Macro = "macro";
+	var Package = "package";
+	var Import = "import";
+	var Using = "using";
+	var Public = "public";
+	var Private = "private";
+	var Static = "static";
+	var Extern = "extern";
+	var Dynamic = "dynamic";
+	var Override = "override";
+	var Class = "class";
+	var Interface = "interface";
+	var Enum = "enum";
+	var Abstract = "abstract";
+	var Typedef = "typedef";
+	var Final = "final";
+	var Inline = "inline";
+}
+
+/* enum abstract PackageContentKind(Int) {
+	var Module;
+	var Package;
+}*/
+typedef Package = {
+	var path:JsonPackagePath;
+	// var ?contents:Array<{name:String, kind:PackageContentKind}>;
+}
+
+typedef Module = {
+	var path:JsonModulePath;
+	// var ?contents:Array<ModuleType>;
+}
+
+enum abstract DisplayItemKind<T>(String) {
+	var Local:DisplayItemKind<DisplayLocal<Dynamic>>;
+	var ClassField:DisplayItemKind<ClassFieldOccurrence<Dynamic>>;
+	var EnumField:DisplayItemKind<EnumFieldOccurrence<Dynamic>>;
+
+	/** Only for the enum values in enum abstracts, other fields use `ClassField`. **/
+	var EnumAbstractField:DisplayItemKind<ClassFieldOccurrence<Dynamic>>;
+
+	var Type:DisplayItemKind<DisplayModuleType>;
+	var Package:DisplayItemKind<Package>;
+	var Module:DisplayItemKind<Module>;
+	var Literal:DisplayItemKind<DisplayLiteral<Dynamic>>;
+	var Metadata:DisplayItemKind<Metadata>;
+	var Keyword:DisplayItemKind<Keyword>;
+	var AnonymousStructure:DisplayItemKind<JsonAnon>;
+	var Expression:DisplayItemKind<JsonTExpr>;
+	var TypeParameter:DisplayItemKind<DisplayModuleTypeParameter>;
+}
+
+typedef DisplayItem<T> = {
+	var kind:DisplayItemKind<T>;
+	var args:T;
+	var ?type:JsonType<Dynamic>;
+}
+
+typedef DisplayItemOccurrence<T> = {
+	var range:Range;
+	var item:DisplayItem<T>;
+	var ?moduleType:JsonModuleType<Dynamic>;
+	var ?moduleTypeFollowed:JsonModuleType<Dynamic>;
+}
+
+typedef FieldCompletionSubject<T> = DisplayItemOccurrence<T> & {
+	var ?iterator:{
+		var type:JsonType<Dynamic>;
+	};
+	var ?keyValueIterator:{
+		var key:JsonType<Dynamic>;
+		var value:JsonType<Dynamic>;
+	};
+}
+
+typedef ToplevelCompletion<T> = {
+	var ?expectedType:JsonType<T>;
+	var ?expectedTypeFollowed:JsonType<T>;
+}
+
+typedef StructExtensionCompletion = {
+	var isIntersectionType:Bool;
+}
+
+typedef PatternCompletion<T> = ToplevelCompletion<T> & {
+	var isOutermostPattern:Bool;
+}
+
+enum abstract CompletionModeKind<T>(Int) {
+	var Field:CompletionModeKind<FieldCompletionSubject<Dynamic>>;
+	var StructureField;
+	var Toplevel:CompletionModeKind<ToplevelCompletion<Dynamic>>;
+	var Metadata;
+	var TypeHint;
+	var Extends;
+	var Implements;
+	var StructExtension:CompletionModeKind<StructExtensionCompletion>;
+	var Import;
+	var Using;
+	var New;
+	var Pattern:CompletionModeKind<PatternCompletion<Dynamic>>;
+	var Override;
+	var TypeRelation;
+	var TypeDeclaration;
+}
+
+typedef CompletionMode<T> = {
+	var kind:CompletionModeKind<T>;
+	var ?args:T;
+}
+
+typedef CompletionResponse<T1, T2> = {
+	var items:Array<DisplayItem<T1>>;
+	var mode:CompletionMode<T2>;
+	var ?replaceRange:Range;
+}
+
+typedef CompletionResult = Response<Null<CompletionResponse<Dynamic, Dynamic>>>;
+
+/** CompletionItem Resolve **/
+typedef CompletionItemResolveParams = {
+	var index:Int;
+};
+
+typedef CompletionItemResolveResult = Response<{
+	var item:DisplayItem<Dynamic>;
+}>;
+
+/** GotoDefinition **/
+typedef GotoDefinitionResult = Response<Array<Location>>;
+
+/** GotoTypeDefinition **/
+typedef GotoTypeDefinitionResult = Response<Array<Location>>;
+
+/** Hover **/
+typedef HoverResult = Response<Null<HoverDisplayItemOccurence<Dynamic>>>;
+
+typedef HoverDisplayItemOccurence<T> = DisplayItemOccurrence<T> & {
+	var ?expected:{
+		var ?type:JsonType<Dynamic>;
+		var ?name:{
+			var name:String;
+			var kind:HoverExpectedNameKind;
+		};
+	};
+}
+
+enum abstract HoverExpectedNameKind(Int) {
+	var FunctionArgument;
+	var StructureField;
+}
+
+/** DeterminePackage **/
+typedef DeterminePackageResult = Response<Array<String>>;
+
+/** SignatureHelp **/
+typedef SignatureHelpParams = PositionParams & {
+	var wasAutoTriggered:Bool;
+}
+
+typedef SignatureInformation = JsonFunctionSignature & {
+	var ?documentation:String;
+}
+
+enum abstract SignatureItemKind(Int) {
+	var Call;
+	var ArrayAccess;
+}
+
+typedef SignatureItem = {
+	var signatures:Array<SignatureInformation>;
+	var activeSignature:Int;
+	var activeParameter:Int;
+	var kind:SignatureItemKind;
+}
+
+typedef SignatureHelpResult = Response<Null<SignatureItem>>;
+
+/** General types **/
+typedef PositionParams = FileParams & {
+	/** Unicode character offset in the file. **/
+	var offset:Int;
+
+	var ?contents:String;
+}

+ 36 - 0
std/haxe/display/DocumentUri.hx

@@ -0,0 +1,36 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package haxe.display;
+
+/**
+	A tagging type for string properties that are actually URIs.
+**/
+abstract DocumentUri(String) {
+	public inline function new(uri:String) {
+		this = uri;
+	}
+
+	public inline function toString() {
+		return this;
+	}
+}

+ 71 - 0
std/haxe/display/FsPath.hx

@@ -0,0 +1,71 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package haxe.display;
+
+using StringTools;
+
+abstract FsPath(String) {
+	static final upperCaseDriveRe = ~/^(\/)?([A-Z]:)/;
+
+	public inline function new(path:String) {
+		this = path;
+	}
+
+	/** ported from VSCode sources **/
+	public function toUri():DocumentUri {
+		var path = this;
+		path = path.replace("\\", "/");
+		if (path.fastCodeAt(0) != "/".code)
+			path = "/" + path;
+
+		var parts = ["file://"];
+
+		if (upperCaseDriveRe.match(path))
+			path = upperCaseDriveRe.matched(1) + upperCaseDriveRe.matched(2).toLowerCase() + upperCaseDriveRe.matchedRight();
+
+		var lastIdx = 0;
+		while (true) {
+			var idx = path.indexOf("/", lastIdx);
+			if (idx == -1) {
+				parts.push(UrlEncoder.urlEncode2(path.substring(lastIdx)));
+				break;
+			}
+			parts.push(UrlEncoder.urlEncode2(path.substring(lastIdx, idx)));
+			parts.push("/");
+			lastIdx = idx + 1;
+		}
+		return new DocumentUri(parts.join(""));
+	}
+
+	public inline function toString():String {
+		return this;
+	}
+}
+
+private class UrlEncoder {
+	public static function urlEncode2(s:String):String {
+		return ~/[!'()*]/g.map(s.urlEncode(), function(re) {
+			return "%" + re.matched(0).fastCodeAt(0).hex();
+		});
+	}
+}

+ 108 - 0
std/haxe/display/Protocol.hx

@@ -0,0 +1,108 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package haxe.display;
+
+import haxe.display.Position;
+
+@:publicFields
+class Methods {
+	/**
+		The initialize request is sent from the client to Haxe to determine the capabilities.
+	**/
+	static inline var Initialize = new HaxeRequestMethod<InitializeParams, InitializeResult>("initialize");
+}
+
+/* Initialize */
+typedef InitializeParams = {
+	final ?supportsResolve:Bool;
+
+	/** dot paths to exclude from readClassPaths / toplevel completion **/
+	final ?exclude:Array<String>;
+}
+
+/**
+	Represents a semantic version, see https://semver.org/.
+**/
+typedef Version = {
+	final major:Int;
+	final minor:Int;
+	final patch:Int;
+	final pre:String;
+	final build:String;
+}
+
+typedef InitializeResult = Response<{
+	final protocolVersion:Version;
+	final haxeVersion:Version;
+	final methods:Array<String>;
+}>;
+
+/* general protocol types */
+typedef Timer = {
+	final name:String;
+	final time:Float;
+	final ?path:String;
+	final ?info:String;
+	final ?calls:Int;
+	final ?percentTotal:Float;
+	final ?percentParent:Float;
+	final ?children:Array<Timer>;
+}
+
+typedef Response<T> = {
+	final ?result:T;
+
+	/** UNIX timestamp at the moment the data was sent. **/
+	final ?timestamp:Float;
+
+	/** Only sent if `--times` is enabled. **/
+	final ?timers:Timer;
+}
+
+typedef FileParams = {
+	var file:FsPath;
+}
+
+abstract HaxeRequestMethod<TParams, TResponse>(String) to String {
+	public inline function new(method:String)
+		this = method;
+}
+
+abstract HaxeNotificationMethod<TParams>(String) to String {
+	public inline function new(method:String)
+		this = method;
+}
+
+typedef HaxeResponseErrorData = Array<{
+	var severity:HaxeResponseErrorSeverity;
+	var ?location:Location;
+	var message:String;
+}>;
+
+enum abstract HaxeResponseErrorSeverity(Int) {
+	var Error = 1;
+	var Warning;
+	var Hint;
+}
+
+enum NoData {}

+ 140 - 0
std/haxe/display/Server.hx

@@ -0,0 +1,140 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package haxe.display;
+
+import haxe.display.JsonModuleTypes;
+import haxe.display.Position;
+import haxe.display.Protocol;
+
+@:publicFields
+class ServerMethods {
+	/**
+		This request is sent from the client to Haxe to explore the class paths. This effectively creates a cache for toplevel completion.
+	**/
+	static inline var ReadClassPaths = new HaxeRequestMethod<NoData, Response<{?files:Int}>>("server/readClassPaths");
+
+	static inline var Configure = new HaxeRequestMethod<ConfigureParams, Response<NoData>>("server/configure");
+	static inline var Invalidate = new HaxeRequestMethod<FileParams, Response<NoData>>("server/invalidate");
+	static inline var Contexts = new HaxeRequestMethod<NoData, Response<Array<HaxeServerContext>>>("server/contexts");
+	static inline var Memory = new HaxeRequestMethod<NoData, Response<HaxeMemoryResult>>("server/memory");
+	static inline var Modules = new HaxeRequestMethod<ContextParams, Response<Array<String>>>("server/modules");
+	static inline var Module = new HaxeRequestMethod<ModuleParams, Response<JsonModule>>("server/module");
+	static inline var Files = new HaxeRequestMethod<ContextParams, Response<Array<JsonServerFile>>>("server/files");
+}
+
+/* Configure */
+typedef ConfigurePrintParams = {
+	var ?addedDirectory:Bool;
+	var ?foundDirectories:Bool;
+	var ?changedDirectories:Bool;
+	var ?modulePathChanged:Bool;
+	var ?notCached:Bool;
+	var ?parsed:Bool;
+	var ?removedDirectory:Bool;
+	var ?reusing:Bool;
+	var ?skippingDep:Bool;
+	var ?unchangedContent:Bool;
+	var ?cachedModules:Bool;
+	var ?arguments:Bool;
+	var ?completion:Bool;
+	var ?defines:Bool;
+	var ?signature:Bool;
+	var ?displayPosition:Bool;
+	var ?stats:Bool;
+	var ?message:Bool;
+	var ?socketMessage:Bool;
+	var ?uncaughtError:Bool;
+	var ?newContext:Bool;
+}
+
+typedef ConfigureParams = {
+	final ?noModuleChecks:Bool;
+	final ?print:ConfigurePrintParams;
+}
+
+/* Contexts */
+typedef HaxeServerContext = {
+	final index:Int;
+	final desc:String;
+	final signature:String;
+	final platform:String;
+	final classPaths:Array<String>;
+	final defines:Array<{key:String, value:String}>;
+}
+
+typedef ModuleId = {
+	final path:String;
+	final sign:String;
+}
+
+typedef JsonModule = {
+	final id:Int;
+	final path:JsonModulePath;
+	final types:Array<JsonTypePath>;
+	final file:String;
+	final sign:String;
+	final dependencies:Array<ModuleId>;
+}
+
+typedef JsonServerFile = {
+	final file:String;
+	final time:Float;
+	final pack:String;
+	final moduleName:Null<String>;
+}
+
+/* Memory */
+typedef HaxeMemoryResult = {
+	final contexts:Array<{
+		final context:Null<HaxeServerContext>;
+		final size:Int;
+		final modules:Array<ModulesSizeResult>;
+	}>;
+	final memory:{
+		final totalCache:Int;
+		final haxelibCache:Int;
+		final parserCache:Int;
+		final moduleCache:Int;
+	}
+}
+
+typedef SizeResult = {
+	final path:String;
+	final size:Int;
+}
+
+typedef ModuleTypeSizeResult = SizeResult & {
+	final fields:Array<SizeResult>;
+}
+
+typedef ModulesSizeResult = SizeResult & {
+	final types:Array<ModuleTypeSizeResult>;
+}
+
+typedef ContextParams = {
+	final signature:String;
+}
+
+typedef ModuleParams = ContextParams & {
+	final path:String;
+}

+ 6 - 0
tests/server/.vscode/launch.json

@@ -1,6 +1,12 @@
 {
 	"version": "0.2.0",
 	"configurations": [
+		{
+			"type": "node",
+			"request": "launch",
+			"name": "Launch Program",
+			"program": "${workspaceFolder}/test.js"
+		},
 		{
 			"name": "Macro",
 			"type": "haxe-eval",

+ 4 - 0
tests/server/src/AsyncMacro.hx

@@ -38,6 +38,10 @@ class AsyncMacro {
 				var e = transformHaxeCalls(el);
 				args.push(macro() -> $e);
 				macro runHaxe($a{args});
+			case macro runHaxeJson($a{args}):
+				var e = transformHaxeCalls(el);
+				args.push(macro() -> $e);
+				macro runHaxeJson($a{args});
 			case _:
 				macro {$e0; ${transformHaxeCalls(el)}};
 		}

+ 33 - 15
tests/server/src/HaxeServerTestCase.hx

@@ -1,4 +1,7 @@
-import haxe.display.JsonModuleTypes.JsonModuleType;
+import haxeserver.HaxeServerRequestResult;
+import haxe.display.JsonModuleTypes;
+import haxe.display.Display;
+import haxe.display.Protocol;
 import haxe.Json;
 import haxeserver.process.HaxeServerProcessNode;
 import haxeserver.HaxeServerAsync;
@@ -13,7 +16,7 @@ class HaxeServerTestCase implements ITest {
 	var server:HaxeServerAsync;
 	var vfs:Vfs;
 	var testDir:String;
-	var storedTypes:Array<JsonModuleType<Any>>;
+	var lastResult:HaxeServerRequestResult;
 	var messages:Array<String> = [];
 	var errorMessages = [];
 	var i:Int = 0;
@@ -30,14 +33,11 @@ class HaxeServerTestCase implements ITest {
 		server.stop();
 	}
 
-	function runHaxe(args:Array<String>, storeTypes = false, done:Void->Void) {
+	function runHaxe(args:Array<String>, done:Void->Void) {
 		messages = [];
 		errorMessages = [];
-		storedTypes = [];
-		if (storeTypes) {
-			args = args.concat(['--display', '{ "method": "typer/compiledTypes", "id": 1 }']);
-		}
 		server.rawRequest(args, null, function(result) {
+			lastResult = result;
 			sendLogMessage(result.stdout);
 			for (print in result.prints) {
 				var line = print.trim();
@@ -46,18 +46,16 @@ class HaxeServerTestCase implements ITest {
 			if (result.hasError) {
 				sendErrorMessage(result.stderr);
 			}
-			if (storeTypes) {
-				storedTypes = try {
-					Json.parse(result.stderr).result.result;
-				} catch (e:Dynamic) {
-					trace(e);
-					[];
-				}
-			}
 			done();
 		}, sendErrorMessage);
 	}
 
+	function runHaxeJson<TParams, TResponse>(args:Array<String>, method:HaxeRequestMethod<TParams, TResponse>, methodArgs:TParams, done:Void->Void) {
+		var methodArgs = {method: method, id: 1, params: methodArgs};
+		args = args.concat(['--display', Json.stringify(methodArgs)]);
+		runHaxe(args, done);
+	}
+
 	function sendErrorMessage(msg:String) {
 		var split = msg.split("\n");
 		for (message in split) {
@@ -95,6 +93,12 @@ class HaxeServerTestCase implements ITest {
 	}
 
 	function getStoredType(typePackage:String, typeName:String) {
+		var storedTypes:Array<JsonModuleType<Any>> = try {
+			Json.parse(lastResult.stderr).result.result;
+		} catch (e:Dynamic) {
+			trace(e);
+			[];
+		}
 		for (type in storedTypes) {
 			if (type.pack.join(".") == typePackage && type.name == typeName) {
 				return type;
@@ -103,6 +107,10 @@ class HaxeServerTestCase implements ITest {
 		return null;
 	}
 
+	function parseCompletion():Array<DisplayItem<Dynamic>> {
+		return Json.parse(lastResult.stderr).result.result.items; // this is retarded
+	}
+
 	function assertSuccess(?p:haxe.PosInfos) {
 		Assert.isTrue(0 == errorMessages.length, p);
 	}
@@ -149,4 +157,14 @@ class HaxeServerTestCase implements ITest {
 			Assert.isTrue(check(type), null, p);
 		}
 	}
+
+	function assertHasCompletion<T>(completion:Array<DisplayItem<T>>, f:DisplayItem<T>->Bool, ?p:haxe.PosInfos) {
+		for (type in completion) {
+			if (f(type)) {
+				Assert.pass();
+				return;
+			}
+		}
+		Assert.fail("No such completion", p);
+	}
 }

+ 21 - 1
tests/server/src/Main.hx

@@ -1,5 +1,9 @@
 using StringTools;
 
+import haxe.display.Display;
+import haxe.display.FsPath;
+import haxe.display.Server;
+
 @:timeout(5000)
 class ServerTests extends HaxeServerTestCase {
 	function testNoModification() {
@@ -60,7 +64,7 @@ class ServerTests extends HaxeServerTestCase {
 		vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
 		var args = ["-main", "Empty", "--no-output", "-java", "java"];
 		runHaxe(args);
-		runHaxe(args, true);
+		runHaxeJson(args, cast "typer/compiledTypes" /* TODO */, {});
 		assertHasField("", "Type", "enumIndex", true);
 	}
 
@@ -129,6 +133,22 @@ class ServerTests extends HaxeServerTestCase {
 		runHaxe(args);
 		assertSuccess();
 	}
+
+	function testSyntaxCache() {
+		vfs.putContent("HelloWorld.hx", getTemplate("HelloWorld.hx"));
+		runHaxeJson(["-cp", "."], ServerMethods.ReadClassPaths, null);
+		vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
+		runHaxeJson([], DisplayMethods.Completion, {file: new FsPath("HelloWorld.hx"), offset: 75, wasAutoTriggered: false});
+		var completion = parseCompletion();
+		assertHasCompletion(completion, module -> switch (module.kind) {
+			case Type: module.args.path.typeName == "HelloWorld";
+			case _: false;
+		});
+		// assertHasCompletion(completion, module -> switch (module.kind) {
+		// 	case Type: module.args.path.typeName == "Empty";
+		// 	case _: false;
+		// });
+	}
 }
 
 class Main {