mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ba776dc40 | ||
|
|
abaa5d32d0 | ||
|
|
06817de78e | ||
|
|
6aab0be1d4 | ||
|
|
307e29a7ec | ||
|
|
58e8626cac | ||
|
|
3ac359af86 | ||
|
|
c20909fdb3 | ||
|
|
ec9fe7fdc1 | ||
|
|
c84d7f9c12 | ||
|
|
492fe5663c | ||
|
|
8f5da41a41 | ||
|
|
e4d86f35be | ||
|
|
eed2d4f516 | ||
|
|
63b10b1d72 | ||
|
|
fdc07e4743 | ||
|
|
80ce466d53 | ||
|
|
7e3e95086b | ||
|
|
7c8696abc1 | ||
|
|
8ddc7dd310 | ||
|
|
e76c06a31a | ||
|
|
4c0f864cf8 | ||
|
|
462a7bdbd7 | ||
|
|
e2dbf8146f | ||
|
|
7b21267d07 | ||
|
|
ef3a70f417 | ||
|
|
fc027ea9ad | ||
|
|
4c47fff9af | ||
|
|
d7ff45edac | ||
|
|
38b0898649 | ||
|
|
a3b062e242 | ||
|
|
071e59364f | ||
|
|
a47980981b | ||
|
|
edcb8b96d1 | ||
|
|
1248d8e25a | ||
|
|
ae0830638b | ||
|
|
bab6fed5cf | ||
|
|
27d7501672 | ||
|
|
4dff47d97b | ||
|
|
7a8f00422b | ||
|
|
447af0183f | ||
|
|
1cb8a5724d | ||
|
|
0a9a0117f0 | ||
|
|
d8b9dee510 | ||
|
|
0f31c66c5a | ||
|
|
638a5362e6 | ||
|
|
4f0550ce86 | ||
|
|
bc96a9462b | ||
|
|
749148841d | ||
|
|
2384004ef9 | ||
|
|
927297ad03 | ||
|
|
5b1bee0c68 | ||
|
|
7f5b244113 | ||
|
|
670f46014b | ||
|
|
203e32c3ed | ||
|
|
b7dbbcc589 | ||
|
|
ec7e1013aa | ||
|
|
4b3282cf03 | ||
|
|
58203708ba | ||
|
|
57b6f1ad01 | ||
|
|
921977fbdb | ||
|
|
0618034ace | ||
|
|
2fb1e333b1 | ||
|
|
07740b5c97 | ||
|
|
b08c61ac8d | ||
|
|
f36169d685 | ||
|
|
0be4aef2bd | ||
|
|
32b3449bf2 | ||
|
|
a45a5e27fa | ||
|
|
dda701026c | ||
|
|
90457a0df6 | ||
|
|
b2c5d69692 | ||
|
|
43e31f04cb | ||
|
|
4a1e59697a | ||
|
|
911f5c8d1c | ||
|
|
4918a938b7 | ||
|
|
eac1bbd042 | ||
|
|
d97c586327 | ||
|
|
3c547cc595 | ||
|
|
25afaedfb3 | ||
|
|
39c642ae82 | ||
|
|
55b16c3824 | ||
|
|
86ccb8318f | ||
|
|
2834c2b19c | ||
|
|
71e431a5ec | ||
|
|
65cb8c2deb | ||
|
|
cf5faf2152 | ||
|
|
0f7d4902fd | ||
|
|
f40f091421 | ||
|
|
e21de33ffc | ||
|
|
2d8df590b4 | ||
|
|
590f9e51e7 | ||
|
|
519add7029 | ||
|
|
236adcab1c | ||
|
|
77dcdf5a5b | ||
|
|
82f4765bcc | ||
|
|
91c6acb1fd | ||
|
|
c657fc16de |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ test
|
||||
*.vsix
|
||||
configurations/tmp.txt
|
||||
configurations/test.py
|
||||
.vscode-test
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -10,7 +10,7 @@
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/src"],
|
||||
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
|
||||
114
CHANGELOG.md
114
CHANGELOG.md
@@ -1,5 +1,119 @@
|
||||
# Change Log
|
||||
|
||||
### 0.3.7
|
||||
* Add `lint` configuration to control the behaviors of syntax checking
|
||||
* Fix error with run godot editor when the editor contains spaces
|
||||
* Disable semicolons and brackets checks as default can be enabled with project settings
|
||||
* Fix bugs in syntax valiadating
|
||||
* Sync documentations with godot 3.0.4
|
||||
```json
|
||||
{
|
||||
"lint": {
|
||||
"semicolon": true,
|
||||
"conditionBrackets": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 0.3.6
|
||||
* Fix project configuartion file path
|
||||
|
||||
### 0.3.5
|
||||
* Add option to disable syntax checking for GDScript
|
||||
* Improved inline if else statement syntax checking
|
||||
* More resource type supported for syntax highglight
|
||||
* Bump default godot version to 3.0
|
||||
* Sync the documentations from godot 3.0
|
||||
|
||||
### 0.3.4
|
||||
* Fix bug with builtin symbols parsing for godot 2.1
|
||||
* Improved hover documentation
|
||||
* Show window progress when parsing workspace symbols
|
||||
|
||||
### 0.3.3
|
||||
* Fix some syntax checking errors.
|
||||
* Fix problems with hover documentation with latest VSCode.
|
||||
* Improved builtin class documentation page.
|
||||
* Update the documentation data with latest godot version.
|
||||
|
||||
### 0.3.2
|
||||
* Fix syntax checking error with match statement.
|
||||
* Improved documentation for builtin code blocks.
|
||||
* Start using MarkdonwString to keep links valid.
|
||||
|
||||
### 0.3.1
|
||||
* Update documentations with latest godot.
|
||||
* Fix errors with run script and run project.
|
||||
* Improve code completion with opening script file and constants.
|
||||
* Some improvements for documentations.
|
||||
|
||||
### 0.3.0
|
||||
* Add project root configuration settings as `GodotTools.godotProjectRoot` thanks Konstantin Zaitcev
|
||||
* Add auto indent support for gdscript language
|
||||
* More friendly with godot 3.0 alpha
|
||||
* Updated script snippets
|
||||
* Fix highglight error with gdscript language
|
||||
* Limited code completions
|
||||
|
||||
### 0.2.9
|
||||
* Add configuration `GodotTools.completeNodePath` to switch is complete node pathes
|
||||
* Enhanced syntax highlight with GDScript
|
||||
* Enhanced code completion with GDScript
|
||||
|
||||
### 0.2.8
|
||||
* Add godot 3.0 project support with configuration `GodotTools.parseTextScene` >= 3
|
||||
* Add configuration `GodotTools.parseTextScene` to allow disable node path parsing
|
||||
* Remove `GodotTools.editorServerPort` configuration
|
||||
|
||||
### 0.2.7
|
||||
|
||||
* Fix some error with syntax checking
|
||||
* Add symbol support for enumerations
|
||||
* Remove key bindings for `F5`~`F8` as it might be confict with other functionalities of VSCode
|
||||
* You can bind the key bindings back by add following configurations
|
||||
```json
|
||||
{
|
||||
"command": "godot.runWorkspace",
|
||||
"key": "F5"
|
||||
},
|
||||
{
|
||||
"command": "godot.runCurrentScene",
|
||||
"key": "F6"
|
||||
},
|
||||
{
|
||||
"command": "godot.openWithEditor",
|
||||
"key": "F7"
|
||||
},
|
||||
{
|
||||
"command": "godot.updateWorkspaceSymbols",
|
||||
"key": "F8"
|
||||
}
|
||||
```
|
||||
For more references please ready [keybindings](https://code.visualstudio.com/docs/getstarted/keybindings)
|
||||
|
||||
### 0.2.6
|
||||
|
||||
* Add shorthand if else expression support
|
||||
* Add `enum` and `match` expression support
|
||||
* Fix bugs with syntax checking
|
||||
* Updated documentation data with godot 2.1.3
|
||||
* Add syntax checking for end of expression
|
||||
* The pulugin is compiled with latest VSCode thanks @arrkiin
|
||||
* Add key bindings for open workspace with godot editor with `F7` and update workspace symbols with `F8`
|
||||
|
||||
### 0.2.5
|
||||
|
||||
* Run games within VSCode terminals
|
||||
* Add key bindings for `F5 to run the workspace` and `F6 to run the edting scene`
|
||||
* Fix a lot of bugs with unused vaiable cheching
|
||||
* Move workspace symbols state notice to status bar
|
||||
|
||||
### 0.2.4
|
||||
|
||||
* Add code cheching for asignments and comparations
|
||||
* Impoved builtin documentation preview page
|
||||
* Fix bugs with unused vaiable cheching
|
||||
|
||||
### 0.2.3
|
||||
* Fix known errors with code syntax checking
|
||||
* Add configuration `ignoreIndentedVars` to allow ignore indented variables in scripts
|
||||
|
||||
97
README.md
97
README.md
@@ -2,78 +2,63 @@ A complete set of tools to code games with the [Godot game engine](http://www.go
|
||||
|
||||
## Features
|
||||
|
||||
The plug-in comes with a wealth of features to make your programming experience as comfortable as possible
|
||||
The extension comes with a wealth of features to make your Godot programming experience as comfortable as possible:
|
||||
|
||||
- Syntax highlighting for the GDscript language
|
||||
- Syntax highlighting for the tscn and tres scene formats
|
||||
- Function definitions and documentation on hover
|
||||
- Rich auto completion
|
||||
- Syntax highlighting for the GDscript (`.gd`) language
|
||||
- Syntax highlighting for the `.tscn` and `.tres` scene formats
|
||||
- Function definitions and documentation display on hover (see image below)
|
||||
- Rich auto-completion
|
||||
- Static code validation
|
||||
- Open projects and scenes in Godot from VScode
|
||||
- Ctrl click on a variable or method call to jump to its definition
|
||||
- Run/debug the godot game with VSCode with F5(coming soon)
|
||||
- Open projects and scenes in Godot from VS Code
|
||||
- Ctrl-click on a variable or method call to jump to its definition
|
||||
- Full documentation of the Godot engine's API supported
|
||||
|
||||

|
||||

|
||||
|
||||
## Available commands
|
||||
## Available Commands
|
||||
|
||||
The plug-ins adds a few entries to the command palette
|
||||
The extension adds a few entries to the VS Code Command Palette under "GodotTools":
|
||||
|
||||
- Update Workspace Symbols
|
||||
- Run workspace as godot project
|
||||
- Open workspace with godot editor
|
||||
- Update workspace symbols
|
||||
- Run workspace as Godot project
|
||||
- Open workspace with Godot editor
|
||||
- Run current scene
|
||||
|
||||
## Settings
|
||||
|
||||
You can use the following settings to setup the Godot Tools:
|
||||
### Godot
|
||||
|
||||
- GodotTools.editorServerPort: The http server port used by the EditorServer Godot module (_see Extra Functionality below_)
|
||||
- GodotTools.maxNumberOfProblems: Sets the limit for the issues reported by the static code validator
|
||||
- GodotTools.editorPath: An absolute path pointing at the Godot Editor executable file. Required to run the project and test scenes from VScode
|
||||
- GodotTools.workspaceDocumentWithMarkdown: Control the documentations of workspace symbols should be rendered as plain text or html from markdown
|
||||
- GodotTools.ignoreIndentedVars: Parse variables defined after indent of not
|
||||
If you like this extension, you can set VS Code as your default script editor for Godot by following these steps:
|
||||
1. Open editor settings
|
||||
2. Select `Text Editor / External`
|
||||
3. Make sure the `Use External Editor` box is checked
|
||||
4. Fill `Exec Path` with the path to your VS Code executable
|
||||
5. Fill `Exec Flags` with `{project} --goto {file}:{line}:{col}`
|
||||
|
||||
## Extra functionality
|
||||
### VS Code
|
||||
|
||||
If you want to get an even better experience with this plug-in, you can extend its functionality using the following modules and VScode extensions:
|
||||
You can use the following settings to configure Godot Tools:
|
||||
- **GodotTools.godotVersion** - The Godot version of your project.
|
||||
- **GodotTools.editorPath** - The absolute path to the Godot executable. Required to run the project and test scenes directly from VS Code.
|
||||
- **GodotTools.workspaceDocumentWithMarkdown** - Control how the documentation of workspace symbols should be rendered: as plain text or as HTML from Markdown.
|
||||
- **GodotTools.ignoreIndentedVars** - Only parse variables defined on lines without an indentation.
|
||||
- **GodotTools.parseTextScene** - Parse a file as a Godot scene when the file name ends with `.tscn`.
|
||||
- **GodotTools.completeNodePath** - Show node paths within a workspace as part of code completion.
|
||||
- **GodotTools.godotProjectRoot** - Your Godot project's directory, which contains `project.godot` or `engine.cfg`.
|
||||
|
||||
### Godot modules
|
||||
## Issues and Contributions
|
||||
|
||||
These are modules for the goats editor itself, programmed in C++. In order to use them, you have to create a [custom build](http://docs.godotengine.org/en/stable/reference/compiling_for_windows.html) of the engine. Only do that if you know what you're doing.
|
||||
The [Godot Tools](https://github.com/GodotExplorer/godot-tools) extension and [engine modules](https://github.com/GodotExplorer/editor-server) are both hosted on GitHub. Feel free to open issues there and create pull requests anytime.
|
||||
|
||||
- [EditorServer](https://github.com/GodotExplorer/editor-server/tree/master/editor_server): Using HTTP requests, this module gets extra information from Godot to improve autocompletion.
|
||||
- [VSCode](https://github.com/GodotExplorer/editor-server/tree/master/vscode_tools): The VS code module generates a setting file that Visual Studio code can use to generate Tasks automatically.
|
||||
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md) for the latest changes.
|
||||
|
||||
### VScode extensions
|
||||
## FAQ
|
||||
|
||||
### Why isn't Intellisense showing up for me?
|
||||
|
||||
- [TOML language](https://marketplace.visualstudio.com/items?itemName=be5invis.toml): Godot uses this minimal language to store settings. For example in your project config file. If you want to get syntax highlighting for these files, you will have to install the TOML language extension.
|
||||
Make sure you save your `.gd` file, then run "GodotTools: Update Workspace Symbols" from the Command Palette.
|
||||
|
||||
## Issues and contributions
|
||||
|
||||
The [Godot Tools](https://github.com/GodotExplorer/godot-tools) and the go to [engine modules](https://github.com/GodotExplorer/editor-server) are all hosted on GitHub. Feel free to open issues there and create pull requests anytime.
|
||||
|
||||
## Release Notes
|
||||
|
||||
### 0.2.3
|
||||
* Fix known errors with code syntax checking
|
||||
* Add configuration `GodotTools.ignoreIndentedVars` to allow ignore indented variables in scripts
|
||||
* Enhanced hover tip documentation rendering with code examples
|
||||
* Add launch configurations to launch game with F5(expiremental)
|
||||
* The plugin is open source under MIT license
|
||||
|
||||
### 0.2.2
|
||||
* Better Syntax validating for code blocks
|
||||
* More waring for non-python liked expression
|
||||
|
||||
### 0.2.1
|
||||
* Support markdown render in hover tips for documentations in workspace symbols
|
||||
* Add configuration `GodotTools.workspaceDocumentWithMarkdown` to control workspace documentation rendering
|
||||
|
||||
[Read more from the full change log](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
|
||||
|
||||
|
||||
## TODOS:
|
||||
* Print game output log into VSCode console while game launched from the plugin
|
||||
* Convert official BBCode documentation into Markdown and render it to HTML with documentation previewer pages
|
||||
* Add mermaid support with documentation
|
||||
## TODO:
|
||||
* Convert official BBCode documentation into Markdown and render it into HTML with documentation previewer pages
|
||||
* Add mermaid support with documentation
|
||||
* Undefined variable checking
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"name": "comment.line.number-sign.gdscript"
|
||||
},
|
||||
{
|
||||
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|breakpoint)\\b",
|
||||
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|and|in|is|not|or|return|onready|setget|enum|match|breakpoint)\\b",
|
||||
"name": "keyword.control.gdscript"
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -21,5 +21,9 @@
|
||||
["(", ")"],
|
||||
["[", "]"],
|
||||
["{", "}"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "^\\s*((class|func|else|elif|for|if|match|while|enum)|(.*\\sdo\\b))\\b[^\\{;]*$",
|
||||
"decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(else|elif)\\b)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, ${id} and ${id:label} and ${1:label} for variables. Variables with the same id are connected.
|
||||
// Example:
|
||||
"Subclass": {
|
||||
"Inner class": {
|
||||
"prefix": "class",
|
||||
"body": [
|
||||
"class $1 extends ${2:Reference}",
|
||||
@@ -57,7 +57,23 @@
|
||||
"func _input_event(event):",
|
||||
"\t${1:pass}"
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
"_draw method of Node": {
|
||||
"prefix": "draw",
|
||||
"body": [
|
||||
"func _draw():",
|
||||
"\t${1:pass}"
|
||||
]
|
||||
},
|
||||
|
||||
"_gui_input method of Node": {
|
||||
"prefix": "guii",
|
||||
"body": [
|
||||
"func _gui_input(event):",
|
||||
"\t${1:pass}"
|
||||
]
|
||||
},
|
||||
|
||||
"for loop": {
|
||||
"prefix": "for",
|
||||
@@ -145,7 +161,7 @@
|
||||
"define onready variables": {
|
||||
"prefix": "onready",
|
||||
"body": [
|
||||
"onready var ${1:name}${2: = default}${3: setget }"
|
||||
"onready var ${1:name} = get_node($2)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -156,6 +172,13 @@
|
||||
]
|
||||
},
|
||||
|
||||
"Is instance of a class or script": {
|
||||
"prefix": "is",
|
||||
"body": [
|
||||
"${1:instance} is ${2:class}"
|
||||
]
|
||||
},
|
||||
|
||||
"element in array": {
|
||||
"prefix": "in",
|
||||
"body": [
|
||||
@@ -192,5 +215,11 @@
|
||||
"body": [
|
||||
"set_process_input(true)"
|
||||
]
|
||||
},
|
||||
"pass statement": {
|
||||
"prefix": "pass",
|
||||
"body": [
|
||||
"pass"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
doc/.gitignore
vendored
Normal file
2
doc/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
classes/*.xml
|
||||
classes-*.xml
|
||||
83763
doc/classes-2.1.json
Normal file
83763
doc/classes-2.1.json
Normal file
File diff suppressed because it is too large
Load Diff
90257
doc/classes-3.0.json
Normal file
90257
doc/classes-3.0.json
Normal file
File diff suppressed because it is too large
Load Diff
66075
doc/classes.json
66075
doc/classes.json
File diff suppressed because it is too large
Load Diff
66
doc/xmldoc2json-2.1.py
Executable file
66
doc/xmldoc2json-2.1.py
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/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()
|
||||
|
||||
80
doc/xmldoc2json-3.0.py
Executable file
80
doc/xmldoc2json-3.0.py
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/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()
|
||||
|
||||
BIN
icon.png
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
BIN
img/godot-tools.jpg
Normal file
BIN
img/godot-tools.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
2236
package-lock.json
generated
Normal file
2236
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
147
package.json
147
package.json
@@ -2,35 +2,36 @@
|
||||
"name": "godot-tools",
|
||||
"displayName": "Godot Tools",
|
||||
"icon": "icon.png",
|
||||
"description": "\"Tools for game development with godot game engine\"",
|
||||
"version": "0.2.3",
|
||||
"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.5.0"
|
||||
"vscode": "^1.1.21"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:engine.cfg",
|
||||
"workspaceContains:project.godot",
|
||||
"onLanguage:gdscript"
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
"commands":[
|
||||
"commands": [
|
||||
{
|
||||
"command": "godot.updateWorkspaceSymbols",
|
||||
"title": "GodotTools: Update Workspace Symbols"
|
||||
"title": "GodotTools: Update workspace symbols"
|
||||
},
|
||||
{
|
||||
"command": "godot.runWorkspace",
|
||||
"title": "GodotTools: Run workspace as godot project"
|
||||
"title": "GodotTools: Run workspace as Godot project"
|
||||
},
|
||||
{
|
||||
"command": "godot.openWithEditor",
|
||||
"title": "GodotTools: Open workspace with godot editor"
|
||||
"title": "GodotTools: Open workspace with Godot editor"
|
||||
},
|
||||
{
|
||||
"command": "godot.runCurrentScene",
|
||||
@@ -39,13 +40,8 @@
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Godot tools configuration",
|
||||
"title": "Godot Tools configuration",
|
||||
"properties": {
|
||||
"GodotTools.editorServerPort": {
|
||||
"type": "number",
|
||||
"default": 6996,
|
||||
"description": "The server port of your EditorServer"
|
||||
},
|
||||
"GodotTools.maxNumberOfProblems": {
|
||||
"type": "number",
|
||||
"default": 100,
|
||||
@@ -54,17 +50,51 @@
|
||||
"GodotTools.editorPath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The absolute path of your godot editor"
|
||||
"description": "The absolute path to the Godot executable"
|
||||
},
|
||||
"GodotTools.workspaceDocumentWithMarkdown": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Render workspace documentations as markdown content"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -72,13 +102,24 @@
|
||||
{
|
||||
"id": "gdscript",
|
||||
"aliases": [
|
||||
"Godot Engine Script",
|
||||
"GDScript",
|
||||
"gdscript"
|
||||
],
|
||||
"extensions": [
|
||||
".gd"
|
||||
],
|
||||
"configuration": "./configurations/gdscript-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
"cfg",
|
||||
"tres",
|
||||
"tscn",
|
||||
"godot",
|
||||
"gdns",
|
||||
"gdnlib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
@@ -93,82 +134,24 @@
|
||||
"language": "gdscript",
|
||||
"path": "./configurations/snippets.json"
|
||||
}
|
||||
],
|
||||
"breakpoints": [
|
||||
{
|
||||
"language": "gdscript"
|
||||
}
|
||||
],
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "godot",
|
||||
"label": "Godot Game",
|
||||
"program": "./out/src/debug/godotDebugger.js",
|
||||
"runtime": "node",
|
||||
"configurationSnippets": [
|
||||
{
|
||||
"label": "Godot Game: Launch",
|
||||
"description": "A new configuration for launching a godot game",
|
||||
"body": {
|
||||
"type": "godot",
|
||||
"request": "launch",
|
||||
"name": "Godot Game",
|
||||
"godot": "${1:The abusolut path of your godot binary}",
|
||||
"projectDir": "^\"\\${workspaceRoot}\"",
|
||||
"params": [],
|
||||
"runWithEditor": false
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"configurationAttributes": {
|
||||
"launch": {
|
||||
"required": [ "godot", "runWithEditor", "projectDir" ],
|
||||
"properties": {
|
||||
"godot": {
|
||||
"type": "string",
|
||||
"description": "The dirctory of your godot project",
|
||||
"default": ""
|
||||
},
|
||||
"runWithEditor": {
|
||||
"type": "boolean",
|
||||
"description": "Launch the game with godot editor.",
|
||||
"default": false
|
||||
},
|
||||
"projectDir": {
|
||||
"type": "string",
|
||||
"description": "The dirctory of your godot project",
|
||||
"default": "${workspaceRoot}"
|
||||
},
|
||||
"params": {
|
||||
"type": "array",
|
||||
"description": "Addtional params passed to godot",
|
||||
"default": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"initialConfigurations": "godot.provideInitialDebugConfigurations"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
"compile": "tsc -watch -p ./",
|
||||
"compile": "node ./node_modules/typescript/bin/tsc -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^2.0.3",
|
||||
"vscode": "^1.0.0",
|
||||
"mocha": "^2.3.3",
|
||||
"@types/node": "^6.0.40",
|
||||
"@types/mocha": "^2.2.32"
|
||||
"@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",
|
||||
"node-cmd": "1.2.0",
|
||||
"vscode-debugprotocol": "^1.17.0",
|
||||
"vscode-debugadapter": "^1.17.0"
|
||||
"vscode-debugadapter": "^1.17.0"
|
||||
}
|
||||
}
|
||||
|
||||
189
src/config.ts
189
src/config.ts
@@ -9,10 +9,21 @@ interface NodeInfo {
|
||||
instance: string
|
||||
};
|
||||
|
||||
interface CompletionSymbols {
|
||||
classes : CompletionItem[],
|
||||
functions : CompletionItem[],
|
||||
signals : CompletionItem[],
|
||||
constants : CompletionItem[],
|
||||
properties : CompletionItem[],
|
||||
nodes : CompletionItem[],
|
||||
builtinConstants: CompletionItem[]
|
||||
};
|
||||
|
||||
class Config {
|
||||
private symbols;
|
||||
private classes;
|
||||
public bintinSybmolInfoList: CompletionItem[];
|
||||
|
||||
private workspaceSymbols; // filePath: GDScript in symbolparser.ts
|
||||
private builtinCompletions : CompletionSymbols;
|
||||
private builtinClassDoc;
|
||||
public parser: GDScriptSymbolParser;
|
||||
// scriptpath : scenepath
|
||||
public scriptSceneMap: Object;
|
||||
@@ -22,8 +33,16 @@ class Config {
|
||||
public builtinSymbolInfoMap: Object;
|
||||
|
||||
constructor() {
|
||||
this.symbols = {};
|
||||
this.bintinSybmolInfoList = [];
|
||||
this.builtinCompletions = {
|
||||
classes : [],
|
||||
functions : [],
|
||||
signals : [],
|
||||
constants : [],
|
||||
properties : [],
|
||||
nodes : [],
|
||||
builtinConstants: []
|
||||
};
|
||||
this.workspaceSymbols = {};
|
||||
this.builtinSymbolInfoMap = {};
|
||||
this.nodeInfoMap = {};
|
||||
this.scriptSceneMap = {};
|
||||
@@ -40,19 +59,19 @@ class Config {
|
||||
}
|
||||
|
||||
setSymbols(path, s) {
|
||||
this.symbols[this.normalizePath(path)] = s;
|
||||
this.workspaceSymbols[this.normalizePath(path)] = s;
|
||||
}
|
||||
|
||||
getSymbols(path) {
|
||||
return this.symbols[this.normalizePath(path)];
|
||||
return this.workspaceSymbols[this.normalizePath(path)];
|
||||
}
|
||||
|
||||
setAllSymbols(s) {
|
||||
this.symbols = s;
|
||||
this.workspaceSymbols = s;
|
||||
}
|
||||
|
||||
getAllSymbols() {
|
||||
return this.symbols;
|
||||
return this.workspaceSymbols;
|
||||
}
|
||||
|
||||
normalizePath(path) {
|
||||
@@ -75,7 +94,7 @@ class Config {
|
||||
const content = fs.readFileSync(docfile, "utf-8");
|
||||
const docdata = JSON.parse(content);
|
||||
if(docdata.classes) {
|
||||
this.classes = docdata.classes;
|
||||
this.builtinClassDoc = docdata.classes;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
@@ -83,18 +102,16 @@ class Config {
|
||||
console.error(error);
|
||||
}
|
||||
if(done) {
|
||||
for (let key of Object.keys(this.classes)) {
|
||||
const classdoc = this.classes[key];
|
||||
const bintinSybmolInfoList = this.bintinSybmolInfoList;
|
||||
for (let key of Object.keys(this.builtinClassDoc)) {
|
||||
const classdoc = this.builtinClassDoc[key];
|
||||
const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
|
||||
// class
|
||||
// ---------------------- class -----------------
|
||||
const item: CompletionItem = new CompletionItem(classdoc.name, CompletionItemKind.Class);
|
||||
item.detail = 'Native Class';
|
||||
item.documentation = classdoc.brief_description + " \n\n" +classdoc.description;
|
||||
bintinSybmolInfoList.push(item);
|
||||
item.documentation = classdoc.brief_description + " \n" +classdoc.description;
|
||||
this.builtinCompletions.classes.push(item);
|
||||
builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc};
|
||||
// methods
|
||||
const methods = classdoc.methods
|
||||
// ----------------------- 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?"()":"");
|
||||
@@ -103,38 +120,55 @@ class Config {
|
||||
mi.detail = m.return_type;
|
||||
let argstr = "";
|
||||
m.arguments.map(arg=>{
|
||||
argstr += `${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value.length:''}${m.arguments.indexOf(arg)==m.arguments.length-1?'':', '}`;
|
||||
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 mdoc = `${m.return_type} ${classdoc.name}.${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;
|
||||
bintinSybmolInfoList.push(mi);
|
||||
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
|
||||
// ------------------------------ 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}`;
|
||||
bintinSybmolInfoList.push(ci);
|
||||
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 properties = classdoc.properties;
|
||||
const parseProp = (p)=>{
|
||||
});
|
||||
// ----------------------- properties -----------------------
|
||||
const parseProp = (p) => {
|
||||
const pi = new CompletionItem(p.name, CompletionItemKind.Property);
|
||||
pi.detail = `${p.type} of ${classdoc.name}`;
|
||||
pi.documentation = p.description;
|
||||
bintinSybmolInfoList.push(pi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = {completionItem: pi, rowDoc: p};
|
||||
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;
|
||||
@@ -144,10 +178,25 @@ class Config {
|
||||
return done;
|
||||
};
|
||||
|
||||
getWorkspaceCompletionItems(): CompletionItem[] {
|
||||
let items: CompletionItem[] = [];
|
||||
for (let path of Object.keys(this.symbols)) {
|
||||
const script = this.symbols[path];
|
||||
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)) {
|
||||
@@ -159,41 +208,47 @@ class Config {
|
||||
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 ${workspace.asRelativePath(path)}`;
|
||||
item.documentation += `${kindName} defined in ${path}`;
|
||||
_items.push(item);
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
items = [...items, ...addScriptItems(script.classes, CompletionItemKind.Class, "Class")];
|
||||
items = [...items, ...addScriptItems(script.functions, CompletionItemKind.Method, "Method")];
|
||||
items = [...items, ...addScriptItems(script.variables, CompletionItemKind.Variable, "Variable")];
|
||||
items = [...items, ...addScriptItems(script.signals, CompletionItemKind.Interface, "Signal")];
|
||||
items = [...items, ...addScriptItems(script.constants, CompletionItemKind.Enum, "Constant")];
|
||||
|
||||
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())];
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
items = [...items, ...addSceneNodes()];
|
||||
|
||||
return items;
|
||||
return symbols;
|
||||
}
|
||||
|
||||
loadScene(scenePath: string) {
|
||||
@@ -255,13 +310,17 @@ class Config {
|
||||
}
|
||||
|
||||
getClass(name: string) {
|
||||
return this.classes[name];
|
||||
return this.builtinClassDoc[name];
|
||||
}
|
||||
|
||||
getBuiltinCompletions() {
|
||||
return this.builtinCompletions;
|
||||
}
|
||||
|
||||
getBuiltinClassNameList() {
|
||||
let namelist = null;
|
||||
if(this.classes)
|
||||
namelist = Object.keys(this.classes);
|
||||
if (this.builtinClassDoc)
|
||||
namelist = Object.keys(this.builtinClassDoc);
|
||||
if(!namelist)
|
||||
namelist = [];
|
||||
return namelist;
|
||||
|
||||
@@ -1,162 +1,162 @@
|
||||
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');
|
||||
// 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[];
|
||||
}
|
||||
// export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
// godot: string;
|
||||
// projectDir: string;
|
||||
// runWithEditor: boolean;
|
||||
// params: string[];
|
||||
// }
|
||||
|
||||
class GodotDebugSession extends DebugSession {
|
||||
// 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;
|
||||
// // 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();
|
||||
}
|
||||
// /**
|
||||
// * 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 {
|
||||
// /**
|
||||
// * 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());
|
||||
// // 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;
|
||||
// // 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 use 'evaluate' when hovering over source
|
||||
// response.body.supportsEvaluateForHovers = true;
|
||||
|
||||
// make VS Code to show a 'step back' button
|
||||
response.body.supportsStepBack = 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);
|
||||
}
|
||||
// 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 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 {
|
||||
// protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
||||
|
||||
this.sendResponse(response);
|
||||
}
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||
// protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||
|
||||
// return the default thread
|
||||
response.body = {
|
||||
threads: [
|
||||
new Thread(GodotDebugSession.THREAD_ID, "thread 1")
|
||||
]
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
// // 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);
|
||||
}
|
||||
// /**
|
||||
// * 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 scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): 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 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 reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): 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 stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
||||
// this.sendResponse(response);
|
||||
// }
|
||||
|
||||
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): 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;
|
||||
}
|
||||
// /**
|
||||
// * 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);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
// DebugSession.run(GodotDebugSession);
|
||||
|
||||
@@ -34,14 +34,67 @@ 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 > {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let items:CompletionItem[] = config.getWorkspaceCompletionItems();
|
||||
items = [...items, ...config.bintinSybmolInfoList];
|
||||
resolve(items);
|
||||
});
|
||||
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 > {
|
||||
|
||||
@@ -15,16 +15,19 @@ import config from '../config';
|
||||
import {isStr, getSelectedContent, getStrContent} from './utils';
|
||||
|
||||
class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
constructor() {
|
||||
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(workspace.rootPath, content)
|
||||
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()) {
|
||||
@@ -54,6 +57,8 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
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
|
||||
|
||||
@@ -1,134 +1,194 @@
|
||||
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 = [];
|
||||
const text = doc.getText();
|
||||
|
||||
const check = (name:string, range: vscode.Range) => {
|
||||
var matchs = text.match(new RegExp(`[^0-9A-Za-z_]\\s*${name}[^0-9A-Za-z_]\\s*`, 'g'));
|
||||
let count = matchs?matchs.length:0;
|
||||
var incomment = text.match(new RegExp(`#[^0-9A-z_]*${name}[^0-9A-z_]`, '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 diagnostics = [];
|
||||
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";
|
||||
|
||||
if(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(/[^\w](if|elif|else|for|while|func|class)[^\w].*?/) && !line.match(/#.*?[^\w](if|elif|else|for|while|func|class)[^\w].*?/)) {
|
||||
var range = new vscode.Range(i, curLineStartAt, i, line.length);
|
||||
if(!line.match(/(if|elif|else|for|while|func|class).*?\:/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "':' expected at end of the line.", DiagnosticSeverity.Error));
|
||||
else if(line.match(/(if|elif|while|func|class)\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+]|\{.*?\}|\[.*?\]/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
|
||||
else if(line.match(/(if|elif|while)\s*\(.*\)/))
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Extra brackets in condition expression.", DiagnosticSeverity.Warning));
|
||||
|
||||
if(line.match(/([^\w]if|elif|else|for|while|func|class[^\w]).*\:[ \t]+[^#\s]+/))
|
||||
return
|
||||
else 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.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(new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error));
|
||||
}
|
||||
else
|
||||
diagnostics.push(new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error));
|
||||
}
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptDiagnosticSeverity;
|
||||
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;
|
||||
|
||||
@@ -17,9 +17,7 @@ function genLink(title:string, uri:string, span=true):string {
|
||||
|
||||
function getProp(rawDoc:any, propname: string, action=(s :string)=>s): string {
|
||||
let prop = rawDoc[propname];
|
||||
if(prop && prop.length > 0)
|
||||
prop = action(prop);
|
||||
return prop;
|
||||
return action(prop);
|
||||
}
|
||||
|
||||
class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
@@ -67,9 +65,55 @@ class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
});
|
||||
}
|
||||
|
||||
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 =>{
|
||||
return `${genLink(type,type)} `;
|
||||
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){
|
||||
@@ -81,18 +125,30 @@ class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
}
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${ret_type} ${mDoc.name} (${args}) <i>${mDoc.qualifiers}</i></h4>
|
||||
<p>${mDoc.description}</p>
|
||||
${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>
|
||||
<p>${pDoc.description}</p>
|
||||
<h4>${genLink(pDoc.type,pDoc.type)} ${pDoc.name}</h4>
|
||||
<ul>
|
||||
${setter}
|
||||
${getter}
|
||||
</ul>
|
||||
<p>${descContent}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
@@ -171,6 +227,7 @@ class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
return null;
|
||||
const classname = rawDoc.name;
|
||||
let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{
|
||||
if (!inherits) return "";
|
||||
return "<h4>Inherits: " + genLink(inherits, inherits, true) +"</h4>";
|
||||
});
|
||||
|
||||
@@ -189,18 +246,38 @@ class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
subclasses = "<h3>Inherited by</h3> " + "<ul><li>" + subclasses + "</li></ul>";
|
||||
|
||||
let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{
|
||||
return "<h3>Brief Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
||||
return dec;
|
||||
});
|
||||
let descript = getProp(rawDoc, "description", (dec:string)=>{
|
||||
return "<h3>Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
||||
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>Methods</h3><ul>${methods}</ul/>`;
|
||||
methods = `<h3>Public Methods</h3><ul>${methods}</ul/>`;
|
||||
|
||||
let signals = "";
|
||||
for(let s of rawDoc.signals) {
|
||||
@@ -229,17 +306,19 @@ class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
let doc = `
|
||||
${linkStyle}
|
||||
<h1>Native Class ${classname}</h1>
|
||||
<h4>${briefDescript}</h4>
|
||||
<p>${category}</p>
|
||||
<p>${inherits}</p>
|
||||
<p>${subclasses}</p>
|
||||
<p>${briefDescript}</p>
|
||||
<p>${descript}</p>
|
||||
<p>${methods}</p>
|
||||
<p>${propHeaders}</p>
|
||||
<p>${methodHeaders}</p>
|
||||
<p>${signals}</p>
|
||||
<p>${constants}</p>
|
||||
<p>${props}</p>
|
||||
<p>${methods}</p>
|
||||
`;
|
||||
return doc;
|
||||
return this.format_documentation(doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
Position,
|
||||
CancellationToken,
|
||||
Hover,
|
||||
MarkedString,
|
||||
MarkdownString,
|
||||
workspace,
|
||||
Uri,
|
||||
CompletionItem,
|
||||
@@ -32,44 +32,57 @@ class GDScriptHoverProvider implements HoverProvider {
|
||||
if (isStr(hoverText))
|
||||
hoverText = getStrContent(hoverText);
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let tips: MarkedString[] = [];
|
||||
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 path of Object.keys(workspaceSymbols)) {
|
||||
const script = workspaceSymbols[path];
|
||||
let scriptips: MarkedString[] = [];
|
||||
const getHoverText = (items, type, path): MarkedString[] => {
|
||||
const _items: MarkedString[] = [];
|
||||
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 = path;
|
||||
if (workspace && workspace.asRelativePath(dfile))
|
||||
dfile = workspace.asRelativePath(dfile);
|
||||
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]}`;
|
||||
_items.push({language:'gdscript', value:`${type} ${name}${signature}`});
|
||||
let doc = script.documents[name];
|
||||
let doc ='```gdscript\n' + `${type} ${name}${signature}` + '\n```\n';
|
||||
let rowDoc = script.documents[name];
|
||||
if(!withMarkdwon)
|
||||
doc = "```plaintext\r\n"+doc+"\r\n```";
|
||||
rowDoc += "```plaintext\r\n"+rowDoc+"\r\n```";
|
||||
doc += rowDoc;
|
||||
doc = doc?doc+"\r\n\r\n":"";
|
||||
if(path != "autoload")
|
||||
doc += `*Defined in [${dfile}](${Uri.file(path).toString()})*`;
|
||||
_items.push(doc)
|
||||
if(gdpath != "autoload")
|
||||
doc += `*Defined in [${dfile}](${Uri.file(gdpath).toString()})*`;
|
||||
_items.push(makeMarkdown(doc));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
scriptips = [...scriptips, ...getHoverText(script.variables, 'var', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.constants, 'const', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', path)];
|
||||
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];
|
||||
}
|
||||
};
|
||||
@@ -93,8 +106,8 @@ class GDScriptHoverProvider implements HoverProvider {
|
||||
instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
|
||||
}
|
||||
tips = [...tips,
|
||||
`${genLink(node.type, node.type)} ${fullpath}`,
|
||||
`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`
|
||||
makeMarkdown(`${genLink(node.type, node.type)} ${fullpath}`),
|
||||
makeMarkdown(`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`)
|
||||
];
|
||||
break;
|
||||
}
|
||||
@@ -102,11 +115,21 @@ class GDScriptHoverProvider implements HoverProvider {
|
||||
}
|
||||
};
|
||||
|
||||
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):MarkedString[] => {
|
||||
const item2MarkdStrings = (name: string,item: CompletionItem, rowDoc: any):MarkdownString => {
|
||||
let value = "";
|
||||
let doc = item.documentation;
|
||||
let doc = format_documentation(item.documentation);
|
||||
// get class name
|
||||
let classname = name;
|
||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
||||
@@ -118,8 +141,7 @@ class GDScriptHoverProvider implements HoverProvider {
|
||||
|
||||
const genMethodMarkDown = ():string =>{
|
||||
let content = `${genLink(rowDoc.return_type, rowDoc.return_type)} `;
|
||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
||||
content += `${genLink(classname, classname)}.`;
|
||||
if (rowDoc.name != classname) content += `${genLink(classname, classname)}.`;
|
||||
let args = "";
|
||||
for(let arg of rowDoc.arguments){
|
||||
if(rowDoc.arguments.indexOf(arg)!=0)
|
||||
@@ -128,34 +150,34 @@ class GDScriptHoverProvider implements HoverProvider {
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
content += `${genLink(rowDoc.name, classname+'.'+rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
|
||||
content += `${genLink(rowDoc.name, classname+'.' + rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
|
||||
return content;
|
||||
};
|
||||
|
||||
switch (item.kind) {
|
||||
case CompletionItemKind.Class:
|
||||
return [`Native Class ${genLink(classname, classname)}`, doc];
|
||||
return makeMarkdown(`Native Class ${genLink(classname, classname)}\n${doc}`);
|
||||
case CompletionItemKind.Method:
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
return [genMethodMarkDown(), doc];
|
||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
||||
return makeMarkdown(`${genMethodMarkDown()}\n${doc}`);
|
||||
case CompletionItemKind.Interface:
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
return ['signal ' + genMethodMarkDown(), doc];
|
||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
||||
return makeMarkdown(`signal ${genMethodMarkDown()}\n${doc}`);
|
||||
case CompletionItemKind.Variable:
|
||||
case CompletionItemKind.Property:
|
||||
return [`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)}`, doc];
|
||||
return makeMarkdown(`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)}\n${doc}`);
|
||||
case CompletionItemKind.Enum:
|
||||
return [`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)} = ${rowDoc.value}`, doc];
|
||||
return makeMarkdown(`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)} = ${rowDoc.value}\n${doc}`);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return [name, doc];
|
||||
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))];
|
||||
tips = [...tips, item2MarkdStrings(name, item.completionItem, item.rowDoc)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,7 +13,8 @@ interface GDScript {
|
||||
// symbol: marked string
|
||||
documents: {},
|
||||
// name : value
|
||||
constvalues: {}
|
||||
constvalues: {},
|
||||
enumerations: {}
|
||||
}
|
||||
|
||||
class GDScriptSymbolParser {
|
||||
@@ -27,14 +28,26 @@ class GDScriptSymbolParser {
|
||||
variables: {},
|
||||
signals: {},
|
||||
classes: {},
|
||||
base: "Object",
|
||||
native: "Object",
|
||||
base: "",
|
||||
native: "",
|
||||
signatures: {},
|
||||
documents: {},
|
||||
constvalues: {}
|
||||
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 = [];
|
||||
@@ -138,7 +151,7 @@ class GDScriptSymbolParser {
|
||||
}
|
||||
|
||||
let varreg = /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/;
|
||||
let varreg2 = "var\\s+$X$\\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?/;
|
||||
@@ -172,14 +185,39 @@ class GDScriptSymbolParser {
|
||||
script.constvalues[key] = match[2];
|
||||
}
|
||||
|
||||
let classnames = getMatches(/class\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*extends\s+/, 1);
|
||||
const classes = findLineRanges(classnames, "class\\s+$X$\\s*extends\\s+");
|
||||
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] = determRange(key, classes);
|
||||
script.classes[key] = r;
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
// console.log(script);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -195,4 +233,4 @@ class GDScriptSymbolParser {
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSymbolParser;
|
||||
export default GDScriptSymbolParser;
|
||||
@@ -48,6 +48,12 @@ class GDScriptSymbolProvider implements DocumentSymbolProvider {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ class GDScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ var glob = require("glob")
|
||||
import config from './config';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
const cmd = require('node-cmd');
|
||||
class ToolManager {
|
||||
|
||||
private workspaceDir: string = "";
|
||||
@@ -19,19 +18,30 @@ class ToolManager {
|
||||
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;
|
||||
if(vscode.workspace && this.workspaceDir) {
|
||||
vscode.workspace.registerTextDocumentContentProvider('godotdoc', new GDScriptDocumentContentProvider());
|
||||
this.workspaceDir = this.workspaceDir.replace(/\\/g, "/");
|
||||
this.loadWorkspaceSymbols();
|
||||
}
|
||||
if(0) { // TODO: EditorServer validate
|
||||
this.validate();
|
||||
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);
|
||||
@@ -39,66 +49,82 @@ class ToolManager {
|
||||
this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider();
|
||||
vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
|
||||
// definition provider
|
||||
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder());
|
||||
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder(this._rootDir));
|
||||
// hover provider
|
||||
vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
|
||||
// code completion provider
|
||||
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
|
||||
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.runCurrentScenr.bind(this)),
|
||||
vscode.commands.registerCommand('godot.provideInitialDebugConfigurations', this.getDefaultDebugConfig.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)=>{
|
||||
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 godot editor server");
|
||||
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 same with godot editor");
|
||||
vscode.window.showWarningMessage("The opened project is not the same within the Godot editor");
|
||||
}
|
||||
}).catch(e=>{
|
||||
vscode.window.showErrorMessage("Failed connect to godot editor server");
|
||||
}).catch(e => {
|
||||
vscode.window.showErrorMessage("Failed connecting to the Godot editor server");
|
||||
});
|
||||
}
|
||||
|
||||
loadAllSymbols(): Promise<any> {
|
||||
loadAllSymbols(): Promise < any > {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
glob( self.workspaceDir +"/**/*.gd", (err, files)=>{
|
||||
if(!err) {
|
||||
glob(self.workspaceDir + "/**/*.gd", (err, files) => {
|
||||
if (!err) {
|
||||
const symbols = {};
|
||||
for(let i=0; i< files.length; i++)
|
||||
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, "engine.cfg");
|
||||
if(fs.existsSync(engincfg) && fs.statSync(engincfg).isFile()) {
|
||||
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: {}};
|
||||
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);
|
||||
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) {
|
||||
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( self.workspaceDir, gdpath);
|
||||
|
||||
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 = "Auto loaded instance of " + `[${showgdpath}](${vscode.Uri.file(gdpath).toString()})`;
|
||||
|
||||
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);
|
||||
@@ -114,106 +140,144 @@ class ToolManager {
|
||||
}
|
||||
}
|
||||
resolve(symbols);
|
||||
}
|
||||
else
|
||||
} else
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private loadAllNodesInWorkspace() {
|
||||
glob( this.workspaceDir +"/**/*.tscn", (err, files)=>{
|
||||
if(!err) {
|
||||
glob(this.workspaceDir + "/**/*.tscn", (err, files) => {
|
||||
if (!err) {
|
||||
const symbols = {};
|
||||
for(let i=0; i< files.length; i++)
|
||||
for (let i = 0; i < files.length; i++)
|
||||
config.loadScene(files[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadWorkspaceSymbols() {
|
||||
this.loadAllNodesInWorkspace();
|
||||
this.loadAllSymbols().then(symbols=>{
|
||||
vscode.window.showInformationMessage("Update GDScript symbols done");
|
||||
config.setAllSymbols(symbols);
|
||||
}).catch(e=>{
|
||||
vscode.window.showWarningMessage("Update GDScript symbols failed");
|
||||
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="") {
|
||||
private openWorkspaceWithEditor(params = "") {
|
||||
let workspaceValid = false
|
||||
if(this.workspaceDir) {
|
||||
let cfg = path.join(this.workspaceDir, "engine.cfg");
|
||||
if( fs.existsSync(cfg) && fs.statSync(cfg).isFile())
|
||||
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)
|
||||
this.runEditor(`-path ${this.workspaceDir} ${params}`);
|
||||
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");
|
||||
vscode.window.showErrorMessage("Current workspace is not a Godot project");
|
||||
}
|
||||
|
||||
private runEditor(params="") {
|
||||
const editorPath = vscode.workspace.getConfiguration("GodotTools").get("editorPath", "")
|
||||
if(!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
|
||||
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 {
|
||||
cmd.run(`${editorPath} ${params}`);
|
||||
}
|
||||
}
|
||||
|
||||
private runCurrentScenr() {
|
||||
let scenePath = null
|
||||
if(vscode.window.activeTextEditor)
|
||||
scenePath = vscode.workspace.asRelativePath(vscode.window.activeTextEditor.document.uri);
|
||||
if(scenePath.endsWith(".gd"))
|
||||
scenePath = config.scriptSceneMap[config.normalizePath(scenePath)];
|
||||
if(scenePath && (scenePath.endsWith(".tscn") || scenePath.endsWith(".scn"))) {
|
||||
scenePath = ` res://${scenePath} `;
|
||||
this.openWorkspaceWithEditor(scenePath);
|
||||
}
|
||||
else
|
||||
vscode.window.showErrorMessage("Current document is not a scene file");
|
||||
}
|
||||
|
||||
private getDefaultDebugConfig() {
|
||||
const editorPath = vscode.workspace.getConfiguration("GodotTools").get("editorPath", "")
|
||||
if(this.workspaceDir) {
|
||||
const config = {
|
||||
version: '0.2.3',
|
||||
configurations: [{
|
||||
type: 'godot',
|
||||
request: 'launch',
|
||||
name: path.basename(this.workspaceDir),
|
||||
godot: editorPath,
|
||||
projectDir: "${workspaceRoot}",
|
||||
params: [],
|
||||
runWithEditor: false
|
||||
}]
|
||||
} else {
|
||||
let existingTerminal = vscode.window.terminals.find(t => t._name === "GodotTools")
|
||||
if (existingTerminal) {
|
||||
existingTerminal.dispose()
|
||||
}
|
||||
return JSON.stringify(config, null, '\t');
|
||||
let terminal = vscode.window.createTerminal("GodotTools");
|
||||
editorPath = this.escapeCmd(editorPath);
|
||||
let cmmand = `${editorPath} ${params}`;
|
||||
terminal.sendText(cmmand, true);
|
||||
terminal.show();
|
||||
}
|
||||
else {
|
||||
vscode.window.showErrorMessage("Cannot create launch without godot project workspace");
|
||||
return ""
|
||||
}
|
||||
|
||||
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)
|
||||
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, "doc", "classes.json"));
|
||||
if(!done)
|
||||
vscode.window.showErrorMessage("Load GDScript documentations failed");
|
||||
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;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as vscode from 'vscode';
|
||||
import {Disposable, window} from 'vscode';
|
||||
import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
|
||||
import GDScriptCompleter from './gdscript/completion';
|
||||
@@ -40,7 +41,9 @@ class WindowWatcher {
|
||||
if(window.activeTextEditor != undefined) {
|
||||
const doc = window.activeTextEditor.document;
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
}
|
||||
this._lastText = {path: doc.fileName, version: doc.version};
|
||||
}
|
||||
}
|
||||
@@ -55,7 +58,9 @@ class WindowWatcher {
|
||||
// Check content changed
|
||||
if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
}
|
||||
this._lastText = curText;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user