diff --git a/AST.fu b/AST.fu index 8cc054a9..84041f8d 100644 --- a/AST.fu +++ b/AST.fu @@ -1276,6 +1276,17 @@ public class FuMethod : FuMethodBase public override bool IsStatic() => this.CallType == FuCallType.Static; public bool IsAbstractOrVirtual() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual; public bool IsAbstractVirtualOrOverride() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual || this.CallType == FuCallType.Override; + public static string CallTypeToString(FuCallType callType) + { + switch (callType) { + case FuCallType.Static: return "static"; + case FuCallType.Abstract: return "abstract"; + case FuCallType.Virtual: return "virtual"; + case FuCallType.Override: return "override"; + case FuCallType.Sealed: return "sealed"; + default: assert false; + } + } public FuVar!? FirstParameter() { assert this.Parameters.First is FuVar! first; // FIXME: FuVar!? diff --git a/Parser.fu b/Parser.fu index b54b771d..1560c945 100644 --- a/Parser.fu +++ b/Parser.fu @@ -945,18 +945,6 @@ public class FuParser : FuLexer method.Body = ParseBlock(method); } - static string CallTypeToString(FuCallType callType) - { - switch (callType) { - case FuCallType.Static: return "static"; - case FuCallType.Abstract: return "abstract"; - case FuCallType.Virtual: return "virtual"; - case FuCallType.Override: return "override"; - case FuCallType.Sealed: return "sealed"; - default: assert false; - } - } - void ReportFormerError(int line, int column, int length, string message) { this.Host.ReportError(this.Host.Program.SourceFiles.Last().Filename, line, column, column + length, message); @@ -964,7 +952,7 @@ public class FuParser : FuLexer void ReportCallTypeError(int line, int column, string kind, FuCallType callType) { - string callTypeString = CallTypeToString(callType); + string callTypeString = FuMethod.CallTypeToString(callType); ReportFormerError(line, column, callTypeString.Length, $"{kind} cannot be {callTypeString}"); } diff --git a/editors/vscode/README.md b/editors/vscode/README.md index bed7b608..e1792993 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -6,6 +6,7 @@ A Visual Studio Code extension for the [Fusion programming language](https://fus * Go to / Peek definition * Go to / Peek / Find all implementations * Go to / Peek / Find all references +* Hovers * Snippets Fusion is a language designed to be translated automatically to diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 1deb562f..1d2509bb 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -19,7 +19,8 @@ // along with Fusion Transpiler. If not, see http://www.gnu.org/licenses/ import * as vscode from "vscode"; -import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost, FuSymbolReferenceVisitor, FuStatement, FuSymbol, FuContainerType, FuEnum, FuClass, FuMember, FuField, FuMethod } from "./fucheck.js"; +import { FuParser, FuProgram, FuSystem, FuSema, FuSemaHost, FuSymbolReferenceVisitor, FuStatement, FuSymbol, FuContainerType, + FuEnum, FuClass, FuMember, FuConst, FuVar, FuParameters, FuField, FuCallType, FuMethod } from "./fucheck.js"; class VsCodeHost extends FuSemaHost { @@ -136,6 +137,63 @@ class VsCodeDiagnostics extends VsCodeHost } } +class VsCodeSymbolLocator extends VsCodeHost +{ + protected async findSymbol(document: vscode.TextDocument, position: vscode.Position): Promise + { + const parser = this.createParser(); + const filename = document.uri.toString(); + parser.findName(filename, position.line, position.character); + const files = await vscode.workspace.findFiles("*.fu"); + if (files.some(uri => uri.toString() == filename)) + await this.parseFolder(files, parser); + else + this.parseDocument(document, parser); + this.doSema(); + return parser.getFoundDefinition(); + } + + static #getSignature(method: FuMethod): string + { + var code = method.callType == FuCallType.NORMAL ? "" : FuMethod.callTypeToString(method.callType) + " "; + code = `${code}${method.type} ${method.name}`; + if (!method.isStatic() && method.isMutator()) + code += "!"; + code += "("; + for (var param = method.firstParameter(); param != null;) { + code = `${code}${param.type} ${param.name}`; + param = param.nextVar(); + if (param == null) + break; + code += ", "; + } + return code + ")"; + } + + async getHover(document: vscode.TextDocument, position: vscode.Position): Promise + { + const symbol = await this.findSymbol(document, position); + if (symbol == null) + return null; + var code = symbol.name; + if (symbol instanceof FuClass) + code = `class ${code}`; + else if (symbol instanceof FuEnum) + code = `enum ${code}`; + else if (symbol instanceof FuConst) + code = `const ${symbol.type} ${code}`; + else if (symbol instanceof FuVar) + code = `(${symbol.parent instanceof FuParameters ? "parameter" : "local variable"}) ${symbol.type} ${code}`; + else if (symbol instanceof FuMethod) + code = VsCodeSymbolLocator.#getSignature(symbol); + else if (symbol instanceof FuField) + code = `(field) ${symbol.type} ${code}`; + else if (symbol instanceof FuMember) // property + code = `${symbol.type} ${code}`; + return new vscode.Hover(new vscode.MarkdownString().appendCodeblock(code, "fusion")); + } +} + class VsCodeReferenceCollector extends FuSymbolReferenceVisitor { provider: VsCodeGotoProvider; @@ -152,24 +210,10 @@ class VsCodeReferenceCollector extends FuSymbolReferenceVisitor } } -class VsCodeGotoProvider extends VsCodeHost +class VsCodeGotoProvider extends VsCodeSymbolLocator { #locations: vscode.Location[] = []; - async #findSymbol(document: vscode.TextDocument, position: vscode.Position): Promise - { - const parser = this.createParser(); - const filename = document.uri.toString(); - parser.findName(filename, position.line, position.character); - const files = await vscode.workspace.findFiles("*.fu"); - if (files.some(uri => uri.toString() == filename)) - await this.parseFolder(files, parser); - else - this.parseDocument(document, parser); - this.doSema(); - return parser.getFoundDefinition(); - } - pushLocation(statement: FuStatement): void { if (statement.loc > 0) { @@ -181,7 +225,7 @@ class VsCodeGotoProvider extends VsCodeHost async findDefinition(document: vscode.TextDocument, position: vscode.Position): Promise { - const symbol = await this.#findSymbol(document, position); + const symbol = await this.findSymbol(document, position); if (symbol != null) this.pushLocation(symbol); return this.#locations; @@ -189,7 +233,7 @@ class VsCodeGotoProvider extends VsCodeHost async findImplementations(document: vscode.TextDocument, position: vscode.Position): Promise { - const symbol = await this.#findSymbol(document, position); + const symbol = await this.findSymbol(document, position); if (symbol != null) { if (symbol instanceof FuClass) { for (const subclass of this.program.classes) { @@ -211,7 +255,7 @@ class VsCodeGotoProvider extends VsCodeHost async findReferences(document: vscode.TextDocument, position: vscode.Position): Promise { - const symbol = await this.#findSymbol(document, position); + const symbol = await this.findSymbol(document, position); if (symbol != null) new VsCodeReferenceCollector(this).findReferences(this.program, symbol); return this.#locations; @@ -266,6 +310,11 @@ export function activate(context: vscode.ExtensionContext): void })); context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => diagnostics.check(e.document))); context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(document => diagnostics.delete(document))); + vscode.languages.registerHoverProvider("fusion", { + provideHover(document, position, token) { + return new VsCodeSymbolLocator().getHover(document, position); + } + }); vscode.languages.registerDefinitionProvider("fusion", { provideDefinition(document, position, token) { return new VsCodeGotoProvider().findDefinition(document, position); diff --git a/editors/vscode/syntaxes/fusion.tmLanguage.json b/editors/vscode/syntaxes/fusion.tmLanguage.json index d2daf898..bd5b4830 100644 --- a/editors/vscode/syntaxes/fusion.tmLanguage.json +++ b/editors/vscode/syntaxes/fusion.tmLanguage.json @@ -12,8 +12,7 @@ "include": "#comment" }, { - "name": "storage.modifier.fu", - "match": "\\bpublic\\b" + "include": "#storage-modifier" }, { "include": "#enum-declaration" @@ -23,6 +22,12 @@ }, { "include": "#native-statement" + }, + { + "include": "#type" + }, + { + "include": "#method-declaration" } ], "repository": { diff --git a/libfut.cpp b/libfut.cpp index e6375acc..ac2df8ee 100644 --- a/libfut.cpp +++ b/libfut.cpp @@ -2352,6 +2352,24 @@ bool FuMethod::isAbstractVirtualOrOverride() const return this->callType == FuCallType::abstract || this->callType == FuCallType::virtual_ || this->callType == FuCallType::override_; } +std::string_view FuMethod::callTypeToString(FuCallType callType) +{ + switch (callType) { + case FuCallType::static_: + return "static"; + case FuCallType::abstract: + return "abstract"; + case FuCallType::virtual_: + return "virtual"; + case FuCallType::override_: + return "override"; + case FuCallType::sealed: + return "sealed"; + default: + std::abort(); + } +} + FuVar * FuMethod::firstParameter() const { FuVar * first = static_cast(this->parameters.first); @@ -4214,24 +4232,6 @@ void FuParser::parseMethod(FuClass * klass, std::shared_ptr method) method->body = parseBlock(method.get()); } -std::string_view FuParser::callTypeToString(FuCallType callType) -{ - switch (callType) { - case FuCallType::static_: - return "static"; - case FuCallType::abstract: - return "abstract"; - case FuCallType::virtual_: - return "virtual"; - case FuCallType::override_: - return "override"; - case FuCallType::sealed: - return "sealed"; - default: - std::abort(); - } -} - void FuParser::reportFormerError(int line, int column, int length, std::string_view message) const { this->host->reportError(this->host->program->sourceFiles.back().filename, line, column, column + length, message); @@ -4239,7 +4239,7 @@ void FuParser::reportFormerError(int line, int column, int length, std::string_v void FuParser::reportCallTypeError(int line, int column, std::string_view kind, FuCallType callType) const { - std::string_view callTypeString = callTypeToString(callType); + std::string_view callTypeString = FuMethod::callTypeToString(callType); reportFormerError(line, column, std::ssize(callTypeString), std::format("{} cannot be {}", kind, callTypeString)); } diff --git a/libfut.cs b/libfut.cs index 8fdbf41f..37e0b0d1 100644 --- a/libfut.cs +++ b/libfut.cs @@ -2873,6 +2873,24 @@ public static FuMethod New(FuClass klass, FuVisibility visibility, FuCallType ca public bool IsAbstractVirtualOrOverride() => this.CallType == FuCallType.Abstract || this.CallType == FuCallType.Virtual || this.CallType == FuCallType.Override; + public static string CallTypeToString(FuCallType callType) + { + switch (callType) { + case FuCallType.Static: + return "static"; + case FuCallType.Abstract: + return "abstract"; + case FuCallType.Virtual: + return "virtual"; + case FuCallType.Override: + return "override"; + case FuCallType.Sealed: + return "sealed"; + default: + throw new NotImplementedException(); + } + } + public FuVar FirstParameter() { FuVar first = (FuVar) this.Parameters.First; @@ -4499,24 +4517,6 @@ void ParseMethod(FuClass klass, FuMethod method) method.Body = ParseBlock(method); } - static string CallTypeToString(FuCallType callType) - { - switch (callType) { - case FuCallType.Static: - return "static"; - case FuCallType.Abstract: - return "abstract"; - case FuCallType.Virtual: - return "virtual"; - case FuCallType.Override: - return "override"; - case FuCallType.Sealed: - return "sealed"; - default: - throw new NotImplementedException(); - } - } - void ReportFormerError(int line, int column, int length, string message) { this.Host.ReportError(this.Host.Program.SourceFiles[^1].Filename, line, column, column + length, message); @@ -4524,7 +4524,7 @@ void ReportFormerError(int line, int column, int length, string message) void ReportCallTypeError(int line, int column, string kind, FuCallType callType) { - string callTypeString = CallTypeToString(callType); + string callTypeString = FuMethod.CallTypeToString(callType); ReportFormerError(line, column, callTypeString.Length, $"{kind} cannot be {callTypeString}"); } diff --git a/libfut.hpp b/libfut.hpp index 63400331..dd9cd7d8 100644 --- a/libfut.hpp +++ b/libfut.hpp @@ -1343,6 +1343,7 @@ class FuMethod : public FuMethodBase bool isStatic() const override; bool isAbstractOrVirtual() const; bool isAbstractVirtualOrOverride() const; + static std::string_view callTypeToString(FuCallType callType); FuVar * firstParameter() const; int getParametersCount() const; const FuMethod * getDeclaringMethod() const; @@ -1659,7 +1660,6 @@ class FuParser : public FuLexer std::shared_ptr parseStatement(); FuCallType parseCallType(); void parseMethod(FuClass * klass, std::shared_ptr method); - static std::string_view callTypeToString(FuCallType callType); void reportFormerError(int line, int column, int length, std::string_view message) const; void reportCallTypeError(int line, int column, std::string_view kind, FuCallType callType) const; void parseClass(std::shared_ptr doc, int line, int column, bool isPublic, FuCallType callType); diff --git a/libfut.js b/libfut.js index 9e5dbdb8..055b899d 100644 --- a/libfut.js +++ b/libfut.js @@ -3020,6 +3020,24 @@ export class FuMethod extends FuMethodBase return this.callType == FuCallType.ABSTRACT || this.callType == FuCallType.VIRTUAL || this.callType == FuCallType.OVERRIDE; } + static callTypeToString(callType) + { + switch (callType) { + case FuCallType.STATIC: + return "static"; + case FuCallType.ABSTRACT: + return "abstract"; + case FuCallType.VIRTUAL: + return "virtual"; + case FuCallType.OVERRIDE: + return "override"; + case FuCallType.SEALED: + return "sealed"; + default: + throw new Error(); + } + } + firstParameter() { let first = this.parameters.first; @@ -4719,24 +4737,6 @@ export class FuParser extends FuLexer method.body = this.#parseBlock(method); } - static #callTypeToString(callType) - { - switch (callType) { - case FuCallType.STATIC: - return "static"; - case FuCallType.ABSTRACT: - return "abstract"; - case FuCallType.VIRTUAL: - return "virtual"; - case FuCallType.OVERRIDE: - return "override"; - case FuCallType.SEALED: - return "sealed"; - default: - throw new Error(); - } - } - #reportFormerError(line, column, length, message) { this.host.reportError(this.host.program.sourceFiles.at(-1).filename, line, column, column + length, message); @@ -4744,7 +4744,7 @@ export class FuParser extends FuLexer #reportCallTypeError(line, column, kind, callType) { - let callTypeString = FuParser.#callTypeToString(callType); + let callTypeString = FuMethod.callTypeToString(callType); this.#reportFormerError(line, column, callTypeString.length, `${kind} cannot be ${callTypeString}`); }