Files
godot-vscode-plugin/src/tool_manager.ts

284 lines
11 KiB
TypeScript

import * as vscode from 'vscode';
import godotRequest from './request';
import GDScriptSymbolProvider from './gdscript/symbolprovider';
import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider';
import GDScriptCompletionItemProvider from './gdscript/completion';
import GDScriptDefinitionProivder from './gdscript/definitionprovider';
import GDScriptHoverProvider from './gdscript/hoverprovider';
import GDScriptDocumentContentProvider from './gdscript/docprovider';
import GDScriptSignatureHelpProvider from './gdscript/signature_helper';
var glob = require("glob")
import config from './config';
import * as path from 'path';
import * as fs from 'fs';
class ToolManager {
private workspaceDir: string = "";
private symbolprovider: GDScriptSymbolProvider = null;
private workspacesymbolprovider: GDScriptWorkspaceSymbolProvider = null;
private _disposable: vscode.Disposable;
private _context: vscode.ExtensionContext;
private _projectFile : string = "project.godot";
private _rootDir : string = "";
private _biuitinDocFile : string = "doc/classes-3.0.json";
constructor(context: vscode.ExtensionContext) {
this._context = context;
this.workspaceDir = vscode.workspace.rootPath;
let completionDollar = false;
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 3.0) < 3) {
this._projectFile = "engine.cfg";
this._biuitinDocFile = "doc/classes-2.1.json";
completionDollar = true;
}
this.loadClasses();
if (vscode.workspace && this.workspaceDir) {
vscode.workspace.registerTextDocumentContentProvider('godotdoc', new GDScriptDocumentContentProvider());
this.workspaceDir = this.workspaceDir.replace(/\\/g, "/");
this._rootDir = vscode.workspace.getConfiguration("GodotTools").get("godotProjectRoot", this.workspaceDir);
this._rootDir = this._rootDir.replace("${workspaceRoot}", this.workspaceDir);
this.loadWorkspaceSymbols();
}
// documentation symbol provider
this.symbolprovider = new GDScriptSymbolProvider();
vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider);
// workspace symbol provider
this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider();
vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
// definition provider
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder(this._rootDir));
// hover provider
vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
// code completion provider
if (completionDollar)
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'", "$");
else
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
// signature help provider
vscode.languages.registerSignatureHelpProvider('gdscript', new GDScriptSignatureHelpProvider(), '(', ',');
// Commands
this._disposable = vscode.Disposable.from(
vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)),
vscode.commands.registerCommand('godot.runWorkspace', () => { this.openWorkspaceWithEditor() }),
vscode.commands.registerCommand('godot.openWithEditor', () => { this.openWorkspaceWithEditor("-e") }),
vscode.commands.registerCommand('godot.runCurrentScene', this.runCurrentScene.bind(this)),
);
}
validate() {
const self = this;
godotRequest({
action: "editor",
command: "projectdir"
}).then((res: any) => {
let path = res.path;
if (path && path.length > 0 && path.endsWith("/"))
path = path.substring(0, path.length - 1)
if (path.toLowerCase() == self.workspaceDir.toLowerCase())
vscode.window.showInformationMessage("Connected to the Godot editor server");
else {
vscode.window.showWarningMessage("The opened project is not the same within the Godot editor");
}
}).catch(e => {
vscode.window.showErrorMessage("Failed connecting to the Godot editor server");
});
}
loadAllSymbols(): Promise < any > {
const self = this;
return new Promise((resolve, reject) => {
glob(self.workspaceDir + "/**/*.gd", (err, files) => {
if (!err) {
const symbols = {};
for (let i = 0; i < files.length; i++)
symbols[config.normalizePath(files[i])] = config.loadSymbolsFromFile(files[i]);
// load autoloads from engin.cfg
const engincfg = path.join(self.workspaceDir, this._projectFile);
if (fs.existsSync(engincfg) && fs.statSync(engincfg).isFile()) {
try {
const script = {
constants: {},
functions: {},
variables: {},
signals: {},
classes: {},
base: "Object",
native: "Object",
constpathes: {},
documents: {},
constvalues: {}
};
let content: string = fs.readFileSync(engincfg, 'utf-8');
if (content && content.indexOf("[autoload]") != -1) {
content = content.substring(content.indexOf("[autoload]") + "[autoload]".length, content.length);
content = content.substring(0, content.indexOf("["));
const lines = content.split(/\r?\n/);
lines.map((l) => {
if (l.indexOf("=") != 0) {
const name = l.substring(0, l.indexOf("="));
let gdpath = l.substring(l.indexOf("res://") + "res://".length, l.indexOf(".gd") + ".gd".length);
gdpath = path.join(this._rootDir, gdpath);
let showgdpath = vscode.workspace.asRelativePath(gdpath);
let doc = "Autoloaded instance of " + `[${showgdpath}](${vscode.Uri.file(gdpath).toString()})`;
doc = doc.replace(/"/g, " ");
script.constants[name] = new vscode.Range(0, 0, 0, 0);
script.constvalues[name] = "autoload";
script.documents[name] = doc;
script.constpathes[name] = gdpath;
}
});
}
symbols["autoload"] = script;
} catch (error) {
console.error(error);
}
}
resolve(symbols);
} else
reject(err);
});
});
}
private loadAllNodesInWorkspace() {
glob(this.workspaceDir + "/**/*.tscn", (err, files) => {
if (!err) {
const symbols = {};
for (let i = 0; i < files.length; i++)
config.loadScene(files[i]);
}
});
}
private loadWorkspaceSymbols() {
let handle = this.showProgress("Loading symbols");
if (vscode.workspace.getConfiguration("GodotTools").get("parseTextScene", false)) {
this.loadAllNodesInWorkspace();
}
this.loadAllSymbols().then(symbols => {
handle();
vscode.window.setStatusBarMessage("$(check) Workspace symbols", 5000);
config.setAllSymbols(symbols);
}).catch(e => {
handle();
vscode.window.setStatusBarMessage("$(x) Workspace symbols", 5000);
});
}
private openWorkspaceWithEditor(params = "") {
let workspaceValid = false
if (this.workspaceDir) {
let cfg = path.join(this._rootDir, this._projectFile);
console.log(cfg);
if (fs.existsSync(cfg) && fs.statSync(cfg).isFile())
workspaceValid = true;
}
if (workspaceValid) {
let pathFlag = "-path";
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 2.1) >= 3)
pathFlag = "--path";
this.runEditor(`${pathFlag} "${this._rootDir}" ${params}`);
}
else
vscode.window.showErrorMessage("Current workspace is not a Godot project");
}
private runEditor(params = "") {
let editorPath = vscode.workspace.getConfiguration("GodotTools").get("editorPath", "")
editorPath = editorPath.replace("${workspaceRoot}", this.workspaceDir);
console.log(editorPath);
if (!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
vscode.window.showErrorMessage("Invalid editor path to run the project");
} else {
let existingTerminal = vscode.window.terminals.find(t => t._name === "GodotTools")
if (existingTerminal) {
existingTerminal.dispose()
}
let terminal = vscode.window.createTerminal("GodotTools");
editorPath = this.escapeCmd(editorPath);
let cmmand = `${editorPath} ${params}`;
terminal.sendText(cmmand, true);
terminal.show();
}
}
private runCurrentScene() {
const absFilePath = vscode.window.activeTextEditor.document.uri.fsPath;
let scenePath = null
if (vscode.window.activeTextEditor) {
scenePath = path.relative(this._rootDir, absFilePath);
scenePath = scenePath.replace(/\\/g, "/");
}
// Run scripts directly which is inhired from SceneTree or MainLoop
if (scenePath.endsWith(".gd")) {
const scriptPath = scenePath;
scenePath = config.scriptSceneMap[config.normalizePath(scenePath)];
if (!scenePath) {
const script = config.loadSymbolsFromFile(absFilePath);
if (script) {
if(script.native == "SceneTree" || script.native == "MainLoop") {
this.runEditor(`-s "${absFilePath}"`);
return;
}
}
}
}
if (scenePath) {
if (scenePath.endsWith(".gd"))
scenePath = ` -s "res://${scenePath}" `;
else
scenePath = ` "res://${scenePath}" `;
this.openWorkspaceWithEditor(scenePath);
} else
vscode.window.showErrorMessage("Current document is not a scene file or MainLoop");
}
loadClasses() {
let done: boolean = false;
if (this.workspaceDir)
done = config.loadClasses(path.join(this.workspaceDir, ".vscode", "classes.json"));
if (!done)
done = config.loadClasses(path.join(this._context.extensionPath, this._biuitinDocFile));
if (!done)
vscode.window.showErrorMessage("Loading GDScript documentation failed");
}
dispose() {
this._disposable.dispose();
}
private showProgress(message: string) {
let r_resolve;
vscode.window.withProgress({ location: vscode.ProgressLocation.Window}, p => {
return new Promise((resolve, reject) => {
p.report({message});
r_resolve = resolve;
});
});
return r_resolve;
}
private escapeCmd(cmd: string) {
// Double quote command (should work in at least cmd.exe and bash)
let cmdEsc = `"${cmd}"`;
// Fetch Windows shell type
let shell = vscode.workspace.getConfiguration("terminal.integrated.shell").get("windows", "");
// For powershell we prepend an & to prevent the command being treated as a string
if (shell.endsWith("powershell.exe") && process.platform === "win32") {
cmdEsc = `&${cmdEsc}`;
}
return cmdEsc
}
};
export default ToolManager;