mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Checkout new GDScriptLanguageClient branch
This commit is contained in:
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
46
.vscode/launch.json
vendored
46
.vscode/launch.json
vendored
@@ -1,28 +1,24 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/test"],
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch",
|
||||
"env": {
|
||||
"VSCODE_DEBUG_MODE": true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -6,5 +6,6 @@
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
46
.vscode/tasks.json
vendored
46
.vscode/tasks.json
vendored
@@ -1,30 +1,20 @@
|
||||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "0.1.0",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// the command is a shell script
|
||||
"isShellCommand": true,
|
||||
|
||||
// show the output window only if unrecognized errors occur.
|
||||
"showOutput": "silent",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile", "--loglevel", "silent"],
|
||||
|
||||
// The tsc compiler is started in watching mode
|
||||
"isBackground": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch"
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
test/**
|
||||
src/**
|
||||
**/*.map
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/tslint.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017 Geequlim
|
||||
Copyright (c) 2016-2019 Geequlim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -96,7 +96,7 @@
|
||||
},
|
||||
|
||||
"keywords": {
|
||||
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|in|is|return|onready|setget|enum|match|breakpoint|tool|extends|signal|class)\\b",
|
||||
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|in|is|return|onready|setget|enum|match|breakpoint|tool|extends|signal|class|class_name)\\b",
|
||||
"name": "keyword.control.gdscript"
|
||||
},
|
||||
"letter": {
|
||||
|
||||
2
doc/.gitignore
vendored
2
doc/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
classes/*.xml
|
||||
classes-*.xml
|
||||
83763
doc/classes-2.1.json
83763
doc/classes-2.1.json
File diff suppressed because it is too large
Load Diff
90257
doc/classes-3.0.json
90257
doc/classes-3.0.json
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
|
||||
def parseClass(data):
|
||||
dictCls = dict(data.attrib)
|
||||
dictCls['brief_description'] = data.find("brief_description").text.strip()
|
||||
dictCls['description'] = data.find("description").text.strip()
|
||||
dictCls['methods'] = []
|
||||
for m in data.find("methods"):
|
||||
dictCls['methods'].append(parseMethod(m))
|
||||
dictCls['signals'] = []
|
||||
for s in (data.find("signals") if data.find("signals") is not None else []):
|
||||
dictCls['signals'].append(parseMethod(s))
|
||||
dictCls['constants'] = []
|
||||
for c in (data.find("constants") if data.find("constants") is not None else []):
|
||||
dictCls['constants'].append(parseConstant(c))
|
||||
dictCls['properties'] = []
|
||||
for m in (data.find("members") if data.find("members") is not None else []):
|
||||
dictCls['properties'].append(parseProperty(m))
|
||||
dictCls['theme_properties'] = []
|
||||
for thi in (data.find("theme_items") if data.find("theme_items") is not None else []):
|
||||
dictCls['theme_properties'].append(parseProperty(thi))
|
||||
return dictCls
|
||||
|
||||
def parseMethod(data):
|
||||
dictMethod = dict(data.attrib)
|
||||
dictMethod['description'] = data.find("description").text.strip()
|
||||
dictMethod['return_type'] = data.find("return").attrib["type"] if data.find("return") is not None else ""
|
||||
if "qualifiers" not in dictMethod: dictMethod["qualifiers"] = ""
|
||||
dictMethod["arguments"] = []
|
||||
for arg in data.iter('argument'):
|
||||
dictMethod["arguments"].append(parseArgument(arg))
|
||||
return dictMethod
|
||||
|
||||
def parseArgument(data):
|
||||
dictArg = dict(data.attrib)
|
||||
if "dictArg" in dictArg: dictArg.pop("index")
|
||||
dictArg["default_value"] = dictArg["default"] if "default" in dictArg else ""
|
||||
if "default" in dictArg: dictArg.pop("default")
|
||||
return dictArg
|
||||
|
||||
def parseConstant(data):
|
||||
dictConst = dict(data.attrib)
|
||||
dictConst["description"] = data.text.strip()
|
||||
return dictConst
|
||||
|
||||
def parseProperty(data):
|
||||
dictProp = dict(data.attrib)
|
||||
dictProp["description"] = data.text.strip()
|
||||
return dictProp
|
||||
|
||||
def main():
|
||||
if len(sys.argv) >=2 :
|
||||
tree = ET.parse(open(sys.argv[1], 'r'))
|
||||
classes = {}
|
||||
for cls in tree.getroot():
|
||||
dictCls = parseClass(cls)
|
||||
classes[dictCls['name']] = dictCls
|
||||
jsonContent = json.dumps({"classes": classes, "version": "2.1.4"}, ensure_ascii=False, indent=2)
|
||||
print(jsonContent)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
import json
|
||||
import os
|
||||
|
||||
def glob_path(path, pattern):
|
||||
import os, fnmatch
|
||||
result = []
|
||||
for root, _, files in os.walk(path):
|
||||
for filename in files:
|
||||
if fnmatch.fnmatch(filename, pattern):
|
||||
result.append(os.path.join(root, filename))
|
||||
return result
|
||||
|
||||
def parseClass(data):
|
||||
dictCls = dict(data.attrib)
|
||||
dictCls['brief_description'] = data.find("brief_description").text.strip()
|
||||
dictCls['description'] = data.find("description").text.strip()
|
||||
dictCls['methods'] = []
|
||||
for m in data.find("methods"):
|
||||
dictCls['methods'].append(parseMethod(m))
|
||||
dictCls['signals'] = []
|
||||
for s in (data.find("signals") if data.find("signals") is not None else []):
|
||||
dictCls['signals'].append(parseMethod(s))
|
||||
dictCls['constants'] = []
|
||||
for c in (data.find("constants") if data.find("constants") is not None else []):
|
||||
dictCls['constants'].append(parseConstant(c))
|
||||
dictCls['properties'] = []
|
||||
for m in (data.find("members") if data.find("members") is not None else []):
|
||||
dictCls['properties'].append(parseProperty(m))
|
||||
dictCls['theme_properties'] = []
|
||||
for thi in (data.find("theme_items") if data.find("theme_items") is not None else []):
|
||||
dictCls['theme_properties'].append(parseProperty(thi))
|
||||
return dictCls
|
||||
|
||||
def parseMethod(data):
|
||||
dictMethod = dict(data.attrib)
|
||||
dictMethod['description'] = data.find("description").text.strip()
|
||||
dictMethod['return_type'] = data.find("return").attrib["type"] if data.find("return") is not None else ""
|
||||
if "qualifiers" not in dictMethod: dictMethod["qualifiers"] = ""
|
||||
dictMethod["arguments"] = []
|
||||
for arg in data.iter('argument'):
|
||||
dictMethod["arguments"].append(parseArgument(arg))
|
||||
return dictMethod
|
||||
|
||||
def parseArgument(data):
|
||||
dictArg = dict(data.attrib)
|
||||
if "dictArg" in dictArg: dictArg.pop("index")
|
||||
dictArg["default_value"] = dictArg["default"] if "default" in dictArg else ""
|
||||
if "default" in dictArg: dictArg.pop("default")
|
||||
return dictArg
|
||||
|
||||
def parseConstant(data):
|
||||
dictConst = dict(data.attrib)
|
||||
dictConst["description"] = data.text.strip()
|
||||
return dictConst
|
||||
|
||||
def parseProperty(data):
|
||||
dictProp = dict(data.attrib)
|
||||
dictProp["description"] = data.text.strip()
|
||||
return dictProp
|
||||
|
||||
def main():
|
||||
if len(sys.argv) >=2 :
|
||||
if os.path.isdir(sys.argv[1]):
|
||||
classes = {}
|
||||
for f in glob_path(sys.argv[1], "**.xml"):
|
||||
if f.find("/classes/") == -1 and f.find("/doc_classes/") == -1:
|
||||
continue
|
||||
tree = ET.parse(open(f, 'r'))
|
||||
cls = tree.getroot()
|
||||
dictCls = parseClass(cls)
|
||||
classes[dictCls['name']] = dictCls
|
||||
jsonContent = json.dumps({"classes": classes, "version": "3.0.4"}, ensure_ascii=False, indent=2)
|
||||
print(jsonContent)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
2236
package-lock.json
generated
2236
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
242
package.json
242
package.json
@@ -1,157 +1,89 @@
|
||||
{
|
||||
"name": "godot-tools",
|
||||
"displayName": "Godot Tools",
|
||||
"icon": "icon.png",
|
||||
"description": "Tools for game development with godot game engine",
|
||||
"version": "0.3.7",
|
||||
"publisher": "geequlim",
|
||||
"repository": "https://github.com/GodotExplorer/godot-tools",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.1.21"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:engine.cfg",
|
||||
"workspaceContains:project.godot",
|
||||
"onLanguage:gdscript"
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "godot.updateWorkspaceSymbols",
|
||||
"title": "GodotTools: Update workspace symbols"
|
||||
},
|
||||
{
|
||||
"command": "godot.runWorkspace",
|
||||
"title": "GodotTools: Run workspace as Godot project"
|
||||
},
|
||||
{
|
||||
"command": "godot.openWithEditor",
|
||||
"title": "GodotTools: Open workspace with Godot editor"
|
||||
},
|
||||
{
|
||||
"command": "godot.runCurrentScene",
|
||||
"title": "GodotTools: Run current scene"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Godot Tools configuration",
|
||||
"properties": {
|
||||
"GodotTools.maxNumberOfProblems": {
|
||||
"type": "number",
|
||||
"default": 100,
|
||||
"description": "Controls the maximum number of problems produced by the server."
|
||||
},
|
||||
"GodotTools.editorPath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The absolute path to the Godot executable"
|
||||
},
|
||||
"GodotTools.workspaceDocumentWithMarkdown": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Render workspace documentations as Markdown content"
|
||||
},
|
||||
"GodotTools.ignoreIndentedVars": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Only parse variables without indents in GDScript"
|
||||
},
|
||||
"GodotTools.godotVersion": {
|
||||
"type": "number",
|
||||
"default": 3.0,
|
||||
"description": "The Godot version of your project"
|
||||
},
|
||||
"GodotTools.parseTextScene": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Parse a file as a Godot scene when the file name ends with tscn"
|
||||
},
|
||||
"GodotTools.completeNodePath": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Show node paths within a workspace as part of code completion"
|
||||
},
|
||||
"GodotTools.godotProjectRoot": {
|
||||
"type": "string",
|
||||
"default": "${workspaceRoot}",
|
||||
"description": "Your Godot project's directory"
|
||||
},
|
||||
"GodotTools.enableSyntaxChecking": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Turn on/off syntax checking for GDScript"
|
||||
},
|
||||
"GodotTools.lint": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"semicolon": false,
|
||||
"conditionBrackets": false,
|
||||
"unusedSymbols": true
|
||||
},
|
||||
"description": "Lint configuration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "gdscript",
|
||||
"aliases": [
|
||||
"GDScript",
|
||||
"gdscript"
|
||||
],
|
||||
"extensions": [
|
||||
".gd"
|
||||
],
|
||||
"configuration": "./configurations/gdscript-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
"cfg",
|
||||
"tres",
|
||||
"tscn",
|
||||
"godot",
|
||||
"gdns",
|
||||
"gdnlib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"scopeName": "source.gdscript",
|
||||
"path": "./configurations/GDScript.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"snippets": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"path": "./configurations/snippets.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
"compile": "node ./node_modules/typescript/bin/tsc -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.9.4",
|
||||
"mocha": "^5.2.0",
|
||||
"typescript": "^3.0.3",
|
||||
"vscode": "^1.1.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^7.1.1",
|
||||
"vscode-debugprotocol": "^1.17.0",
|
||||
"vscode-debugadapter": "^1.17.0"
|
||||
}
|
||||
"name": "godot-tools",
|
||||
"displayName": "godot-tools",
|
||||
"icon": "icon.png",
|
||||
"version": "1.0.0",
|
||||
"description": "Tools for game development with godot game engine",
|
||||
"repository": "https://github.com/GodotExplorer/godot-tools",
|
||||
"publisher": "geequlim",
|
||||
"engines": {
|
||||
"vscode": "^1.33.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:project.godot",
|
||||
"onLanguage:gdscript"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"scripts": {
|
||||
"vscode:prepublish": "yarn run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "yarn run compile && node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Godot Tools configuration",
|
||||
"properties": {
|
||||
"godot_tools.gdscript_lsp_server_port": {
|
||||
"type": "number",
|
||||
"default": 6008,
|
||||
"description": "The websocket server port of the GDScript Langugae Protocol server"
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "gdscript",
|
||||
"aliases": [
|
||||
"GDScript",
|
||||
"gdscript"
|
||||
],
|
||||
"extensions": [
|
||||
".gd"
|
||||
],
|
||||
"configuration": "./configurations/gdscript-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
"cfg",
|
||||
"tres",
|
||||
"tscn",
|
||||
"godot",
|
||||
"gdns",
|
||||
"gdnlib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"scopeName": "source.gdscript",
|
||||
"path": "./configurations/GDScript.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"snippets": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"path": "./configurations/snippets.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/node": "^10.12.21",
|
||||
"@types/ws": "^6.0.1",
|
||||
"tslint": "^5.16.0",
|
||||
"typescript": "^3.4.5",
|
||||
"vscode": "^1.1.33",
|
||||
"vscode-languageclient": "^5.2.1",
|
||||
"websocket-stream": "^5.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
331
src/config.ts
331
src/config.ts
@@ -1,331 +0,0 @@
|
||||
import GDScriptSymbolParser from './gdscript/symbolparser';
|
||||
import * as fs from 'fs';
|
||||
import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode';
|
||||
|
||||
interface NodeInfo {
|
||||
name: string,
|
||||
type: string,
|
||||
parent: string,
|
||||
instance: string
|
||||
};
|
||||
|
||||
interface CompletionSymbols {
|
||||
classes : CompletionItem[],
|
||||
functions : CompletionItem[],
|
||||
signals : CompletionItem[],
|
||||
constants : CompletionItem[],
|
||||
properties : CompletionItem[],
|
||||
nodes : CompletionItem[],
|
||||
builtinConstants: CompletionItem[]
|
||||
};
|
||||
|
||||
class Config {
|
||||
|
||||
private workspaceSymbols; // filePath: GDScript in symbolparser.ts
|
||||
private builtinCompletions : CompletionSymbols;
|
||||
private builtinClassDoc;
|
||||
public parser: GDScriptSymbolParser;
|
||||
// scriptpath : scenepath
|
||||
public scriptSceneMap: Object;
|
||||
// scenepath : NodeInfo[]
|
||||
public nodeInfoMap: Object;
|
||||
// symbolname: {completionItem: CompletionItem, rowDoc: docdata}
|
||||
public builtinSymbolInfoMap: Object;
|
||||
|
||||
constructor() {
|
||||
this.builtinCompletions = {
|
||||
classes : [],
|
||||
functions : [],
|
||||
signals : [],
|
||||
constants : [],
|
||||
properties : [],
|
||||
nodes : [],
|
||||
builtinConstants: []
|
||||
};
|
||||
this.workspaceSymbols = {};
|
||||
this.builtinSymbolInfoMap = {};
|
||||
this.nodeInfoMap = {};
|
||||
this.scriptSceneMap = {};
|
||||
this.parser = new GDScriptSymbolParser();
|
||||
}
|
||||
|
||||
loadSymbolsFromFile(path) {
|
||||
var ignoreIndentedVars = false;
|
||||
if(workspace)
|
||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
||||
const script = this.parser.parseFile(path, ignoreIndentedVars);
|
||||
this.setSymbols(path, script);
|
||||
return script;
|
||||
}
|
||||
|
||||
setSymbols(path, s) {
|
||||
this.workspaceSymbols[this.normalizePath(path)] = s;
|
||||
}
|
||||
|
||||
getSymbols(path) {
|
||||
return this.workspaceSymbols[this.normalizePath(path)];
|
||||
}
|
||||
|
||||
setAllSymbols(s) {
|
||||
this.workspaceSymbols = s;
|
||||
}
|
||||
|
||||
getAllSymbols() {
|
||||
return this.workspaceSymbols;
|
||||
}
|
||||
|
||||
normalizePath(path) {
|
||||
let newpath = path;
|
||||
if( path.indexOf(":") != -1){
|
||||
let parts = path.split(":");
|
||||
newpath = parts[0].toUpperCase();
|
||||
newpath += ":";
|
||||
for(let i=1; i<parts.length; i++)
|
||||
newpath += parts[i];
|
||||
}
|
||||
newpath = newpath.replace(/\\/g, "/");
|
||||
return newpath;
|
||||
}
|
||||
|
||||
loadClasses(docfile: string): boolean {
|
||||
let done: boolean = false;
|
||||
try {
|
||||
if(fs.existsSync(docfile) && fs.statSync(docfile).isFile()) {
|
||||
const content = fs.readFileSync(docfile, "utf-8");
|
||||
const docdata = JSON.parse(content);
|
||||
if(docdata.classes) {
|
||||
this.builtinClassDoc = docdata.classes;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if(done) {
|
||||
for (let key of Object.keys(this.builtinClassDoc)) {
|
||||
const classdoc = this.builtinClassDoc[key];
|
||||
const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
|
||||
// ---------------------- class -----------------
|
||||
const item: CompletionItem = new CompletionItem(classdoc.name, CompletionItemKind.Class);
|
||||
item.detail = 'Native Class';
|
||||
item.documentation = classdoc.brief_description + " \n" +classdoc.description;
|
||||
this.builtinCompletions.classes.push(item);
|
||||
builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc};
|
||||
// ----------------------- functions -----------------------
|
||||
const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name)=>{
|
||||
const mi = new CompletionItem(m.name, kind);
|
||||
mi.insertText = insertAction(m.name) + (m.arguments.length==0?"()":"");
|
||||
mi.filterText = m.name
|
||||
mi.sortText = m.name
|
||||
mi.detail = m.return_type;
|
||||
let argstr = "";
|
||||
m.arguments.map(arg=>{
|
||||
argstr += `${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}${m.arguments.indexOf(arg)==m.arguments.length-1?'':', '}`;
|
||||
});
|
||||
// mi.label=`${m.name}(${argstr}) ${m.qualifiers}`;
|
||||
let methodName = `${classdoc.name}.${m.name}`;
|
||||
if (classdoc.name == m.name) methodName = m.name;
|
||||
let mdoc = `${m.return_type} ${methodName}(${argstr}) ${m.qualifiers}`;
|
||||
mdoc += " \n\n";
|
||||
mdoc += m.description;
|
||||
mi.documentation = mdoc;
|
||||
if(CompletionItemKind.Interface == kind)
|
||||
this.builtinCompletions.signals.push(mi);
|
||||
else
|
||||
this.builtinCompletions.functions.push(mi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = {completionItem: mi, rowDoc: m};
|
||||
};
|
||||
// methods
|
||||
const methods = classdoc.methods
|
||||
methods.map(m=>parsMethod(m, CompletionItemKind.Method));
|
||||
// signals
|
||||
const signals = classdoc.signals;
|
||||
signals.map(s=>parsMethod(s, CompletionItemKind.Interface));
|
||||
// ------------------------------ constants ---------------------
|
||||
const constants = classdoc.constants;
|
||||
constants.map(c=>{
|
||||
const ci = new CompletionItem(c.name, CompletionItemKind.Enum);
|
||||
ci.detail = c.value;
|
||||
ci.documentation = `${classdoc.name}.${c.name} = ${c.value}`;
|
||||
if(key[0] == "@" || key == "Node" || key == "Control")
|
||||
this.builtinCompletions.builtinConstants.push(ci);
|
||||
else
|
||||
this.builtinCompletions.constants.push(ci);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = {completionItem: ci, rowDoc: c};
|
||||
});
|
||||
// ----------------------- properties -----------------------
|
||||
const parseProp = (p) => {
|
||||
const pi = new CompletionItem(p.name, CompletionItemKind.Property);
|
||||
pi.detail = `${p.type} of ${classdoc.name}`;
|
||||
pi.documentation = p.description;
|
||||
this
|
||||
.builtinCompletions
|
||||
.properties
|
||||
.push(pi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = {
|
||||
completionItem: pi,
|
||||
rowDoc: p
|
||||
};
|
||||
};
|
||||
// properties
|
||||
const properties = classdoc.properties;
|
||||
properties.map(p=>parseProp(p));
|
||||
// theme_properties
|
||||
const theme_properties = classdoc.theme_properties;
|
||||
theme_properties.map(p=>parseProp(p));
|
||||
}
|
||||
}
|
||||
return done;
|
||||
};
|
||||
|
||||
getWorkspaceCompletionItems(script_files = []) : CompletionSymbols {
|
||||
const symbols = {
|
||||
classes: [],
|
||||
functions: [],
|
||||
signals: [],
|
||||
constants: [],
|
||||
properties: [],
|
||||
nodes: [],
|
||||
builtinConstants: []
|
||||
};
|
||||
if (script_files.length == 0)
|
||||
script_files = Object.keys(this.workspaceSymbols);
|
||||
for (let path of script_files) {
|
||||
const script = this.workspaceSymbols[path];
|
||||
if (workspace) {
|
||||
const root = this.normalizePath(workspace.rootPath) + "/";
|
||||
if (path.startsWith(root))
|
||||
path = path.replace(root, "");
|
||||
}
|
||||
const addScriptItems = (items, kind: CompletionItemKind, kindName:string = "Symbol", insertText = (n)=>n)=>{
|
||||
const _items: CompletionItem[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
const signature = (script.signatures && script.signatures[name])?script.signatures[name]:"";
|
||||
const cvalue = (script.constvalues && script.constvalues[name])?script.constvalues[name]:"";
|
||||
const item = new CompletionItem(name+signature, kind);
|
||||
item.sortText = name;
|
||||
item.filterText = name;
|
||||
item.detail = cvalue;
|
||||
item.insertText = insertText(name) + (signature=="()"?"()":"");
|
||||
item.documentation = (script.documents && script.documents[name])?script.documents[name]+"\r\n":"";
|
||||
item.documentation += `${kindName} defined in ${path}`;
|
||||
_items.push(item);
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
|
||||
symbols.classes = [ ...(symbols.classes), ...(addScriptItems(script.classes, CompletionItemKind.Class, "Class"))]
|
||||
symbols.functions = [ ...(symbols.functions), ...(addScriptItems(script.functions, CompletionItemKind.Method, "Method"))]
|
||||
symbols.signals = [ ...(symbols.signals), ...(addScriptItems(script.signals, CompletionItemKind.Interface, "Signal"))]
|
||||
symbols.properties = [ ...(symbols.properties), ...(addScriptItems(script.variables, CompletionItemKind.Variable, "Variable"))]
|
||||
symbols.constants = [ ...(symbols.constants), ...(addScriptItems(script.constants, CompletionItemKind.Enum, "Constant"))]
|
||||
|
||||
if(script.enumerations)
|
||||
symbols.constants = [...(symbols.constants), ...(addScriptItems(script.enumerations, CompletionItemKind.Enum, "Enumeration"))];
|
||||
}
|
||||
|
||||
if(workspace.getConfiguration("GodotTools").get("completeNodePath", false)) {
|
||||
const addSceneNodes = ()=>{
|
||||
const _items: CompletionItem[] = [];
|
||||
for (let scnenepath of Object.keys(this.nodeInfoMap)) {
|
||||
const nodes: NodeInfo[] = this.nodeInfoMap[scnenepath];
|
||||
nodes.map((n=>{
|
||||
const item = new CompletionItem(n.name, CompletionItemKind.Reference);
|
||||
item.detail = n.type;
|
||||
item.documentation = `${n.parent}/${n.name} in ${scnenepath}`;
|
||||
_items.push(item);
|
||||
|
||||
const fullitem = new CompletionItem(`${n.parent}/${n.name}`, CompletionItemKind.Reference);
|
||||
fullitem.detail = n.type;
|
||||
fullitem.filterText = n.name;
|
||||
fullitem.sortText = n.name;
|
||||
fullitem.documentation = `${n.parent}/${n.name} in ${scnenepath}`;
|
||||
_items.push(fullitem);
|
||||
}));
|
||||
}
|
||||
return _items;
|
||||
};
|
||||
symbols.nodes = [...(symbols.nodes), ...(addSceneNodes())];
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
loadScene(scenePath: string) {
|
||||
if(fs.existsSync(scenePath) && fs.statSync(scenePath).isFile()) {
|
||||
try {
|
||||
const content: string = fs.readFileSync(scenePath, 'utf-8');
|
||||
if(content) {
|
||||
// extern resources
|
||||
const exteres = {};
|
||||
let reg = /ext_resource path="res:\/\/(.*)" type="(.*)" id=(\d+)/g;
|
||||
let match = reg.exec(content);
|
||||
while (match != null) {
|
||||
const path = match[1];
|
||||
const type = match[2];
|
||||
const id = match[3];
|
||||
exteres[id] = {path, type};
|
||||
if (type == "Script") {
|
||||
let workspacescenepath = scenePath;
|
||||
if(workspace)
|
||||
workspacescenepath = workspace.asRelativePath(scenePath);
|
||||
this.scriptSceneMap[path] = workspacescenepath;
|
||||
}
|
||||
match = reg.exec(content);
|
||||
}
|
||||
// nodes
|
||||
const nodes: NodeInfo[] = [];
|
||||
reg = /node\s+name="(.*)"\s+type="(.*)"\s+parent="(.*)"/g;
|
||||
match = reg.exec(content);
|
||||
while (match != null) {
|
||||
nodes.push({
|
||||
name : match[1],
|
||||
type : match[2],
|
||||
parent : match[3],
|
||||
instance: ""
|
||||
});
|
||||
match = reg.exec(content);
|
||||
}
|
||||
// packed scenes
|
||||
reg = /node name="(.*)" parent="(.*)" instance=ExtResource\(\s*(\d+)\s*\)/g;
|
||||
match = reg.exec(content);
|
||||
while (match != null) {
|
||||
const id = match[3];
|
||||
nodes.push({
|
||||
name : match[1],
|
||||
type : exteres[id].type,
|
||||
parent : match[2],
|
||||
instance: exteres[id].path
|
||||
});
|
||||
match = reg.exec(content);
|
||||
}
|
||||
if(workspace)
|
||||
scenePath = workspace.asRelativePath(scenePath);
|
||||
this.nodeInfoMap[scenePath] = nodes;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClass(name: string) {
|
||||
return this.builtinClassDoc[name];
|
||||
}
|
||||
|
||||
getBuiltinCompletions() {
|
||||
return this.builtinCompletions;
|
||||
}
|
||||
|
||||
getBuiltinClassNameList() {
|
||||
let namelist = null;
|
||||
if (this.builtinClassDoc)
|
||||
namelist = Object.keys(this.builtinClassDoc);
|
||||
if(!namelist)
|
||||
namelist = [];
|
||||
return namelist;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default new Config();
|
||||
@@ -1,162 +0,0 @@
|
||||
// import {
|
||||
// DebugSession,
|
||||
// InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent, Event,
|
||||
// Thread, StackFrame, Scope, Source, Handles, Breakpoint
|
||||
// } from 'vscode-debugadapter';
|
||||
// import {DebugProtocol} from 'vscode-debugprotocol';
|
||||
// import * as fs from 'fs';
|
||||
// import * as path from 'path';
|
||||
// const cmd = require('node-cmd');
|
||||
|
||||
// export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
// godot: string;
|
||||
// projectDir: string;
|
||||
// runWithEditor: boolean;
|
||||
// params: string[];
|
||||
// }
|
||||
|
||||
// class GodotDebugSession extends DebugSession {
|
||||
|
||||
// // we don't support multiple threads, so we can use a hardcoded ID for the default thread
|
||||
// private static THREAD_ID = 1;
|
||||
|
||||
// /**
|
||||
// * Creates a new debug adapter that is used for one debug session.
|
||||
// * We configure the default implementation of a debug adapter here.
|
||||
// */
|
||||
// public constructor() {
|
||||
// super();
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * The 'initialize' request is the first request called by the frontend
|
||||
// * to interrogate the features the debug adapter provides.
|
||||
// */
|
||||
// protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
|
||||
// // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
|
||||
// // we request them early by sending an 'initializeRequest' to the frontend.
|
||||
// // The frontend will end the configuration sequence by calling 'configurationDone' request.
|
||||
// this.sendEvent(new InitializedEvent());
|
||||
|
||||
// // This debug adapter implements the configurationDoneRequest.
|
||||
// response.body.supportsConfigurationDoneRequest = true;
|
||||
|
||||
// // make VS Code to use 'evaluate' when hovering over source
|
||||
// response.body.supportsEvaluateForHovers = true;
|
||||
|
||||
// // make VS Code to show a 'step back' button
|
||||
// response.body.supportsStepBack = true;
|
||||
|
||||
// this.log("initializeRequest");
|
||||
// this.log_err("initializeRequest");
|
||||
// this.log_console("initializeRequest");
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
// for(let key of Object.keys(args))
|
||||
// this.log(`${key} : ${args[key]}`);
|
||||
// let workspaceValid = false
|
||||
// if(args.godot && fs.existsSync(args.godot) && fs.statSync(args.godot).isFile() ) {
|
||||
// if(args.projectDir && fs.existsSync(args.projectDir) && fs.statSync(args.projectDir).isDirectory() ) {
|
||||
// let cfg = path.join(args.projectDir, "engine.cfg");
|
||||
// if( fs.existsSync(cfg) && fs.statSync(cfg).isFile())
|
||||
// workspaceValid = true;
|
||||
// }
|
||||
// }
|
||||
// if(workspaceValid) {
|
||||
// let params = `-path ${args.projectDir} `;
|
||||
// if(args.runWithEditor)
|
||||
// params += "-e";
|
||||
// if(args.params) {
|
||||
// for(let p of args.params)
|
||||
// params += " " + p;
|
||||
// }
|
||||
// let cmdcontent = `${args.godot} ${params}`;
|
||||
// this.log(cmdcontent)
|
||||
// // TODO: print outputs in terminal console
|
||||
// cmd.run(cmdcontent);
|
||||
// this.sendEvent(new TerminatedEvent());
|
||||
// }
|
||||
// else {
|
||||
// this.log_err("Invalidate path of projectDir or godot:");
|
||||
// this.log_err(JSON.stringify(args, null, '\t'));
|
||||
// this.sendEvent(new TerminatedEvent());
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
||||
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||
|
||||
// // return the default thread
|
||||
// response.body = {
|
||||
// threads: [
|
||||
// new Thread(GodotDebugSession.THREAD_ID, "thread 1")
|
||||
// ]
|
||||
// };
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line.
|
||||
// */
|
||||
// protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
||||
// this.sendEvent(new TerminatedEvent());
|
||||
// }
|
||||
|
||||
// protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Fire StoppedEvent if line is not empty.
|
||||
// */
|
||||
// private fireStepEvent(response: DebugProtocol.Response, ln: number): boolean {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// private log(msg: string) {
|
||||
// const e = new OutputEvent(msg, "stdout");
|
||||
// this.sendEvent(e);
|
||||
// }
|
||||
// private log_err(msg: string) {
|
||||
// const e = new OutputEvent(msg, "stderr");
|
||||
// this.sendEvent(e);
|
||||
// }
|
||||
// private log_console(msg: string) {
|
||||
// const e = new OutputEvent(msg, "console");
|
||||
// this.sendEvent(e);
|
||||
// }
|
||||
// }
|
||||
|
||||
// DebugSession.run(GodotDebugSession);
|
||||
@@ -1,13 +1,16 @@
|
||||
'use strict';
|
||||
import { workspace, Disposable, ExtensionContext } from 'vscode';
|
||||
import WindowWatch from "./window_watcher";
|
||||
import ToolManager from './tool_manager';
|
||||
import { ExtensionContext } from "vscode";
|
||||
import GDScriptLanguageClient from "./lsp/GDScriptLanguageClient";
|
||||
|
||||
let tool: ToolManager = null;
|
||||
let client: GDScriptLanguageClient = null;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
tool = new ToolManager(context);
|
||||
context.subscriptions.push(tool);
|
||||
context.subscriptions.push(new WindowWatch());
|
||||
console.log("[GodotTools]: Extension Activated");
|
||||
client = new GDScriptLanguageClient();
|
||||
context.subscriptions.push(client.start());
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> {
|
||||
if (client) {
|
||||
return client.stop();
|
||||
}
|
||||
return new Promise((resolve, reject) => {});
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import {
|
||||
CompletionItemProvider,
|
||||
Position,
|
||||
TextDocument,
|
||||
CancellationToken,
|
||||
CompletionItem,
|
||||
CompletionList,
|
||||
languages,
|
||||
Disposable,
|
||||
CompletionItemKind
|
||||
} from 'vscode';
|
||||
|
||||
import requestGodot from '../request';
|
||||
import config from '../config';
|
||||
|
||||
interface CompleteRequest {
|
||||
path: string,
|
||||
text: string,
|
||||
cursor: {
|
||||
row: number,
|
||||
column: number
|
||||
}
|
||||
}
|
||||
|
||||
interface CompletionResult {
|
||||
suggestions: string[],
|
||||
hint: string,
|
||||
prefix: string,
|
||||
valid: boolean
|
||||
}
|
||||
|
||||
class GDScriptCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
private get_previous_flag(document : TextDocument, position : Position): string {
|
||||
const line = document.lineAt(position).text;
|
||||
let res = "";
|
||||
for (let index = position.character; index >= 0; index--) {
|
||||
res = line[index];
|
||||
if (['.', '$', '"', "'"].indexOf(res) != -1 )
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
provideCompletionItems(document : TextDocument, position : Position, token : CancellationToken) : CompletionItem[] | Thenable < CompletionItem[] > | CompletionList | Thenable < CompletionList > {
|
||||
|
||||
const lastFlag = this.get_previous_flag(document, position);
|
||||
const builtins = config.getBuiltinCompletions();
|
||||
|
||||
let items:CompletionItem[] = [...(builtins.builtinConstants)];
|
||||
if(!lastFlag || lastFlag.trim().length == 0) {
|
||||
const workspaces = config.getWorkspaceCompletionItems([config.normalizePath(document.fileName)]);
|
||||
items = [
|
||||
...items,
|
||||
...(workspaces.functions),
|
||||
...(workspaces.classes),
|
||||
...(workspaces.constants),
|
||||
...(workspaces.properties),
|
||||
...(builtins.functions),
|
||||
...(builtins.classes),
|
||||
...(builtins.constants),
|
||||
]
|
||||
}
|
||||
else {
|
||||
const workspaces = config.getWorkspaceCompletionItems();
|
||||
if(lastFlag.trim() == ".") {
|
||||
items = [
|
||||
...items,
|
||||
...(workspaces.functions),
|
||||
...(workspaces.constants),
|
||||
...(workspaces.properties),
|
||||
...(workspaces.classes),
|
||||
...(builtins.functions),
|
||||
...(builtins.constants),
|
||||
...(builtins.properties)
|
||||
]
|
||||
}
|
||||
else if(lastFlag.trim() == "'" || lastFlag.trim() == '"') {
|
||||
items = [
|
||||
...items,
|
||||
...(workspaces.signals),
|
||||
...(workspaces.functions),
|
||||
...(workspaces.properties),
|
||||
...(builtins.signals),
|
||||
...(builtins.functions),
|
||||
...(builtins.properties),
|
||||
...(workspaces.nodes),
|
||||
]
|
||||
}
|
||||
else if(lastFlag.trim() == "$") {
|
||||
items = [ ...(workspaces.nodes) ]
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
resolveCompletionItem(item : CompletionItem, token : CancellationToken) : CompletionItem | Thenable < CompletionItem > {
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptCompletionItemProvider;
|
||||
@@ -1,91 +0,0 @@
|
||||
import {
|
||||
DefinitionProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
Definition,
|
||||
Location,
|
||||
workspace,
|
||||
Uri,
|
||||
Range
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import {isStr, getSelectedContent, getStrContent} from './utils';
|
||||
|
||||
class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
private _rootFolder : string = "";
|
||||
|
||||
constructor(rootFolder: string) {
|
||||
this._rootFolder = rootFolder;
|
||||
}
|
||||
|
||||
provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Definition | Thenable < Definition > {
|
||||
const getDefinitions = (content: string):Location[]| Location => {
|
||||
if(content.startsWith("res://")) {
|
||||
content = content.replace("res://", "");
|
||||
if(workspace && workspace.rootPath) {
|
||||
content = path.join(this._rootFolder, content);
|
||||
}
|
||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
||||
}
|
||||
else if(fs.existsSync(content) && fs.statSync(content).isFile()) {
|
||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
||||
}
|
||||
else {
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let locations: Location[] = [];
|
||||
// check from workspace
|
||||
for (let path of Object.keys(workspaceSymbols)) {
|
||||
const script = workspaceSymbols[path];
|
||||
if(path == "autoload" && script.constpathes && script.constpathes[content])
|
||||
path = script.constpathes[content];
|
||||
let scriptitems: Location[] = [];
|
||||
const checkDifinition = (items)=>{
|
||||
const _items: Location[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
if(name == content) {
|
||||
_items.push(new Location(Uri.file(path), items[name]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.variables)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.constants)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.functions)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.signals)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.classes)];
|
||||
if(script.enumerations)
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.enumerations)];
|
||||
locations = [...locations, ...scriptitems];
|
||||
}
|
||||
// check from builtin
|
||||
if(config.getClass(content) != null) {
|
||||
const uri = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${content}`)));
|
||||
locations.push(new Location(Uri.parse(uri), new Range(0,0,0,0)));
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let selStr = getSelectedContent(document, position);
|
||||
if(selStr) {
|
||||
// For strings
|
||||
if(isStr(selStr)) {
|
||||
selStr = getStrContent(selStr);
|
||||
let fpath = path.join(path.dirname(document.uri.fsPath), selStr)
|
||||
if(fs.existsSync(fpath) && fs.statSync(fpath).isFile())
|
||||
selStr = fpath
|
||||
}
|
||||
resolve(getDefinitions(selStr));
|
||||
}
|
||||
else
|
||||
reject(new Error("Empty selection"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptDefinitionProivder;
|
||||
@@ -1,194 +0,0 @@
|
||||
import requestGodot from "../request";
|
||||
import * as vscode from 'vscode';
|
||||
import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
interface GDParseError {
|
||||
message : string,
|
||||
column : number,
|
||||
row : number
|
||||
}
|
||||
|
||||
interface GDScript {
|
||||
members : {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {}
|
||||
},
|
||||
base : string,
|
||||
errors : GDParseError[],
|
||||
valid : boolean,
|
||||
is_tool : boolean,
|
||||
native : string
|
||||
}
|
||||
|
||||
interface ParseRequest {
|
||||
text : string,
|
||||
path : string
|
||||
}
|
||||
|
||||
class GDScriptDiagnosticSeverity {
|
||||
private _subscription : DiagnosticCollection;
|
||||
|
||||
constructor() {
|
||||
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._subscription.dispose()
|
||||
}
|
||||
|
||||
async validateScript(doc : vscode.TextDocument, script : any) {
|
||||
if (doc.languageId == 'gdscript') {
|
||||
if (script) {
|
||||
let diagnostics = [ ...(this.validateExpression(doc)), ...(this.validateUnusedSymbols(doc, script)) ];
|
||||
this._subscription.set(doc.uri, diagnostics);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private validateUnusedSymbols(doc : vscode.TextDocument, script) {
|
||||
let diagnostics = [];
|
||||
let cfg : any = vscode.workspace.getConfiguration("GodotTools").get("lint");
|
||||
if (!cfg.unusedSymbols)
|
||||
return diagnostics
|
||||
|
||||
const text = doc.getText();
|
||||
const check = (name : string, range : vscode.Range) => {
|
||||
var matchs = text.match(new RegExp(`([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
||||
let count = matchs ? matchs.length : 0;
|
||||
var incomment = text.match(new RegExp(`#.*?([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
||||
count -= incomment ? incomment.length : 0;
|
||||
if (count <= 1)
|
||||
diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning));
|
||||
};
|
||||
// Unused variables
|
||||
for (let key of Object.keys(script.variables))
|
||||
check(key, script.variables[key]);
|
||||
for (let key of Object.keys(script.constants))
|
||||
check(key, script.constants[key]);
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private validateExpression(doc : vscode.TextDocument) {
|
||||
let cfg : any = vscode.workspace.getConfiguration("GodotTools").get("lint");
|
||||
|
||||
let diagnostics = [];
|
||||
let expectEndOfLine = false;
|
||||
const text = doc.getText();
|
||||
const lines = text.split(/\r?\n/);
|
||||
lines.map((line : string, i : number) => {
|
||||
let matchstart = /[^\s]+.*/.exec(line);
|
||||
let curLineStartAt = 0;
|
||||
if (matchstart)
|
||||
curLineStartAt = matchstart.index;
|
||||
|
||||
// ignore comments
|
||||
if (line.match(/^\s*#.*/) || line.match(/^#.*/))
|
||||
return
|
||||
// normalize line content
|
||||
line = "\t" + line + "\t";
|
||||
var range = new vscode.Range(i, curLineStartAt, i, line.length);
|
||||
|
||||
if (cfg.semicolon && line.match(/[^#].*?\;/) && !line.match(/[#].*?\;/)) {
|
||||
const semicolonIndex = line.indexOf(';');
|
||||
diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex + 1), "Statement contains a semicolon.", DiagnosticSeverity.Warning));
|
||||
}
|
||||
if (line.match(/[^#].*?/) && expectEndOfLine) {
|
||||
if (!line.match(/.*?(\\|\:)/)) {
|
||||
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
||||
expectEndOfLine = false;
|
||||
}
|
||||
if (line.match(/.*?\:/))
|
||||
expectEndOfLine = false;
|
||||
}
|
||||
const colonKeywords = /\b(if|elif|else|for|while|func|class|match)\b/;
|
||||
let keywords = line.match(colonKeywords)
|
||||
if (keywords) {
|
||||
if(line.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || line.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?\'`)))
|
||||
return
|
||||
if(line.match(new RegExp(`.*?#.*?\\s${keywords[1]}\\s.*?`)))
|
||||
return
|
||||
if(line.match(/.*?\sif\s+(\!|\[|\{|\w|").*?\s+else\s+[^\s]+/))
|
||||
return
|
||||
if (line.match(/.*?\\/))
|
||||
expectEndOfLine = true;
|
||||
else if (line.match(/.*?\:[\s+]+[^#\s]+/))
|
||||
return
|
||||
else if (!line.match(/.*?(\\|\:)/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
||||
else if (line.match(/\s(if|elif|while|func|class|match)\s*\:/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Indentifier expected before ':'", DiagnosticSeverity.Error));
|
||||
else if (line.match(/[^\w]for[^\w]/) && !line.match(/\s+for\s\w+\s+in\s+|[\w+]|\{.*?\}|\[.*?\]|\(.*?\)/)){
|
||||
if(!(line.match(/".*?for.*?"/) || line.match(/'.*?for.*?'/)))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
|
||||
}
|
||||
else if (cfg.conditionBrackets && line.match(/\s(if|elif|while|match)\s*\(.*\)\s*:\s*$/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Extra brackets in condition expression.", DiagnosticSeverity.Warning));
|
||||
const blockIndetCheck = function() {
|
||||
const err = new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error);
|
||||
if (i < lines.length - 1) {
|
||||
let next = i + 1;
|
||||
let nextline = lines[next];
|
||||
// changes nextline until finds a line containg text or comes to the last line
|
||||
while (((!nextline || !nextline.trim().length) || nextline.match(/^\s*#/)) && next < lines.length - 1) {
|
||||
++next;
|
||||
nextline = lines[next];
|
||||
}
|
||||
let nextLineStartAt = -1;
|
||||
let match = /[^\s]+.*/.exec(nextline);
|
||||
if (match)
|
||||
nextLineStartAt = match.index;
|
||||
|
||||
if (nextLineStartAt <= curLineStartAt)
|
||||
diagnostics.push(err);
|
||||
}
|
||||
else if(line.match(/\:\s*$/))
|
||||
diagnostics.push(err);
|
||||
};
|
||||
if(!expectEndOfLine)
|
||||
blockIndetCheck();
|
||||
}
|
||||
// Do not check : for end of statement as it breaks match statment
|
||||
let endOfStateMentWithComma = false;
|
||||
if(endOfStateMentWithComma && !line.match(colonKeywords) && line.match(/\:\s*$/)) {
|
||||
let showErr = true;
|
||||
if( i >= 1 ) {
|
||||
let previous = i - 1;
|
||||
let previousline = lines[previous];
|
||||
while(previousline.match(/\\\s*$/) && previous>=1) {
|
||||
--previous;
|
||||
const ppreviousline = lines[previous];
|
||||
if(ppreviousline.match(/\\\s*$/))
|
||||
previousline = ppreviousline;
|
||||
}
|
||||
const keywords = previousline.match(colonKeywords);
|
||||
if(keywords && !(previousline.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || previousline.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?'`)) ))
|
||||
showErr = false
|
||||
}
|
||||
if(showErr)
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Expected end of statement after expression", DiagnosticSeverity.Error));
|
||||
}
|
||||
if (line.match(/(if|elif|while|return)\s+\w+\s*=\s*\w+/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Assignment in condition or return expressions", DiagnosticSeverity.Warning));
|
||||
else if (line.indexOf("==") > 0 && !line.match(/\:\s*/)) {
|
||||
const endAt = line.indexOf("==");
|
||||
const precontent = line.substring(0, endAt);
|
||||
if (!precontent.match(/\s(if|elif|while|return)\s/) && !precontent.match(/=[^=]/) && !precontent.match(/assert\s*\(/) && !expectEndOfLine) {
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Unhandled comparation expression contains", DiagnosticSeverity.Warning));
|
||||
}
|
||||
}
|
||||
let match = /var\s+(\w+)\s*=\s*(\w+)/.exec(line);
|
||||
if (match && match.length > 2 && match[1].length > 0 && match[1] == match[2]) {
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Self Assignment may cause error.", DiagnosticSeverity.Warning));
|
||||
}
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptDiagnosticSeverity;
|
||||
@@ -1,325 +0,0 @@
|
||||
import {TextDocumentContentProvider, DocumentLinkProvider, Uri, CancellationToken } from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
const linkStyle = `
|
||||
<style>
|
||||
a { color: #6e8ae7; text-decoration: none;}
|
||||
</style>
|
||||
`;
|
||||
|
||||
function genLink(title:string, uri:string, span=true):string {
|
||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
||||
let link = `<a href="${u}">${title}</a>`;
|
||||
if(span)
|
||||
link = `<span>${link}</span>`;
|
||||
return link;
|
||||
};
|
||||
|
||||
function getProp(rawDoc:any, propname: string, action=(s :string)=>s): string {
|
||||
let prop = rawDoc[propname];
|
||||
return action(prop);
|
||||
}
|
||||
|
||||
class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide textual content for a given uri.
|
||||
*
|
||||
* The editor will use the returned string-content to create a readonly
|
||||
* [document](TextDocument). Resources allocated should be released when
|
||||
* the corresponding document has been [closed](#workspace.onDidCloseTextDocument).
|
||||
*
|
||||
* @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.
|
||||
* @param token A cancellation token.
|
||||
* @return A string or a thenable that resolves to such.
|
||||
*/
|
||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
|
||||
const request = uri.authority;
|
||||
let classname = request;
|
||||
let membername = null;
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
if(request.indexOf(".") != -1) {
|
||||
classname = request.substring(0, request.indexOf("."));
|
||||
if(!request.endsWith("."))
|
||||
membername = request.substring(request.indexOf(".")+1, request.length);
|
||||
}
|
||||
if(classname.length >= 1) {
|
||||
for(let key of config.getBuiltinClassNameList()) {
|
||||
if(key.toLowerCase() == classname) {
|
||||
classname = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(classname && classname.length > 0) {
|
||||
if(membername && membername.length >0 )
|
||||
resolve(self.genMemberDoc(classname, membername)) ;
|
||||
else
|
||||
resolve(self.genClassDoc(config.getClass(classname)));
|
||||
}
|
||||
reject(new Error("Open Documentation Failed!"));
|
||||
});
|
||||
}
|
||||
|
||||
format_documentation(text: string): string {
|
||||
let doc = text.replace(/\[code\]/g, "<code>").replace(/\[\/code\]/g, "</code>");
|
||||
doc = doc.replace(/\[codeblock\]/g, '<pre><code class="gdscript">').replace(/\[\/codeblock]/g, "</code></pre>");
|
||||
doc = doc.replace(/\[i\]/g, "<i>").replace(/\[\/i\]/g, "</i>");
|
||||
doc = doc.replace(/\[b\]/g, "<b>").replace(/\[\/b\]/g, "</b>");
|
||||
doc = doc.replace(/\[u\]/g, "<u>").replace(/\[\/u\]/g, "</u>");
|
||||
doc = doc.replace(/\n\t\t\t\t/g, "\n\t");
|
||||
return doc;
|
||||
};
|
||||
|
||||
genMethodDoc(mDoc:any):string {
|
||||
let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
|
||||
if(type.length > 0)
|
||||
return `${genLink(type,type)} `;
|
||||
else
|
||||
return "void";
|
||||
});
|
||||
let args = "";
|
||||
for(let arg of mDoc.arguments){
|
||||
if(mDoc.arguments.indexOf(arg)!=0)
|
||||
args += ", ";
|
||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
var docContent = mDoc.description;
|
||||
if (!docContent) {
|
||||
docContent = `There is currently no description for this method. Please help us by <span><a href="http://docs.godotengine.org/en/latest/community/contributing/updating_the_class_reference.html">contributing one</a></span>!`;
|
||||
}
|
||||
let doc = `
|
||||
<li>
|
||||
<h4 id="${mDoc.name}">${ret_type} ${mDoc.name} (${args}) <i>${mDoc.qualifiers}</i></h4>
|
||||
<p>${docContent}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genPropHeader(mDoc:any, classname:string): string {
|
||||
let type = getProp(mDoc, "type", (type:string):string => `${genLink(type,type)} `);
|
||||
return `<li>${type} ${genLink(mDoc.name, classname+"."+mDoc.name)}</li>`;
|
||||
}
|
||||
|
||||
genMethodHeader(mDoc:any, classname:string):string {
|
||||
let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
|
||||
if(type.length > 0)
|
||||
return `${genLink(type,type)} `;
|
||||
else
|
||||
return "void";
|
||||
});
|
||||
let args = "";
|
||||
for(let arg of mDoc.arguments){
|
||||
if(mDoc.arguments.indexOf(arg)!=0)
|
||||
args += ", ";
|
||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
let doc = `
|
||||
<li>
|
||||
${ret_type} ${genLink(mDoc.name, classname+"."+mDoc.name)} (${args}) <i>${mDoc.qualifiers}</i>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
genPropDoc(pDoc:any): string {
|
||||
let setter = pDoc.setter;
|
||||
if(setter) setter = `<li>Setter: ${setter}(value)</li>`; else setter = "";
|
||||
let getter = pDoc.getter;
|
||||
if(getter) getter = `<li>Getter: ${getter}()</li>`; else getter = "";
|
||||
let descContent = pDoc.description;
|
||||
if(!descContent) {
|
||||
descContent = `There is currently no description for this property. Please help us by <span><a href="http://docs.godotengine.org/en/latest/community/contributing/updating_the_class_reference.html">contributing one</a></span>!`;
|
||||
}
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${genLink(pDoc.type,pDoc.type)} ${pDoc.name}</h4>
|
||||
<ul>
|
||||
${setter}
|
||||
${getter}
|
||||
</ul>
|
||||
<p>${descContent}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genConstDoc(cDoc:any): string {
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${cDoc.name} = ${cDoc.value}</h4>
|
||||
<p>${cDoc.description}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genMemberDoc(classname, membername): string {
|
||||
let realDoc = null;
|
||||
const classdoc = config.getClass(classname);
|
||||
if(!classdoc)
|
||||
return null;
|
||||
for(let m of classdoc.methods) {
|
||||
if(m.name.toLowerCase() == membername) {
|
||||
realDoc = this.genMethodDoc(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!realDoc) {
|
||||
for(let s of classdoc.signals) {
|
||||
if(s.name.toLowerCase() == membername) {
|
||||
realDoc = this.genMethodDoc(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let c of classdoc.constants) {
|
||||
if(c.name.toLowerCase() == membername) {
|
||||
realDoc = this.genConstDoc(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let p of classdoc.properties) {
|
||||
if(p.name.toLowerCase() == membername) {
|
||||
realDoc = this.genPropDoc(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let p of classdoc.theme_properties) {
|
||||
if(p.name.toLowerCase() == membername) {
|
||||
realDoc = this.genPropDoc(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc)
|
||||
return null;
|
||||
|
||||
let doc = `
|
||||
${linkStyle}
|
||||
<h2>Documentation of ${genLink(classname, classname)}.${membername}</h2>
|
||||
<ul>${realDoc}</ul>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genClassDoc(rawDoc): string {
|
||||
if(!rawDoc)
|
||||
return null;
|
||||
const classname = rawDoc.name;
|
||||
let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{
|
||||
if (!inherits) return "";
|
||||
return "<h4>Inherits: " + genLink(inherits, inherits, true) +"</h4>";
|
||||
});
|
||||
|
||||
let category = getProp(rawDoc, "category", (c:string)=>{
|
||||
return "<h4>Category: " + c +"</h4>";
|
||||
});
|
||||
|
||||
let subclasses = "";
|
||||
for(let key of config.getBuiltinClassNameList()) {
|
||||
let c = config.getClass(key);
|
||||
if(c && c.inherits == classname) {
|
||||
subclasses += genLink(key, key, true) + " "
|
||||
}
|
||||
};
|
||||
if(subclasses && subclasses.length > 0)
|
||||
subclasses = "<h3>Inherited by</h3> " + "<ul><li>" + subclasses + "</li></ul>";
|
||||
|
||||
let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{
|
||||
return dec;
|
||||
});
|
||||
let descript = getProp(rawDoc, "description", (dec:string)=>{
|
||||
if(dec)
|
||||
return "<h3>Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
||||
else
|
||||
return "";
|
||||
});
|
||||
|
||||
const setter_getters = {};
|
||||
let propHeaders = ""
|
||||
for(let p of rawDoc.properties) {
|
||||
propHeaders += this.genPropHeader(p, classname);
|
||||
if(p.setter)
|
||||
setter_getters[p.setter] = true;
|
||||
if(p.getter)
|
||||
setter_getters[p.getter] = true;
|
||||
}
|
||||
if(propHeaders.length >0)
|
||||
propHeaders = `<h3>Member List</h3><ul>${propHeaders}</ul/>`;
|
||||
|
||||
let methodHeaders = ""
|
||||
let methods = "";
|
||||
for(let m of rawDoc.methods) {
|
||||
if(setter_getters[m.name]) continue;
|
||||
methodHeaders += this.genMethodHeader(m, classname);
|
||||
methods += this.genMethodDoc(m);
|
||||
}
|
||||
if(methodHeaders.length >0)
|
||||
methodHeaders = `<h3>Public Methods</h3><ul>${methodHeaders}</ul/>`;
|
||||
if(methods.length >0 )
|
||||
methods = `<h3>Public Methods</h3><ul>${methods}</ul/>`;
|
||||
|
||||
let signals = "";
|
||||
for(let s of rawDoc.signals) {
|
||||
signals += this.genMethodDoc(s);
|
||||
}
|
||||
if(signals.length >0 )
|
||||
signals = `<h3>Signals</h3><ul>${signals}</ul/>`;
|
||||
|
||||
let props = "";
|
||||
for(let p of rawDoc.properties) {
|
||||
props += this.genPropDoc(p)
|
||||
}
|
||||
for(let p of rawDoc.theme_properties) {
|
||||
props += this.genPropDoc(p)
|
||||
}
|
||||
if(props.length >0 )
|
||||
props = `<h3>Properties</h3><ul>${props}</ul>`
|
||||
|
||||
let constants = "";
|
||||
for(let c of rawDoc.constants) {
|
||||
constants += this.genConstDoc(c);
|
||||
}
|
||||
if(constants.length >0 )
|
||||
constants = `<h3>Constants</h3><ul>${constants}</ul>`
|
||||
|
||||
let doc = `
|
||||
${linkStyle}
|
||||
<h1>Native Class ${classname}</h1>
|
||||
<h4>${briefDescript}</h4>
|
||||
<p>${category}</p>
|
||||
<p>${inherits}</p>
|
||||
<p>${subclasses}</p>
|
||||
<p>${descript}</p>
|
||||
<p>${propHeaders}</p>
|
||||
<p>${methodHeaders}</p>
|
||||
<p>${signals}</p>
|
||||
<p>${constants}</p>
|
||||
<p>${props}</p>
|
||||
<p>${methods}</p>
|
||||
`;
|
||||
return this.format_documentation(doc);
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptDocumentContentProvider;
|
||||
@@ -1,195 +0,0 @@
|
||||
import {
|
||||
HoverProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
Hover,
|
||||
MarkdownString,
|
||||
workspace,
|
||||
Uri,
|
||||
CompletionItem,
|
||||
CompletionItemKind
|
||||
} from 'vscode';
|
||||
import {
|
||||
isStr,
|
||||
getSelectedContent,
|
||||
getStrContent
|
||||
} from './utils';
|
||||
import config from '../config';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
function genLink(title:string, uri:string):string {
|
||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
||||
return `[${title}](${u})`;
|
||||
};
|
||||
|
||||
class GDScriptHoverProvider implements HoverProvider {
|
||||
constructor() {}
|
||||
|
||||
provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover | Thenable < Hover > {
|
||||
let hoverText = getSelectedContent(document, position);
|
||||
if (isStr(hoverText))
|
||||
hoverText = getStrContent(hoverText);
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let tips: MarkdownString[] = [];
|
||||
const withMarkdwon = workspace.getConfiguration("GodotTools").get("workspaceDocumentWithMarkdown", false);
|
||||
|
||||
|
||||
const makeMarkdown = (content): MarkdownString => {
|
||||
let md = new MarkdownString(content);
|
||||
md.isTrusted = true;
|
||||
return md;
|
||||
}
|
||||
|
||||
// check from workspace
|
||||
const genWorkspaceTips = ()=> {
|
||||
for (let filepath of Object.keys(workspaceSymbols)) {
|
||||
const script = workspaceSymbols[filepath];
|
||||
let scriptips: MarkdownString[] = [];
|
||||
const getHoverText = (items, type, gdpath): MarkdownString[] => {
|
||||
const _items: MarkdownString[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
if (name == hoverText) {
|
||||
let dfile = gdpath;
|
||||
if (workspace) {
|
||||
const root = config.normalizePath(workspace.rootPath) + "/";
|
||||
if (gdpath.startsWith(root))
|
||||
dfile = gdpath.replace(root, "");
|
||||
}
|
||||
let signature = "";
|
||||
if(type == "func"|| type == "signal" && script.signatures[name])
|
||||
signature = script.signatures[name];
|
||||
if(type == "const" && script.constvalues[name])
|
||||
signature = ` = ${script.constvalues[name]}`;
|
||||
let doc ='```gdscript\n' + `${type} ${name}${signature}` + '\n```\n';
|
||||
let rowDoc = script.documents[name];
|
||||
if(!withMarkdwon)
|
||||
rowDoc += "```plaintext\r\n"+rowDoc+"\r\n```";
|
||||
doc += rowDoc;
|
||||
doc = doc?doc+"\r\n\r\n":"";
|
||||
if(gdpath != "autoload")
|
||||
doc += `*Defined in [${dfile}](${Uri.file(gdpath).toString()})*`;
|
||||
_items.push(makeMarkdown(doc));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
scriptips = [...scriptips, ...getHoverText(script.variables, 'var', filepath)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.constants, 'const', filepath)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', filepath)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', filepath)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', filepath)];
|
||||
if(script.enumerations)
|
||||
scriptips = [...scriptips, ...getHoverText(script.enumerations, 'const', filepath)];
|
||||
tips = [...tips, ...scriptips];
|
||||
}
|
||||
};
|
||||
|
||||
// check from scnes
|
||||
const genNodePathTips = ()=> {
|
||||
for (let scnenepath of Object.keys(config.nodeInfoMap)) {
|
||||
const nodes: any[] = config.nodeInfoMap[scnenepath];
|
||||
for (let index = 0; index < nodes.length; index++) {
|
||||
const node:any = nodes[index];
|
||||
const fullpath = node.parent + "/" + node.name;
|
||||
if(fullpath == hoverText || fullpath.endsWith(hoverText)) {
|
||||
let filepath = scnenepath;
|
||||
if(workspace && workspace.rootPath)
|
||||
filepath = path.join(workspace.rootPath, filepath);
|
||||
let instance = "";
|
||||
if(node.instance && node.instance.length > 1) {
|
||||
let instancepath = node.instance;
|
||||
if(workspace && workspace.rootPath)
|
||||
instancepath = path.join(workspace.rootPath, instancepath);
|
||||
instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
|
||||
}
|
||||
tips = [...tips,
|
||||
makeMarkdown(`${genLink(node.type, node.type)} ${fullpath}`),
|
||||
makeMarkdown(`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`)
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const format_documentation = (text, cls="") => {
|
||||
let doc = text.replace(/\[code\]/g, "`").replace(/\[\/code\]/g, "`");
|
||||
doc = doc.replace(/\[codeblock\]/g, "\n```gdscript\n").replace(/\[\/codeblock]/g, "\n```");
|
||||
doc = doc.replace(/\[i\]/g, "*").replace(/\[\/i\]/g, "*");
|
||||
doc = doc.replace(/\[b\]/g, "**").replace(/\[\/b\]/g, "**");
|
||||
doc = doc.replace(/\[u\]/g, "__").replace(/\[\/u\]/g, "__");
|
||||
doc = doc.replace(/\n\t\t\t\t/g, "\n");
|
||||
return doc;
|
||||
};
|
||||
|
||||
// check from builtin
|
||||
const genBuiltinTips = ()=> {
|
||||
const item2MarkdStrings = (name: string,item: CompletionItem, rowDoc: any):MarkdownString => {
|
||||
let value = "";
|
||||
let doc = format_documentation(item.documentation);
|
||||
// get class name
|
||||
let classname = name;
|
||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
||||
if(matchs) {
|
||||
classname = matchs[0];
|
||||
if(classname.endsWith("."))
|
||||
classname = classname.substring(0, classname.length -1);
|
||||
}
|
||||
|
||||
const genMethodMarkDown = ():string =>{
|
||||
let content = `${genLink(rowDoc.return_type, rowDoc.return_type)} `;
|
||||
if (rowDoc.name != classname) content += `${genLink(classname, classname)}.`;
|
||||
let args = "";
|
||||
for(let arg of rowDoc.arguments){
|
||||
if(rowDoc.arguments.indexOf(arg)!=0)
|
||||
args += ", ";
|
||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
content += `${genLink(rowDoc.name, classname+'.' + rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
|
||||
return content;
|
||||
};
|
||||
|
||||
switch (item.kind) {
|
||||
case CompletionItemKind.Class:
|
||||
return makeMarkdown(`Native Class ${genLink(classname, classname)}\n${doc}`);
|
||||
case CompletionItemKind.Method:
|
||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
||||
return makeMarkdown(`${genMethodMarkDown()}\n${doc}`);
|
||||
case CompletionItemKind.Interface:
|
||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
||||
return makeMarkdown(`signal ${genMethodMarkDown()}\n${doc}`);
|
||||
case CompletionItemKind.Variable:
|
||||
case CompletionItemKind.Property:
|
||||
return makeMarkdown(`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)}\n${doc}`);
|
||||
case CompletionItemKind.Enum:
|
||||
return makeMarkdown(`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)} = ${rowDoc.value}\n${doc}`);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return makeMarkdown(`${name} ${doc}`);
|
||||
};
|
||||
for (let name of Object.keys(config.builtinSymbolInfoMap)) {
|
||||
const pattern = `[A-z@_]+[A-z0-9_]*\\.${hoverText}\\b`;
|
||||
if(name == hoverText || name.match(new RegExp(pattern))) {
|
||||
const item: {completionItem: CompletionItem, rowDoc: any} = config.builtinSymbolInfoMap[name];
|
||||
tips = [...tips, item2MarkdStrings(name, item.completionItem, item.rowDoc)];
|
||||
}
|
||||
}
|
||||
};
|
||||
genBuiltinTips();
|
||||
genWorkspaceTips();
|
||||
genNodePathTips();
|
||||
|
||||
if (tips.length > 0)
|
||||
return new Hover(tips);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptHoverProvider;
|
||||
@@ -1,109 +0,0 @@
|
||||
import {
|
||||
SignatureHelpProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
SignatureInformation,
|
||||
SignatureHelp,
|
||||
CompletionItemKind,
|
||||
ParameterInformation,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import config from '../config';
|
||||
import { countSubStr } from './utils';
|
||||
class GDScriptSignatureHelpProvider implements SignatureHelpProvider {
|
||||
constructor() {}
|
||||
|
||||
provideSignatureHelp(document : TextDocument, position : Position, token : CancellationToken) : SignatureHelp | Thenable < SignatureHelp > {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
const res = self.do_provideSignatureHelp(document, position);
|
||||
resolve(res);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Provide help for the signature at the given position and document.
|
||||
*
|
||||
* @param document The document in which the command was invoked.
|
||||
* @param position The position at which the command was invoked.
|
||||
* @param token A cancellation token.
|
||||
* @return Signature help or a thenable that resolves to such. The lack of a result can be
|
||||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
do_provideSignatureHelp(document : TextDocument, position : Position) : SignatureHelp | Thenable < SignatureHelp > {
|
||||
const range = document.getWordRangeAtPosition(position);
|
||||
let funcname = "";
|
||||
let curparam = 0;
|
||||
const checkPosition = () => {
|
||||
const line = document.lineAt(position);
|
||||
const startPos = line.firstNonWhitespaceCharacterIndex;
|
||||
const endPos = position.character;
|
||||
const queryStr = line.text.substring(startPos, endPos);
|
||||
|
||||
var reg = /([A-z_]+[A-z0-9_]*)\(/g;
|
||||
let match = reg.exec(queryStr);
|
||||
while (match != null) {
|
||||
funcname = match[1];
|
||||
match = reg.exec(queryStr);
|
||||
}
|
||||
if(funcname != "") {
|
||||
const funcrangestr = line.text.substring(line.text.indexOf(queryStr)+queryStr.indexOf(funcname)+funcname.length, endPos);
|
||||
curparam = countSubStr(funcrangestr, ",");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
checkPosition();
|
||||
|
||||
let resultSignatures: SignatureInformation[] = [];
|
||||
|
||||
if (funcname.length > 0) {
|
||||
// Builtin functions
|
||||
for (let key of Object.keys(config.builtinSymbolInfoMap)) {
|
||||
if (key.endsWith(`\.${funcname}`)) {
|
||||
if (config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Method || config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Function) {
|
||||
const rawDoc = config.builtinSymbolInfoMap[key].rowDoc;
|
||||
const item = config.builtinSymbolInfoMap[key].completionItem;
|
||||
let signatureInfor: SignatureInformation = new SignatureInformation(item.documentation.split('\n')[0], rawDoc.description);
|
||||
for(let arg of rawDoc.arguments){
|
||||
let param: ParameterInformation = new ParameterInformation(`${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}`, "");
|
||||
signatureInfor.parameters.push(param);
|
||||
}
|
||||
resultSignatures.push(signatureInfor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// workspace functions
|
||||
for (let path of Object.keys(config.getAllSymbols())) {
|
||||
let script = config.getSymbols(path);
|
||||
if(!script.signatures)
|
||||
continue
|
||||
let relaPath = path;
|
||||
if(workspace && workspace.rootPath)
|
||||
relaPath = workspace.asRelativePath(relaPath);
|
||||
|
||||
for(let f of Object.keys(script.signatures)) {
|
||||
if(f == funcname) {
|
||||
const signatureStr = script.signatures[f];
|
||||
let signature: SignatureInformation = new SignatureInformation(`func ${f}${signatureStr}`, `Method defined in ${relaPath}`);
|
||||
const params = (signatureStr.substring(signatureStr.indexOf("(")+1, signatureStr.indexOf(")"))).split(",");
|
||||
for(let p of params)
|
||||
signature.parameters.push(new ParameterInformation(p, ""));
|
||||
resultSignatures.push(signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(resultSignatures.length > 0) {
|
||||
return ({
|
||||
signatures: resultSignatures,
|
||||
activeSignature: 0,
|
||||
activeParameter: curparam
|
||||
});
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSignatureHelpProvider;
|
||||
@@ -1,236 +0,0 @@
|
||||
import {Range} from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface GDScript {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {},
|
||||
classes: {},
|
||||
base: string,
|
||||
native: string,
|
||||
signatures: {},
|
||||
// symbol: marked string
|
||||
documents: {},
|
||||
// name : value
|
||||
constvalues: {},
|
||||
enumerations: {}
|
||||
}
|
||||
|
||||
class GDScriptSymbolParser {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
parseContent(content: string, ignoreIndentedVars:boolean = false): GDScript {
|
||||
const script: GDScript = {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {},
|
||||
classes: {},
|
||||
base: "",
|
||||
native: "",
|
||||
signatures: {},
|
||||
documents: {},
|
||||
constvalues: {},
|
||||
enumerations: {}
|
||||
}
|
||||
const text = content;
|
||||
const lines = text.split(/\r?\n/);
|
||||
|
||||
// Base class and native class
|
||||
for (let line of lines) {
|
||||
let match;
|
||||
if (match = line.match(/extends\s+(\w+)/)) {
|
||||
script.native = match[1];
|
||||
break;
|
||||
} else if (match = line.match(/extends\s+('|")(.*)('|")/)) {
|
||||
script.base = match[2];
|
||||
}
|
||||
}
|
||||
|
||||
const getMatches = (regex:RegExp, index=1) => {
|
||||
var matches = [];
|
||||
for(let line of lines) {
|
||||
let match;
|
||||
if (match = regex.exec(line)) {
|
||||
let commentReg = RegExp(/#.*?/.source+regex.source);
|
||||
if(!commentReg.exec(line))
|
||||
matches.push(match[index]);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
// var matches = [];
|
||||
// var match;
|
||||
// while (match = regex.exec(string)) {
|
||||
// matches.push(match[index]);
|
||||
// }
|
||||
// return matches;
|
||||
};
|
||||
|
||||
const findLineRanges = (symbols, reg)=>{
|
||||
const sm = {};
|
||||
symbols.map((name:string)=>{
|
||||
let line = 0;
|
||||
let curline = 0;
|
||||
if(Object.keys(sm).indexOf(name) != -1) return;
|
||||
lines.map(l=>{
|
||||
const nreg = reg.replace("$X$", name);
|
||||
if(l.match(nreg) != null) {
|
||||
line = curline;
|
||||
return;
|
||||
}
|
||||
curline += 1;
|
||||
});
|
||||
sm[name] = line;
|
||||
});
|
||||
return sm;
|
||||
}
|
||||
|
||||
const determRange = (key:string, array: any): Range =>{
|
||||
let line = array[key];
|
||||
let startAt = lines[line].indexOf(key);
|
||||
if(line < 0) line = 0;
|
||||
if(startAt < 0) startAt = 0;
|
||||
return new Range(line, startAt, line, startAt + key.length);
|
||||
};
|
||||
|
||||
const parseSignature = (range: Range):string => {
|
||||
let res = "";
|
||||
const line = lines[range.start.line];
|
||||
if(line.indexOf("(")!= -1 && line.indexOf(")")!=-1) {
|
||||
const signature = line.substring(line.indexOf("("), line.indexOf(")")+1);
|
||||
if(signature && signature.length >0)
|
||||
res = signature;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const parseDocument = (range: Range):string => {
|
||||
let mdoc = ""
|
||||
let line = range.start.line;
|
||||
while( line > 0){
|
||||
const linecontent = lines[line];
|
||||
let match = linecontent.match(/\s*#\s*(.*)/);
|
||||
let commentAtEnd = linecontent.match(/[\w'",\[\{\]\}\(\)]+\s*#\s*(.*)/) != null;
|
||||
if(commentAtEnd && linecontent.match(/^#/))
|
||||
commentAtEnd = false;
|
||||
if(!match && line != range.start.line)
|
||||
break;
|
||||
if(commentAtEnd && line != range.start.line)
|
||||
break;
|
||||
if(match) {
|
||||
let lmcontent = linecontent.substring(linecontent.indexOf("#")+1, linecontent.length);
|
||||
if(lmcontent.startsWith(" ") && lmcontent != " ")
|
||||
lmcontent = lmcontent.substring(1, lmcontent.length);
|
||||
mdoc = lmcontent + "\r\n" + mdoc;
|
||||
}
|
||||
else if(line != range.start.line)
|
||||
break
|
||||
--line;
|
||||
}
|
||||
return mdoc;
|
||||
}
|
||||
|
||||
let funcsnames = getMatches(/func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
||||
const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(");
|
||||
for (let key of Object.keys(funcs)) {
|
||||
let r: Range = determRange(key, funcs);
|
||||
script.functions[key] = r;
|
||||
script.signatures[key] = parseSignature(r);
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
|
||||
let signalnames = getMatches(/signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
||||
const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(");
|
||||
for (let key of Object.keys(signals)) {
|
||||
let r: Range = determRange(key, signals);
|
||||
script.signals[key] = r;
|
||||
script.signatures[key] = parseSignature(r);
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
|
||||
let varreg = /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/;
|
||||
let varreg2 = "var\\s+$X$([^\\w]|$)";
|
||||
let vargroup = 1;
|
||||
if(ignoreIndentedVars) {
|
||||
varreg = /^((export.*?var)|var)\s+([_A-Za-z]+[_A-Za-z0-9]*)\s?/;
|
||||
varreg2 = "^((export.*?var)|var)\\s+$X$\\s?";
|
||||
vargroup = 3;
|
||||
}
|
||||
let varnames = getMatches(varreg, vargroup);
|
||||
const vars = findLineRanges(varnames, varreg2);
|
||||
for (let key of Object.keys(vars)){
|
||||
const r:Range = determRange(key, vars)
|
||||
script.variables[key] = r;
|
||||
let newdoc = parseDocument(r);
|
||||
if(newdoc == "" && script.documents[key])
|
||||
newdoc = script.documents[key];
|
||||
script.documents[key] = newdoc;
|
||||
}
|
||||
|
||||
let constnames = getMatches(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/, 1);
|
||||
const consts = findLineRanges(constnames, "const\\s+$X$\\s*");
|
||||
for (let key of Object.keys(consts)){
|
||||
const r:Range = determRange(key, consts)
|
||||
script.constants[key] = r;
|
||||
let newdoc = parseDocument(r);
|
||||
if(newdoc == "" && script.documents[key])
|
||||
newdoc = script.documents[key];
|
||||
script.documents[key] = newdoc;
|
||||
|
||||
const linecontent = lines[r.start.line];
|
||||
const match = linecontent.match(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*=\s*([\w+]+\(.*\)|"[^"]*"|\-?\d+\.?\d*|\[.*\]|\{.*\})/);
|
||||
if(match && match.length && match.length >1)
|
||||
script.constvalues[key] = match[2];
|
||||
}
|
||||
|
||||
let classnames = getMatches(/class\s+([_A-Za-z]+[_A-Za-z0-9]*)(\s|\:)/, 1);
|
||||
const classes = findLineRanges(classnames, "class\\s+$X$(\\s|\\:)");
|
||||
for (let key of Object.keys(classes)) {
|
||||
const r:Range = determRange(key, classes)
|
||||
script.classes[key] = r;
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
|
||||
let enumnames = getMatches(/enum\s+([_A-Za-z]+[_A-Za-z0-9]*)\s+\{/, 1);
|
||||
const enums = findLineRanges(enumnames, "enum\\s+$X$\\s+\{");
|
||||
for (let key of Object.keys(enums)) {
|
||||
const r:Range = determRange(key, enums)
|
||||
script.constants[key] = r;
|
||||
script.documents[key] = parseDocument(r);
|
||||
|
||||
let curindex = r.start.line
|
||||
while (curindex < lines.length) {
|
||||
const line = lines[curindex];
|
||||
let matchs = line.match(/([_A-Za-z]+[_A-Za-z0-9]*)/g);
|
||||
if(matchs && matchs.length >= 1 ){
|
||||
for (var i = 0; i < matchs.length; i++)
|
||||
if(line.indexOf(matchs[i]) > line.indexOf("{"))
|
||||
script.enumerations[matchs[i]] = new Range(curindex, 0, curindex, line.length);
|
||||
}
|
||||
if(line.indexOf("}") == -1)
|
||||
curindex += 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: enumerations without name
|
||||
// const unnamedEnums = text.match(/enum\s+\{.*\}/gm)
|
||||
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
parseFile(path:string, ignoreIndentedVars:boolean = false): GDScript {
|
||||
const self = this;
|
||||
if(fs.existsSync(path) && fs.statSync(path).isFile()){
|
||||
const content = fs.readFileSync(path, 'utf-8');
|
||||
return this.parseContent(content, ignoreIndentedVars);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSymbolParser;
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
DocumentSymbolProvider,
|
||||
TextDocument,
|
||||
SymbolInformation,
|
||||
CancellationToken,
|
||||
SymbolKind,
|
||||
Range,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
|
||||
import GDScriptSymbolParser from '../gdscript/symbolparser';
|
||||
import config from '../config';
|
||||
|
||||
class GDScriptSymbolProvider implements DocumentSymbolProvider {
|
||||
private parser: GDScriptSymbolParser = null;
|
||||
|
||||
constructor() {
|
||||
this.parser = new GDScriptSymbolParser();
|
||||
}
|
||||
|
||||
provideDocumentSymbols(document: TextDocument, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]> {
|
||||
|
||||
const symbols: SymbolInformation[] = [];
|
||||
var ignoreIndentedVars = false;
|
||||
if(workspace)
|
||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
||||
const script = this.parser.parseContent(document.getText(), ignoreIndentedVars);
|
||||
const signatures = script.signatures;
|
||||
config.setSymbols(document.fileName, script);
|
||||
|
||||
const funcs = script.functions;
|
||||
for (let key of Object.keys(funcs))
|
||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Function, funcs[key]));
|
||||
|
||||
const signals = script.signals;
|
||||
for (let key of Object.keys(signals))
|
||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Interface, signals[key]));
|
||||
|
||||
const vars = script.variables;
|
||||
for (let key of Object.keys(vars))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Variable, vars[key]));
|
||||
|
||||
const consts = script.constants;
|
||||
for (let key of Object.keys(consts))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Constant, consts[key]));
|
||||
|
||||
const classes = script.classes;
|
||||
for (let key of Object.keys(classes))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Class, classes[key]));
|
||||
|
||||
if(script.enumerations) {
|
||||
const enumerations = script.enumerations;
|
||||
for (let key of Object.keys(enumerations))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Enum, enumerations[key]));
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSymbolProvider;
|
||||
@@ -1,41 +0,0 @@
|
||||
import {TextDocument, Position} from 'vscode';
|
||||
|
||||
export function isStr(content:string) {
|
||||
return (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
|
||||
}
|
||||
|
||||
export function getSelectedContent(document: TextDocument, position: Position):string {
|
||||
const line = document.lineAt(position);
|
||||
const wordRange = document.getWordRangeAtPosition(position) ;
|
||||
const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*?"|'.*?'|@".*?"/g)
|
||||
let res = line.text.substring(wordRange.start.character, wordRange.end.character);
|
||||
machs.map(m=>{
|
||||
if(m) {
|
||||
const startPos = line.text.indexOf(m);
|
||||
const endPos = startPos + m.length;
|
||||
if(isStr(m) && startPos != -1 && wordRange.start.character >= startPos && wordRange.end.character <= endPos){
|
||||
res = m;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
export function getStrContent(rawstr: string):string {
|
||||
let ss = rawstr;
|
||||
if(isStr(ss)) {
|
||||
ss = ss.replace(/"|'|@"|"""/g,"")
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
export function countSubStr(str:string, sub:string): number {
|
||||
let count = 0;
|
||||
let pos = str.indexOf(sub);
|
||||
while (pos !== -1) {
|
||||
count++;
|
||||
pos = str.indexOf(sub, pos + sub.length);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
class GDScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
|
||||
public provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): vscode.SymbolInformation[] {
|
||||
const scripts = config.getAllSymbols();
|
||||
const symbols: vscode.SymbolInformation[] = [];
|
||||
for (let path of Object.keys(scripts)) {
|
||||
const queryMembers = (query, members, kind: vscode.SymbolKind, path:string, extra=(text)=>text)=> {
|
||||
for (let name of Object.keys(members)) {
|
||||
const range: vscode.Range = members[name];
|
||||
if(name.toLowerCase().indexOf(query.toLowerCase()) != -1) {
|
||||
const symbol: vscode.SymbolInformation = {
|
||||
name: extra(name),
|
||||
kind,
|
||||
containerName: "",
|
||||
location: {
|
||||
uri: vscode.Uri.file(path),
|
||||
range
|
||||
}
|
||||
};
|
||||
symbols.push(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
const scrip = scripts[path];
|
||||
const signatures = scrip.signatures;
|
||||
queryMembers(query, scrip.functions, vscode.SymbolKind.Function, path, (name)=>(name+signatures[name]));
|
||||
queryMembers(query, scrip.signals, vscode.SymbolKind.Interface, path, (name)=>(name+signatures[name]));
|
||||
queryMembers(query, scrip.variables, vscode.SymbolKind.Variable, path);
|
||||
queryMembers(query, scrip.constants, vscode.SymbolKind.Constant, path);
|
||||
queryMembers(query, scrip.classes, vscode.SymbolKind.Class, path);
|
||||
if(scrip.enumerations)
|
||||
queryMembers(query, scrip.enumerations, vscode.SymbolKind.Enum, path);
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptWorkspaceSymbolProvider;
|
||||
47
src/loggger.ts
Normal file
47
src/loggger.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export class Logger {
|
||||
protected buffer: string = "";
|
||||
protected tag: string = '';
|
||||
protected time: boolean = false;
|
||||
|
||||
constructor(tag: string, time: boolean) {
|
||||
this.tag = tag;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.buffer = "";
|
||||
}
|
||||
|
||||
log(...messages) {
|
||||
|
||||
let line = '';
|
||||
if (this.tag) {
|
||||
line += `[${this.tag}]`;
|
||||
}
|
||||
if (this.time) {
|
||||
line += `[${new Date().toISOString()}]`;
|
||||
}
|
||||
if (line) {
|
||||
line += ' ';
|
||||
}
|
||||
|
||||
for (let index = 0; index < messages.length; index++) {
|
||||
line += messages[index];
|
||||
if (index < messages.length) {
|
||||
line += " ";
|
||||
} else {
|
||||
line += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
this.buffer += line;
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
get_buffer(): string {
|
||||
return this.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger('godot-tools', true);
|
||||
export default logger;
|
||||
48
src/lsp/GDScriptLanguageClient.ts
Normal file
48
src/lsp/GDScriptLanguageClient.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { workspace } from "vscode";
|
||||
import * as websocket from "websocket-stream";
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient";
|
||||
import { is_debug_mode, get_configuration } from "../utils";
|
||||
import logger from "../loggger";
|
||||
|
||||
function getClientOptions(): LanguageClientOptions {
|
||||
return {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [
|
||||
{ scheme: "file", language: "gdscript" },
|
||||
{ scheme: "untitled", language: "gdscript" },
|
||||
],
|
||||
synchronize: {
|
||||
// Notify the server about file changes to '.gd files contain in the workspace
|
||||
fileEvents: workspace.createFileSystemWatcher("**/*.gd"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const serverOptions: ServerOptions = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let port = get_configuration("gdscript_lsp_server_port", 6008);
|
||||
const ws = websocket(`ws://localhost:${port}`);
|
||||
if (is_debug_mode()) {
|
||||
let text = '';
|
||||
ws.on('data', (chunk)=>{
|
||||
let message = chunk.toString();
|
||||
text += message;
|
||||
logger.log("[server]", message);
|
||||
});
|
||||
const origin_write = ws._write.bind(ws);
|
||||
ws._write = (function (chunk: any, encoding: string, callback: (error?: Error | null) => void) {
|
||||
let message = chunk.toString();
|
||||
text += message;
|
||||
origin_write(chunk, encoding, callback);
|
||||
logger.log("[client]", message);
|
||||
}).bind(ws);
|
||||
}
|
||||
resolve({reader: ws, writer: ws});
|
||||
});
|
||||
};
|
||||
|
||||
export default class GDScriptLanguageClient extends LanguageClient {
|
||||
constructor() {
|
||||
super(`GDScriptLanguageClient`, serverOptions, getClientOptions());
|
||||
}
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as http from 'http';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
function requestGodot(body : Object) {
|
||||
let postString = JSON.stringify(body);
|
||||
const port = vscode.workspace.getConfiguration("GodotTools").get("editorServerPort", 6996);
|
||||
const options = {
|
||||
hostname: '127.0.0.1',
|
||||
method: 'POST',
|
||||
port,
|
||||
body,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(postString)
|
||||
}
|
||||
};
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
var req = http.request(options, (res) => {
|
||||
let resultString = "";
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => {
|
||||
resultString += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(JSON.parse(resultString));
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
req.write(postString);
|
||||
req.end();
|
||||
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
export default requestGodot;
|
||||
@@ -1,283 +0,0 @@
|
||||
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;
|
||||
9
src/utils.ts
Normal file
9
src/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { workspace } from "vscode";
|
||||
|
||||
export function get_configuration(name: string, default_value: any = null) {
|
||||
return workspace.getConfiguration("godot_tools").get(name, default_value);
|
||||
}
|
||||
|
||||
export function is_debug_mode(): boolean {
|
||||
return process.env.VSCODE_DEBUG_MODE === "true";
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import {Disposable, window} from 'vscode';
|
||||
import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
|
||||
import GDScriptCompleter from './gdscript/completion';
|
||||
import config from './config';
|
||||
|
||||
interface DocumentFlag {
|
||||
path: string,
|
||||
version: number
|
||||
}
|
||||
|
||||
class WindowWatcher {
|
||||
|
||||
private _disposable: Disposable;
|
||||
private _diagnosticSeverity: GDScriptDiagnosticSeverity;
|
||||
private _lastText: DocumentFlag;
|
||||
|
||||
constructor() {
|
||||
let subscriptions: Disposable[] = [];
|
||||
window.onDidChangeTextEditorSelection(this.onDidChangeTextEditorSelection.bind(this), this, subscriptions);
|
||||
window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor.bind(this), this, subscriptions);
|
||||
window.onDidChangeTextEditorOptions(this.onDidChangeTextEditorOptions.bind(this), this, subscriptions);
|
||||
window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions);
|
||||
|
||||
this._diagnosticSeverity = new GDScriptDiagnosticSeverity();
|
||||
this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity);
|
||||
this._lastText = {path: "-1", version: -1};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the [active editor](#window.activeTextEditor)
|
||||
* has changed. *Note* that the event also fires when the active editor changes
|
||||
* to `undefined`.
|
||||
*/
|
||||
private onDidChangeActiveTextEditor(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
|
||||
if(window.activeTextEditor != undefined) {
|
||||
const doc = window.activeTextEditor.document;
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
}
|
||||
this._lastText = {path: doc.fileName, version: doc.version};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the selection in an editor has changed.
|
||||
*/
|
||||
private onDidChangeTextEditorSelection(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorSelection");
|
||||
const doc = window.activeTextEditor.document;
|
||||
const curText: DocumentFlag= {path: doc.fileName, version: doc.version};
|
||||
// Check content changed
|
||||
if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
}
|
||||
this._lastText = curText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the options of an editor have changed.
|
||||
*/
|
||||
private onDidChangeTextEditorOptions(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorOptions", event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the view column of an editor has changed.
|
||||
*/
|
||||
private onDidChangeTextEditorViewColumn(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorViewColumn", event);
|
||||
}
|
||||
}
|
||||
|
||||
export default WindowWatcher;
|
||||
@@ -1,18 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test",
|
||||
"src/gdscript/server",
|
||||
"server"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
14
tslint.json
Normal file
14
tslint.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-string-throw": true,
|
||||
"no-unused-expression": true,
|
||||
"no-duplicate-variable": false,
|
||||
"curly": true,
|
||||
"class-name": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
Reference in New Issue
Block a user