Compare commits

...

64 Commits
0.2.3 ... 0.3.3

Author SHA1 Message Date
geequlim
1248d8e25a release 0.3.3 2017-10-15 15:23:57 +08:00
geequlim
ae0830638b Update documentations with latest 2017-10-15 15:11:39 +08:00
geequlim
bab6fed5cf Fix run editor with space conained path close #42 2017-10-15 14:58:33 +08:00
geequlim
27d7501672 Fix syntax checking error with for close #43 and #39 2017-10-15 14:51:42 +08:00
geequlim
4dff47d97b Fix errors with run current scene 2017-10-15 14:41:45 +08:00
Geequlim
7a8f00422b Update readme 2017-10-13 13:31:11 +08:00
geequlim
447af0183f Improved documentation page. 2017-10-09 23:06:24 +08:00
geequlim
1cb8a5724d Fix hover highlight with latest VSCode version 2017-10-09 21:45:55 +08:00
geequlim
0a9a0117f0 release 0.3.2 2017-09-23 02:07:58 +08:00
geequlim
d8b9dee510 improve hover documentations for code blocks 2017-09-23 02:05:07 +08:00
geequlim
0f31c66c5a MarkedString to MarkdonwString 2017-09-23 01:52:28 +08:00
geequlim
638a5362e6 Fix error complain about match statement 2017-09-23 01:31:07 +08:00
geequlim
4f0550ce86 release 0.3.1 2017-09-22 23:57:26 +08:00
geequlim
bc96a9462b Add constants back with completion 2017-09-09 23:00:57 +08:00
geequlim
749148841d Fix errors with run script
Fix build error on linux
2017-09-09 22:43:32 +08:00
Geequlim
2384004ef9 Implements base class and native class of script parsing 2017-09-06 21:30:28 +08:00
Geequlim
927297ad03 Show symbols in code completion in current document 2017-09-06 21:29:54 +08:00
Geequlim
5b1bee0c68 Merge branch 'master' of github.com:GodotExplorer/godot-tools 2017-08-31 19:17:43 +08:00
Alvaro
7f5b244113 Update package.json (#38)
fix strng to string
2017-08-29 20:01:44 -05:00
Geequlim
670f46014b release 0.3.0 2017-08-29 22:12:34 +08:00
Geequlim
203e32c3ed release 0.3.0 2017-08-29 22:10:03 +08:00
Geequlim
b7dbbcc589 Use ${workspaceRoot} in the settings
Cleanup code for debugger
2017-08-29 22:02:16 +08:00
Konstantin Zaitcev
ec7e1013aa Add project root configuration settings (#37)
* Add project root configuration settings

* Add run scene support for custom godot project directory
2017-08-29 08:43:27 -05:00
Geequlim
4b3282cf03 Support for latest 3.0 run command 2017-08-29 21:38:29 +08:00
Geequlim
58203708ba Add constants of Node and Control to global comletion item list 2017-08-14 21:05:11 +08:00
Geequlim
57b6f1ad01 Fix indentations
Add more snippet
2017-08-14 20:47:59 +08:00
Geequlim
921977fbdb Merge branch 'master' of github.com:GodotExplorer/godot-tools 2017-08-14 16:41:14 +08:00
Geequlim
0618034ace Fix errors with gdscript language support 2017-08-14 16:41:10 +08:00
Geequlim
2fb1e333b1 Merge pull request #35 from somkun/master
Update readme with spelling and FAQ
2017-08-12 12:40:55 +08:00
Andrew Nichols
07740b5c97 Update readme with spelling and FAQ 2017-08-11 20:33:26 -07:00
Geequlim
b08c61ac8d Add indentation rules support for gdscript 2017-08-10 13:51:49 +08:00
geequlim
f36169d685 Make $ to complete node path only 2017-08-06 22:54:15 +08:00
geequlim
0be4aef2bd release 0.2.9 2017-08-06 22:52:47 +08:00
geequlim
32b3449bf2 Add configuration to control is complete with node pathes 2017-08-06 22:43:27 +08:00
geequlim
a45a5e27fa Fix errors with builtin function default values 2017-08-06 17:33:36 +08:00
geequlim
dda701026c enhanced code completion 2017-08-06 16:57:08 +08:00
geequlim
90457a0df6 enhanced syntax highlight 2017-08-06 00:36:44 +08:00
geequlim
b2c5d69692 new icon 2017-08-05 15:03:12 +08:00
geequlim
43e31f04cb Add more 3.0 support
Release 0.2.8
2017-08-04 09:48:45 +08:00
geequlim
4a1e59697a Merge branches 'master' and 'master' of github.com:GodotExplorer/godot-tools 2017-08-03 10:05:16 +08:00
geequlim
911f5c8d1c Add 3.0 support and allow disable parse scenes 2017-08-03 10:04:48 +08:00
Geequlim
4918a938b7 Updat ReadMe 2017-06-15 18:47:24 +08:00
Geequlim
eac1bbd042 update readme 2017-06-15 18:45:02 +08:00
geequlim
d97c586327 release 0.2.7 2017-05-24 21:43:38 +08:00
geequlim
3c547cc595 remove keybindings 2017-05-24 21:43:11 +08:00
geequlim
25afaedfb3 parse named enunmerations 2017-05-23 22:43:43 +08:00
geequlim
39c642ae82 Ignore trailing spaces with indent block checking
Fix #24
2017-05-23 20:44:50 +08:00
arrkiin
55b16c3824 Ignore keywords in comments (#23) 2017-05-10 09:01:12 +08:00
geequlim
86ccb8318f release 0.2.6 2017-05-07 18:21:15 +08:00
geequlim
2834c2b19c Update documentation data to godot 2.1.3 2017-05-07 18:20:45 +08:00
geequlim
71e431a5ec add a script to convert xml documentation into json 2017-05-07 18:20:21 +08:00
geequlim
65cb8c2deb add key bindings for open godot editor and update workspace symbols 2017-05-07 13:33:35 +08:00
geequlim
cf5faf2152 Run script extends from SceneTree is supported 2017-05-07 13:19:40 +08:00
geequlim
0f7d4902fd 1. Add enum and match keywords highlight support
2. Shorthand if else support fix #17
3. Fix compains with used variables in array and dictionary fix #18
4. Fix error syntax check with keywords in strings fix #12
5. Add syntax check for end of expression
2017-05-06 23:25:39 +08:00
arrkiin
f40f091421 Fix sourceMap functionality 2017-05-06 20:22:09 +08:00
arrkiin
e21de33ffc Multiline statements 2017-05-02 11:51:42 +02:00
geequlim
2d8df590b4 release 0.2.5 2017-03-22 03:05:00 +08:00
geequlim
590f9e51e7 Run game within terminal
Remove Debugger
2017-03-22 02:59:21 +08:00
geequlim
519add7029 Move workspace smbols state to status bar 2017-03-22 02:35:01 +08:00
geequlim
236adcab1c improved variable parsing
improved unused variable checking fix #10
2017-03-13 09:48:29 +08:00
geequlim
77dcdf5a5b update 0.2.4 2017-03-09 20:43:26 +08:00
geequlim
82f4765bcc add method index list in documentation previewer 2017-03-09 20:34:19 +08:00
geequlim
91c6acb1fd fix unused variable checking error when be same part of another one 2017-03-09 19:30:06 +08:00
geequlim
c657fc16de add asignment and comparation expression checking 2017-03-09 17:33:01 +08:00
29 changed files with 191610 additions and 66748 deletions

2
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/out/src"],
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
"preLaunchTask": "npm"
},
{

View File

@@ -1,5 +1,89 @@
# Change Log
### 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

View File

@@ -11,9 +11,9 @@ The plug-in comes with a wealth of features to make your programming experience
- 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)
- Full documentation supported with API of godot engine
![Showing the documentation on hover feature](https://raw.githubusercontent.com/GodotExplorer/godot-tools/master/img/documentation-on-hover.png "Method definition and docs on hover")
![Showing the documentation on hover feature](img/godot-tools.jpg)
## Available commands
@@ -26,54 +26,53 @@ The plug-ins adds a few entries to the command palette
## Settings
You can use the following settings to setup the Godot Tools:
If you like this plugin you can set VSCode as your default script editor with following steps:
1. Open editor settings
2. Select `Text Editor / External`
3. Check the `Use External Editor` box with mouse click
4. Fill `Exec Path` to the path of your Visual Studio Code
5. Fill `Exec Flags` with `{project} --goto {file}:{line}:{col}`
- 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
You can use the following settings to setup the Godot Tools:
- GodotTools.godotVersion: The godot version of your project
- 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
## Extra functionality
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:
### Godot modules
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.
- [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.
### VScode extensions
- [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.
- GodotTools.parseTextScene: Parse scene files with extension ends with tscn
- GodotTools.completeNodePath: Show node paths of of workspace in the code completion
- GodotTools.godotProjectRoot: The godot project directory wich contains project.godot or engine.cfg
## 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.
## FAQ
### Intelisense isn't showing up for me
Make sure you save your .gd file, then run "GodotTools: Update Workspace Symbols" from the command palate
## 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.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.2.2
* Better Syntax validating for code blocks
* More waring for non-python liked expression
### 0.3.2
* Fix syntax checking error with match statement.
* Improved documentation for builtin code blocks.
* Start using MarkdonwString to keep links valid.
### 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)
### 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.
[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
* Add mermaid support with documentation
* Undefined variable checking

View File

@@ -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

View File

@@ -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)"
}
}

View File

@@ -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
View File

@@ -0,0 +1,2 @@
classes/*.xml
classes-*.xml

83763
doc/classes-2.1.json Normal file

File diff suppressed because it is too large Load Diff

103934
doc/classes-3.0.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

66
doc/xmldoc2json-2.1.py Executable file
View 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()

70
doc/xmldoc2json-3.0.py Executable file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/python
import sys
import xml.etree.ElementTree as ET
import json
import os
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 fname in os.listdir(sys.argv[1]):
f = os.path.join(sys.argv[1], fname)
tree = ET.parse(open(f, 'r'))
cls = tree.getroot()
dictCls = parseClass(cls)
classes[dictCls['name']] = dictCls
jsonContent = json.dumps({"classes": classes, "version": "3.0.alpha"}, ensure_ascii=False, indent=2)
print(jsonContent)
if __name__ == '__main__':
main()

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

2613
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,24 +2,25 @@
"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.3",
"publisher": "geequlim",
"repository": "https://github.com/GodotExplorer/godot-tools",
"license": "MIT",
"engines": {
"vscode": "^1.5.0"
"vscode": "^1.16.1"
},
"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"
@@ -41,11 +42,6 @@
"type": "object",
"title": "Godot tools configuration",
"properties": {
"GodotTools.editorServerPort": {
"type": "number",
"default": 6996,
"description": "The server port of your EditorServer"
},
"GodotTools.maxNumberOfProblems": {
"type": "number",
"default": 100,
@@ -65,6 +61,26 @@
"type": "boolean",
"default": false,
"description": "Only parse variables without indents in GDScript"
},
"GodotTools.godotVersion": {
"type": "number",
"default": 2.1,
"description": "The godot version of your project"
},
"GodotTools.parseTextScene": {
"type": "boolean",
"default": true,
"description": "Parse scene files with extention ends with tscn"
},
"GodotTools.completeNodePath": {
"type": "boolean",
"default": false,
"description": "Show node pathes of of workspace in the code completion"
},
"GodotTools.godotProjectRoot": {
"type": "string",
"default": "${workspaceRoot}",
"description": "Relate path to the godot project"
}
}
},
@@ -72,13 +88,21 @@
{
"id": "gdscript",
"aliases": [
"Godot Engine Script",
"GDScript",
"gdscript"
],
"extensions": [
".gd"
],
"configuration": "./configurations/gdscript-configuration.json"
},
{
"id": "properties",
"extensions": [
".cfg",
"tres",
"tscn"
]
}
],
"grammars": [
@@ -93,82 +117,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",
"vscode": "^1.1.5",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
},
"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"
}
}

View File

@@ -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);
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,20 @@ 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];
const addScriptItems = (items, kind: CompletionItemKind, kindName:string = "Symbol", insertText = (n)=>n)=>{
const _items: CompletionItem[] = [];
for (let name of Object.keys(items)) {
@@ -164,36 +208,42 @@ class Config {
}
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 +305,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;
@@ -269,4 +323,4 @@ class Config {
};
export default new Config();
export default new Config();

View File

@@ -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);

View File

@@ -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 > {

View File

@@ -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

View File

@@ -4,50 +4,45 @@ import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
import config from '../config';
interface GDParseError {
message: string,
column: number,
row: number
message : string,
column : number,
row : number
}
interface GDScript {
members: {
members : {
constants: {},
functions: {},
variables: {},
signals: {}
},
base: string,
errors: GDParseError[],
valid: boolean,
is_tool: boolean,
native: string
base : string,
errors : GDParseError[],
valid : boolean,
is_tool : boolean,
native : string
}
interface ParseRequest {
text: string,
path: string
text : string,
path : string
}
class GDScriptDiagnosticSeverity {
private _subscription: DiagnosticCollection;
private _subscription : DiagnosticCollection;
constructor() {
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
}
dispose() {
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)),
];
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;
}
@@ -55,80 +50,140 @@ class GDScriptDiagnosticSeverity {
return false;
}
private validateUnusedSymbols(doc: vscode.TextDocument,script) {
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)
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))
for (let key of Object.keys(script.variables))
check(key, script.variables[key]);
for (let key of Object.keys(script.constants))
for (let key of Object.keys(script.constants))
check(key, script.constants[key]);
return diagnostics;
}
private validateExpression(doc: vscode.TextDocument) {
private validateExpression(doc : vscode.TextDocument) {
let diagnostics = [];
let expectEndOfLine = false;
const text = doc.getText();
const lines = text.split(/\r?\n/);
lines.map((line:string, i: number) =>{
lines.map((line : string, i : number) => {
let matchstart = /[^\s]+.*/.exec(line);
let curLineStartAt = 0;
if(matchstart)
if (matchstart)
curLineStartAt = matchstart.index;
// ignore comments
if(line.match(/^\s*#.*/) || line.match(/^#.*/)) return
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(line.match(/[^#].*?\;/) && !line.match(/[#].*?\;/)) {
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));
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));
if (line.match(/[^#].*?/) && expectEndOfLine) {
if (!line.match(/.*?(\\|\:)/)) {
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
expectEndOfLine = false;
}
else
diagnostics.push(new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error));
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+\w.*/))
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(/(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 (line.match(/(if|elif|while|match)\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(/=[^=]/) && !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;

View File

@@ -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);
}
}

View File

@@ -4,7 +4,7 @@ import {
Position,
CancellationToken,
Hover,
MarkedString,
MarkdownString,
workspace,
Uri,
CompletionItem,
@@ -32,16 +32,23 @@ 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[] = [];
let scriptips: MarkdownString[] = [];
const getHoverText = (items, type, path): MarkdownString[] => {
const _items: MarkdownString[] = [];
for (let name of Object.keys(items)) {
if (name == hoverText) {
let dfile = path;
@@ -52,14 +59,15 @@ class GDScriptHoverProvider implements HoverProvider {
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)
_items.push(makeMarkdown(doc));
break;
}
}
@@ -70,6 +78,8 @@ class GDScriptHoverProvider implements HoverProvider {
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', path)];
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', path)];
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', path)];
if(script.enumerations)
scriptips = [...scriptips, ...getHoverText(script.enumerations, 'const', path)];
tips = [...tips, ...scriptips];
}
};
@@ -93,8 +103,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 +112,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 +138,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 +147,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)} ${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()} ${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()} ${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)} ${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} ${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)];
}
}
};

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,29 @@ class ToolManager {
private workspacesymbolprovider: GDScriptWorkspaceSymbolProvider = null;
private _disposable: vscode.Disposable;
private _context: vscode.ExtensionContext;
private _projectFile : string = "engine.cfg";
private _rootDir : string = "";
private _biuitinDocFile : string = "doc/classes-2.1.json";
constructor(context: vscode.ExtensionContext) {
this._context = context;
this.workspaceDir = vscode.workspace.rootPath;
if(vscode.workspace && this.workspaceDir) {
let completionDollar = false;
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 2.1) >= 3) {
this._projectFile = "project.godot";
this._biuitinDocFile = "doc/classes-3.0.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();
}
if(0) { // TODO: EditorServer validate
this.validate();
}
this.loadClasses();
// documentation symbol provider
this.symbolprovider = new GDScriptSymbolProvider();
vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider);
@@ -39,65 +48,81 @@ 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())
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");
else {
vscode.window.showWarningMessage("The opened project is not same with godot editor");
}
}).catch(e=>{
vscode.window.showErrorMessage("Failed connect to godot editor server");
}).catch(e => {
vscode.window.showErrorMessage("Failed connect to 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()})`;
doc = doc.replace(/"/g, " ");
@@ -114,100 +139,104 @@ 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");
if (vscode.workspace.getConfiguration("GodotTools").get("parseTextScene", false)) {
this.loadAllNodesInWorkspace();
}
this.loadAllSymbols().then(symbols => {
vscode.window.setStatusBarMessage("$(check) Workspace symbols", 5000);
config.setAllSymbols(symbols);
}).catch(e => {
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");
}
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}`);
} else {
let terminal = vscode.window.createTerminal("Godot");
let cmmand = `${editorPath.replace(" ", "\\ ")} ${params}`;
terminal.sendText(cmmand, true);
terminal.show();
}
}
private runCurrentScenr() {
private runCurrentScene() {
const absFilePath = vscode.window.activeTextEditor.document.uri.fsPath;
let scenePath = null
if(vscode.window.activeTextEditor)
scenePath = vscode.workspace.asRelativePath(vscode.window.activeTextEditor.document.uri);
if(scenePath.endsWith(".gd"))
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 && (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
}]
if (!scenePath) {
const script = config.loadSymbolsFromFile(absFilePath);
if (script) {
if(script.native == "SceneTree" || script.native == "MainLoop") {
this.runEditor(`-s ${absFilePath}`);
return;
}
}
}
return JSON.stringify(config, null, '\t');
}
else {
vscode.window.showErrorMessage("Cannot create launch without godot project workspace");
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)
if (!done)
done = config.loadClasses(path.join(this._context.extensionPath, this._biuitinDocFile));
if (!done)
vscode.window.showErrorMessage("Load GDScript documentations failed");
}