diff --git a/.haxerc b/.haxerc index b6edbfab..0ea2bfb4 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "569e52e", + "version": "15abdcc", "resolveLibs": "scoped" -} \ No newline at end of file +} diff --git a/src/haxeLanguageServer/Configuration.hx b/src/haxeLanguageServer/Configuration.hx index e00013c2..f28026c9 100644 --- a/src/haxeLanguageServer/Configuration.hx +++ b/src/haxeLanguageServer/Configuration.hx @@ -88,13 +88,13 @@ typedef UserConfig = { var enableDiagnostics:Bool; var enableServerView:Bool; var enableSignatureHelpDocumentation:Bool; + var diagnosticsForAllOpenFiles:Bool; var diagnosticsPathFilter:String; var displayHost:String; var displayPort:EitherType; var buildCompletionCache:Bool; var enableCompletionCacheWarning:Bool; var useLegacyCompletion:Bool; - var populateCacheFromDisplay:Bool; var codeGeneration:CodeGenerationConfig; var exclude:Array; var postfixCompletion:PostfixCompletionConfig; @@ -157,12 +157,12 @@ class Configuration { enableDiagnostics: true, enableServerView: false, enableSignatureHelpDocumentation: true, + diagnosticsForAllOpenFiles: true, diagnosticsPathFilter: "${workspaceRoot}", displayHost: null, displayPort: null, buildCompletionCache: true, enableCompletionCacheWarning: true, - populateCacheFromDisplay: true, useLegacyCompletion: false, codeGeneration: { functions: { diff --git a/src/haxeLanguageServer/features/haxe/DiagnosticsFeature.hx b/src/haxeLanguageServer/features/haxe/DiagnosticsFeature.hx index 1d5cc1c2..71430b6b 100644 --- a/src/haxeLanguageServer/features/haxe/DiagnosticsFeature.hx +++ b/src/haxeLanguageServer/features/haxe/DiagnosticsFeature.hx @@ -1,6 +1,9 @@ package haxeLanguageServer.features.haxe; import haxe.Json; +import haxe.display.Diagnostic; +import haxe.display.Display.DiagnosticsParams; +import haxe.display.Display.DisplayMethods; import haxe.display.JsonModuleTypes; import haxe.ds.BalancedTree; import haxe.io.Path; @@ -13,10 +16,10 @@ import js.Node.setImmediate; import js.node.ChildProcess; import jsonrpc.CancellationToken; import languageServerProtocol.Types.Diagnostic; -import languageServerProtocol.Types.DiagnosticSeverity; import languageServerProtocol.Types.Location; using Lambda; +using haxeLanguageServer.features.haxe.DiagnosticsFeature; class DiagnosticsFeature { public static inline final SortImportsUsingsTitle = "Sort imports/usings"; @@ -30,6 +33,9 @@ class DiagnosticsFeature { final pendingRequests:Map; final errorUri:DocumentUri; + final useJsonRpc:Bool; + final timerName:String; + var haxelibPath:Null; public function new(context:Context) { @@ -38,6 +44,9 @@ class DiagnosticsFeature { pendingRequests = new Map(); errorUri = new FsPath(Path.join([context.workspacePath.toString(), "Error"])).toUri(); + useJsonRpc = context.haxeServer.supports(DisplayMethods.Diagnostics); + timerName = useJsonRpc ? DisplayMethods.Diagnostics : "@diagnostics"; + ChildProcess.exec(context.config.haxelib.executable + " config", (error, stdout, stderr) -> haxelibPath = new FsPath(stdout.trim())); context.languageServerProtocol.onNotification(LanguageServerMethods.RunGlobalDiagnostics, onRunGlobalDiagnostics); @@ -45,16 +54,46 @@ class DiagnosticsFeature { function onRunGlobalDiagnostics(_) { final stopProgress = context.startProgress("Collecting Diagnostics"); - final onResolve = context.startTimer("@diagnostics"); - - context.callDisplay("global diagnostics", ["diagnostics"], null, null, function(result) { - processDiagnosticsReply(null, onResolve, result); - context.languageServerProtocol.sendNotification(LanguageServerMethods.DidRunRunGlobalDiagnostics); - stopProgress(); - }, function(error) { - processErrorReply(null, error); - stopProgress(); - }); + final onResolve = context.startTimer(timerName); + + if (useJsonRpc) { + context.callHaxeMethod(DisplayMethods.Diagnostics, {}, null, result -> { + processDiagnosticsReply(null, onResolve, result); + context.languageServerProtocol.sendNotification(LanguageServerMethods.DidRunRunGlobalDiagnostics); + stopProgress(); + return null; + }, function(error) { + processErrorReply(null, error); + stopProgress(); + }); + } else { + context.callDisplay("global diagnostics", ["diagnostics"], null, null, function(result) { + final data = parseLegacyDiagnostics(result); + if (data == null) { + clearDiagnosticsOnClient(errorUri); + } else { + processDiagnosticsReply(null, onResolve, data); + } + context.languageServerProtocol.sendNotification(LanguageServerMethods.DidRunRunGlobalDiagnostics); + stopProgress(); + }, function(error) { + processErrorReply(null, error); + stopProgress(); + }); + } + } + + function parseLegacyDiagnostics(result:DisplayResult):Null>}>> { + return switch result { + case DResult(s): + try { + Json.parse(s); + } catch (e) { + trace("Error parsing diagnostics response: " + e); + null; + } + case DCancelled: null; + }; } function processErrorReply(uri:Null, error:String) { @@ -108,7 +147,7 @@ class DiagnosticsFeature { final diag = { range: {start: position, end: endPosition}, - severity: DiagnosticSeverity.Error, + severity: languageServerProtocol.Types.DiagnosticSeverity.Error, message: problemMatcher.matched(7) }; publishDiagnostic(targetUri, diag, error); @@ -122,7 +161,7 @@ class DiagnosticsFeature { } final diag = { range: {start: {line: 0, character: 0}, end: {line: 0, character: 0}}, - severity: DiagnosticSeverity.Error, + severity: languageServerProtocol.Types.DiagnosticSeverity.Error, message: problemMatcher.matched(2) }; publishDiagnostic(errorUri, diag, error); @@ -132,22 +171,12 @@ class DiagnosticsFeature { function publishDiagnostic(uri:DocumentUri, diag:Diagnostic, error:String) { context.languageServerProtocol.sendNotification(PublishDiagnosticsNotification.type, {uri: uri, diagnostics: [diag]}); final argumentsMap = diagnosticsArguments[uri] = new DiagnosticsMap(); - argumentsMap.set({code: CompilerError, range: diag.range}, error); + argumentsMap.set({code: DKCompilerError, range: diag.range}, error); } - function processDiagnosticsReply(uri:Null, onResolve:(result:Dynamic, ?debugInfo:String) -> Void, result:DisplayResult) { + function processDiagnosticsReply(uri:Null, onResolve:(result:Dynamic, ?debugInfo:String) -> Void, + data:ReadOnlyArray<{file:haxe.display.FsPath, diagnostics:ReadOnlyArray>}>) { clearDiagnosticsOnClient(errorUri); - final data:Array> = switch result { - case DResult(s): - try { - Json.parse(s); - } catch (e) { - trace("Error parsing diagnostics response: " + e); - return; - } - case DCancelled: - return; - } var count = 0; final sent = new Map(); for (data in data) { @@ -181,7 +210,7 @@ class DiagnosticsFeature { final diag:Diagnostic = { range: range, code: hxDiag.code, - severity: hxDiag.severity, + severity: cast hxDiag.severity, message: hxDiag.kind.getMessage(doc, hxDiag.args, range), data: {kind: hxDiag.kind}, relatedInformation: hxDiag.relatedInformation?.map(rel -> { @@ -192,7 +221,7 @@ class DiagnosticsFeature { message: convertIndentation(rel.message, rel.depth) }) } - if (kind == ReplaceableCode || kind == UnusedImport || diag.message.contains("has no effect") || kind == InactiveBlock) { + if (kind == ReplaceableCode || kind == DKUnusedImport || diag.message.contains("has no effect") || kind == InactiveBlock) { diag.severity = Hint; diag.tags = [Unnecessary]; } @@ -229,23 +258,23 @@ class DiagnosticsFeature { return !PathHelper.matches(path, pathFilter); } - function filterRelevantDiagnostics(diagnostics:Array>):Array> { + function filterRelevantDiagnostics(diagnostics:ReadOnlyArray>):ReadOnlyArray> { // hide regular compiler errors while there's parser errors, they can be misleading final hasProblematicParserErrors = diagnostics.find(d -> switch (d.kind : Int) { - case ParserError: d.args != "Missing ;"; // don't be too strict + case DKParserError: d.args != "Missing ;"; // don't be too strict case _: false; }) != null; if (hasProblematicParserErrors) { diagnostics = diagnostics.filter(d -> switch (d.kind : Int) { - case CompilerError, UnresolvedIdentifier: false; + case DKCompilerError, DKUnresolvedIdentifier: false; case _: true; }); } // hide unused import warnings while there's compiler errors (to avoid false positives) - final hasCompilerErrors = diagnostics.find(d -> d.kind == cast CompilerError) != null; + final hasCompilerErrors = diagnostics.find(d -> d.kind == cast DKCompilerError) != null; if (hasCompilerErrors) { - diagnostics = diagnostics.filter(d -> d.kind != cast UnusedImport); + diagnostics = diagnostics.filter(d -> d.kind != cast DKUnusedImport); } // hide inactive blocks that are contained within other inactive blocks @@ -300,25 +329,62 @@ class DiagnosticsFeature { function invokePendingRequest(uri:DocumentUri, token:CancellationToken) { final doc:Null = context.documents.getHaxe(uri); + if (doc != null) { - final onResolve = context.startTimer("@diagnostics"); - context.callDisplay("@diagnostics", [doc.uri.toFsPath() + "@0@diagnostics"], null, token, result -> { - pendingRequests.remove(uri); - processDiagnosticsReply(uri, onResolve, result); - }, error -> { - pendingRequests.remove(uri); - processErrorReply(uri, error); - }); + final onResolve = context.startTimer(timerName); + if (useJsonRpc) { + var params:DiagnosticsParams = {fileContents: []}; + + if (context.config.user.diagnosticsForAllOpenFiles) { + context.documents.iter(function(doc) { + final path = doc.uri.toFsPath(); + if (doc.languageId == "haxe" && !isPathFiltered(path)) { + params.fileContents.sure().push({file: path, contents: null}); + } + }); + } else { + params.file = doc.uri.toFsPath(); + } + + context.callHaxeMethod(DisplayMethods.Diagnostics, params, token, result -> { + pendingRequests.remove(uri); + processDiagnosticsReply(uri, onResolve, result); + return null; + }, error -> { + pendingRequests.remove(uri); + processErrorReply(uri, error); + }); + } else { + context.callDisplay("@diagnostics", [doc.uri.toFsPath() + "@0@diagnostics"], null, token, result -> { + pendingRequests.remove(uri); + final data = parseLegacyDiagnostics(result); + if (data == null) { + clearDiagnosticsOnClient(errorUri); + } else { + processDiagnosticsReply(null, onResolve, data); + } + }, error -> { + pendingRequests.remove(uri); + processErrorReply(uri, error); + }); + } } else { pendingRequests.remove(uri); } } function cancelPendingRequest(uri:DocumentUri) { - var tokenSource = pendingRequests[uri]; - if (tokenSource != null) { - pendingRequests.remove(uri); - tokenSource.cancel(); + if (useJsonRpc && context.config.user.diagnosticsForAllOpenFiles) { + for (tokenSource in pendingRequests) { + tokenSource.cancel(); + } + pendingRequests.clear(); + } else { + var tokenSource = pendingRequests[uri]; + if (tokenSource != null) { + pendingRequests.remove(uri); + tokenSource.cancel(); + } } } @@ -333,79 +399,22 @@ class DiagnosticsFeature { } } -enum abstract UnresolvedIdentifierSuggestion(Int) { - final Import; - final Typo; -} - -enum abstract MissingFieldCauseKind(String) { - final AbstractParent:MissingFieldCauseKind<{parent:JsonTypePathWithParams}>; - final ImplementedInterface:MissingFieldCauseKind<{parent:JsonTypePathWithParams}>; - final PropertyAccessor:MissingFieldCauseKind<{property:JsonClassField, isGetter:Bool}>; - final FieldAccess:MissingFieldCauseKind<{}>; - final FinalFields:MissingFieldCauseKind<{fields:Array}>; -} - -typedef MissingFieldCause = { - var kind:MissingFieldCauseKind; - var args:T; -} - -typedef MissingField = { - var field:JsonClassField; - var type:JsonType; - - /** - When implementing multiple interfaces, there can be field duplicates among them. This flag is only - true for the first such occurrence of a field, so that the "Implement all" code action doesn't end - up implementing the same field multiple times. - **/ - var unique:Bool; -} - -typedef MissingFieldDiagnostic = { - var fields:Array; - var cause:MissingFieldCause; -} - -typedef MissingFieldDiagnostics = { - var moduleType:JsonModuleType; - var moduleFile:String; - var entries:Array; -} - -typedef ReplaceableCode = { - var description:String; - var range:Range; - var ?newCode:String; -} - -enum abstract DiagnosticKind(Int) from Int to Int { - final UnusedImport:DiagnosticKind; - final UnresolvedIdentifier:DiagnosticKind>; - final CompilerError:DiagnosticKind; - final ReplaceableCode:DiagnosticKind; - final ParserError:DiagnosticKind; - final DeprecationWarning:DiagnosticKind; - final InactiveBlock:DiagnosticKind; - final MissingFields:DiagnosticKind; - - public inline function new(i:Int) { - this = i; - } +class DiagnosticKindHelper { + public static function make(code:Int) + return (code : DiagnosticKind); - public function getMessage(doc:Null, args:T, range:Range) { - return switch (this : DiagnosticKind) { - case UnusedImport: "Unused import/using"; - case UnresolvedIdentifier: + public static function getMessage(dk:DiagnosticKind, doc:Null, args:T, range:Range) { + return switch dk { + case DKUnusedImport: "Unused import/using"; + case DKUnresolvedIdentifier: var message = 'Unknown identifier'; if (doc != null) { message += ' : ${doc.getText(range)}'; } message; - case CompilerError: args.trim(); + case DKCompilerError: args.trim(); case ReplaceableCode: args.description; - case ParserError: args; + case DKParserError: args; case DeprecationWarning: args; case InactiveBlock: "Inactive conditional compilation block"; case MissingFields: diff --git a/src/haxeLanguageServer/features/haxe/codeAction/DiagnosticsCodeActionFeature.hx b/src/haxeLanguageServer/features/haxe/codeAction/DiagnosticsCodeActionFeature.hx index cb72f51d..e39dcb2f 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/DiagnosticsCodeActionFeature.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/DiagnosticsCodeActionFeature.hx @@ -1,5 +1,6 @@ package haxeLanguageServer.features.haxe.codeAction; +import haxe.display.Diagnostic.DiagnosticKind; import haxeLanguageServer.features.haxe.DiagnosticsFeature; import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature; import haxeLanguageServer.features.haxe.codeAction.diagnostics.AddTypeHintActions; @@ -39,13 +40,13 @@ class DiagnosticsCodeActionFeature implements CodeActionContributor { if (kind == null || !(kind is Int)) { // our codes are int, so we don't handle other stuff continue; } - final code = new DiagnosticKind(kind); + final code:DiagnosticKind = DiagnosticKindHelper.make(kind); actions = actions.concat(switch code { - case UnusedImport: UnusedImportActions.createUnusedImportActions(context, params, diagnostic); - case UnresolvedIdentifier: UnresolvedIdentifierActions.createUnresolvedIdentifierActions(context, params, diagnostic); - case CompilerError: CompilerErrorActions.createCompilerErrorActions(context, params, diagnostic); + case DKUnusedImport: UnusedImportActions.createUnusedImportActions(context, params, diagnostic); + case DKUnresolvedIdentifier: UnresolvedIdentifierActions.createUnresolvedIdentifierActions(context, params, diagnostic); + case DKCompilerError: CompilerErrorActions.createCompilerErrorActions(context, params, diagnostic); case ReplaceableCode: ReplaceableCodeActions.createReplaceableCodeActions(context, params, diagnostic); - case ParserError: ParserErrorActions.createParserErrorActions(context, params, diagnostic); + case DKParserError: ParserErrorActions.createParserErrorActions(context, params, diagnostic); case MissingFields: MissingFieldsActions.createMissingFieldsActions(context, params, diagnostic); case _: []; }); diff --git a/src/haxeLanguageServer/features/haxe/codeAction/OrganizeImportsFeature.hx b/src/haxeLanguageServer/features/haxe/codeAction/OrganizeImportsFeature.hx index 615a5278..dc96d974 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/OrganizeImportsFeature.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/OrganizeImportsFeature.hx @@ -187,9 +187,17 @@ class OrganizeImportsFeature { static function organizeImportGroups(doc:HxTextDocument, context:Context, importGroups:Map):Array { var edits:Array = []; + var last = importGroups.get(-1); + importGroups.remove(-1); + for (group in importGroups) { edits = edits.concat(organizeImportGroup(doc, context, group)); } + + if (last != null) { + edits = edits.concat(organizeImportGroup(doc, context, last)); + } + return edits; } diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/CompilerErrorActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/CompilerErrorActions.hx index d4dd340f..9bdd2105 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/CompilerErrorActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/CompilerErrorActions.hx @@ -8,7 +8,7 @@ class CompilerErrorActions { return []; } final actions:Array = []; - final arg = context.diagnostics.getArguments(params.textDocument.uri, CompilerError, diagnostic.range); + final arg = context.diagnostics.getArguments(params.textDocument.uri, DKCompilerError, diagnostic.range); if (arg == null) { return actions; } diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/MissingFieldsActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/MissingFieldsActions.hx index f6a6f2b1..4caf8d51 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/MissingFieldsActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/MissingFieldsActions.hx @@ -1,5 +1,6 @@ package haxeLanguageServer.features.haxe.codeAction.diagnostics; +import haxe.display.Diagnostic.MissingFieldCause; import haxe.display.JsonModuleTypes; import haxe.ds.Option; import haxe.io.Path; diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/OrganizeImportActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/OrganizeImportActions.hx index 2226eaab..2749af25 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/OrganizeImportActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/OrganizeImportActions.hx @@ -1,6 +1,6 @@ package haxeLanguageServer.features.haxe.codeAction.diagnostics; -import haxeLanguageServer.features.haxe.DiagnosticsFeature.DiagnosticKind; +import haxe.display.Diagnostic.DiagnosticKind; import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature; import haxeLanguageServer.features.haxe.codeAction.OrganizeImportsFeature; import haxeLanguageServer.helper.DocHelper; @@ -29,7 +29,7 @@ class OrganizeImportActions { final map = context.diagnostics.getArgumentsMap(uri); final removeUnusedFixes = if (map == null) [] else [ for (key in map.keys()) { - if (key.code == DiagnosticKind.UnusedImport) { + if (key.code == DiagnosticKind.DKUnusedImport) { WorkspaceEditHelper.removeText(DocHelper.untrimRange(doc, key.range)); } } diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/ParserErrorActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/ParserErrorActions.hx index 089d89bd..3e158c30 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/ParserErrorActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/ParserErrorActions.hx @@ -9,7 +9,7 @@ class ParserErrorActions { return []; } final actions:Array = []; - final arg = context.diagnostics.getArguments(params.textDocument.uri, ParserError, diagnostic.range); + final arg = context.diagnostics.getArguments(params.textDocument.uri, DKParserError, diagnostic.range); if (arg == null) { return actions; } diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnresolvedIdentifierActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnresolvedIdentifierActions.hx index a5bf4b8c..4f75801b 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnresolvedIdentifierActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UnresolvedIdentifierActions.hx @@ -9,16 +9,16 @@ class UnresolvedIdentifierActions { if ((params.context.only != null) && (!params.context.only.contains(QuickFix))) { return []; } - final args = context.diagnostics.getArguments(params.textDocument.uri, UnresolvedIdentifier, diagnostic.range); + final args = context.diagnostics.getArguments(params.textDocument.uri, DKUnresolvedIdentifier, diagnostic.range); if (args == null) { return []; } var actions:Array = []; - final importCount = args.count(a -> a.kind == Import); + final importCount = args.count(a -> a.kind == UISImport); for (arg in args) { actions = actions.concat(switch arg.kind { - case Import: createUnresolvedImportActions(context, params, diagnostic, arg, importCount); - case Typo: createTypoActions(context, params, diagnostic, arg); + case UISImport: createUnresolvedImportActions(context, params, diagnostic, arg, importCount); + case UISTypo: createTypoActions(context, params, diagnostic, arg); }); } return actions; diff --git a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UpdateSyntaxActions.hx b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UpdateSyntaxActions.hx index c8a162db..f6cd5a8d 100644 --- a/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UpdateSyntaxActions.hx +++ b/src/haxeLanguageServer/features/haxe/codeAction/diagnostics/UpdateSyntaxActions.hx @@ -1,6 +1,6 @@ package haxeLanguageServer.features.haxe.codeAction.diagnostics; -import haxeLanguageServer.features.haxe.DiagnosticsFeature.DiagnosticKind; +import haxe.display.Diagnostic.DiagnosticKind; import haxeLanguageServer.features.haxe.codeAction.CodeActionFeature; import haxeLanguageServer.helper.DocHelper; import haxeLanguageServer.helper.FormatterHelper; diff --git a/src/haxeLanguageServer/server/HaxeServer.hx b/src/haxeLanguageServer/server/HaxeServer.hx index f37776f3..ca78f20c 100644 --- a/src/haxeLanguageServer/server/HaxeServer.hx +++ b/src/haxeLanguageServer/server/HaxeServer.hx @@ -205,7 +205,6 @@ class HaxeServer { context.callHaxeMethod(ServerMethods.Configure, { noModuleChecks: true, print: context.config.displayServer.print, - populateCacheFromDisplay: context.config.user.populateCacheFromDisplay, legacyCompletion: context.config.user.useLegacyCompletion }, null, _ -> null, function(error) { trace("Error during " + ServerMethods.Configure + " " + error);