mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a563a3584a | ||
|
|
758aafc570 | ||
|
|
eba90dbbf9 | ||
|
|
47647a05ae | ||
|
|
28e284f0ad | ||
|
|
c26320ec03 | ||
|
|
7d20df3b35 | ||
|
|
a84548aeac | ||
|
|
0ae80d6bcd | ||
|
|
325b17a29c | ||
|
|
0938b6384a | ||
|
|
3064849452 | ||
|
|
c416ea6789 | ||
|
|
1e22ac0d9a | ||
|
|
61e1cafdfe | ||
|
|
1844dd570b | ||
|
|
ff7f31776a | ||
|
|
fafabc3b34 | ||
|
|
d6331fee89 | ||
|
|
41b36e6e95 | ||
|
|
ece1f3118d | ||
|
|
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 |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ test
|
||||
*.vsix
|
||||
configurations/tmp.txt
|
||||
configurations/test.py
|
||||
.vscode-test
|
||||
|
||||
46
.vscode/launch.json
vendored
46
.vscode/launch.json
vendored
@@ -1,28 +1,24 @@
|
||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/src"],
|
||||
"preLaunchTask": "npm"
|
||||
},
|
||||
{
|
||||
"name": "Launch Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceRoot}/out/test"],
|
||||
"preLaunchTask": "npm"
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "npm: watch",
|
||||
"env": {
|
||||
"VSCODE_DEBUG_MODE": true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -6,5 +6,6 @@
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
48
.vscode/tasks.json
vendored
48
.vscode/tasks.json
vendored
@@ -1,30 +1,20 @@
|
||||
// Available variables which can be used inside of strings.
|
||||
// ${workspaceRoot}: the root folder of the team
|
||||
// ${file}: the current opened file
|
||||
// ${fileBasename}: the current opened file's basename
|
||||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
// A task runner that calls a custom npm script that compiles the extension.
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "0.1.0",
|
||||
|
||||
// we want to run npm
|
||||
"command": "npm",
|
||||
|
||||
// the command is a shell script
|
||||
"isShellCommand": true,
|
||||
|
||||
// show the output window only if unrecognized errors occur.
|
||||
"showOutput": "silent",
|
||||
|
||||
// we run the custom script "compile" as defined in package.json
|
||||
"args": ["run", "compile", "--loglevel", "silent"],
|
||||
|
||||
// The tsc compiler is started in watching mode
|
||||
"isBackground": true,
|
||||
|
||||
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
test/**
|
||||
src/**
|
||||
**/*.map
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/tslint.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
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
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017 Geequlim
|
||||
Copyright (c) 2016-2019 The Godot Engine community
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
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
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,5 +21,16 @@
|
||||
["(", ")"],
|
||||
["[", "]"],
|
||||
["{", "}"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"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)"
|
||||
},
|
||||
"folding": {
|
||||
"offSide": true,
|
||||
"markers": {
|
||||
"start": "^\\s*#\\s*region\\b",
|
||||
"end": "^\\s*#\\s*endregion\\b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
{
|
||||
|
||||
// Place your snippets for JavaScript React here. Each snippet is defined under a snippet name and has a prefix, body and
|
||||
// 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 +52,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 +156,7 @@
|
||||
"define onready variables": {
|
||||
"prefix": "onready",
|
||||
"body": [
|
||||
"onready var ${1:name}${2: = default}${3: setget }"
|
||||
"onready var ${1:name} = get_node($2)"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -156,6 +167,13 @@
|
||||
]
|
||||
},
|
||||
|
||||
"Is instance of a class or script": {
|
||||
"prefix": "is",
|
||||
"body": [
|
||||
"${1:instance} is ${2:class}"
|
||||
]
|
||||
},
|
||||
|
||||
"element in array": {
|
||||
"prefix": "in",
|
||||
"body": [
|
||||
@@ -192,5 +210,11 @@
|
||||
"body": [
|
||||
"set_process_input(true)"
|
||||
]
|
||||
},
|
||||
"pass statement": {
|
||||
"prefix": "pass",
|
||||
"body": [
|
||||
"pass"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66075
doc/classes.json
66075
doc/classes.json
File diff suppressed because it is too large
Load Diff
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 |
273
package.json
273
package.json
@@ -1,174 +1,115 @@
|
||||
{
|
||||
"name": "godot-tools",
|
||||
"displayName": "Godot Tools",
|
||||
"icon": "icon.png",
|
||||
"description": "\"Tools for game development with godot game engine\"",
|
||||
"version": "0.2.3",
|
||||
"publisher": "geequlim",
|
||||
"repository": "https://github.com/GodotExplorer/godot-tools",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.5.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:engine.cfg",
|
||||
"onLanguage:gdscript"
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
"commands":[
|
||||
{
|
||||
"command": "godot.updateWorkspaceSymbols",
|
||||
"title": "GodotTools: Update Workspace Symbols"
|
||||
},
|
||||
{
|
||||
"command": "godot.runWorkspace",
|
||||
"title": "GodotTools: Run workspace as godot project"
|
||||
},
|
||||
{
|
||||
"command": "godot.openWithEditor",
|
||||
"title": "GodotTools: Open workspace with godot editor"
|
||||
},
|
||||
{
|
||||
"command": "godot.runCurrentScene",
|
||||
"title": "GodotTools: Run current scene"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Godot tools configuration",
|
||||
"properties": {
|
||||
"GodotTools.editorServerPort": {
|
||||
"type": "number",
|
||||
"default": 6996,
|
||||
"description": "The server port of your EditorServer"
|
||||
},
|
||||
"GodotTools.maxNumberOfProblems": {
|
||||
"type": "number",
|
||||
"default": 100,
|
||||
"description": "Controls the maximum number of problems produced by the server."
|
||||
},
|
||||
"GodotTools.editorPath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The absolute path of your godot editor"
|
||||
},
|
||||
"GodotTools.workspaceDocumentWithMarkdown": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Render workspace documentations as markdown content"
|
||||
},
|
||||
"GodotTools.ignoreIndentedVars": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Only parse variables without indents in GDScript"
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "gdscript",
|
||||
"aliases": [
|
||||
"Godot Engine Script",
|
||||
"gdscript"
|
||||
],
|
||||
"extensions": [
|
||||
".gd"
|
||||
],
|
||||
"configuration": "./configurations/gdscript-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"scopeName": "source.gdscript",
|
||||
"path": "./configurations/GDScript.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"snippets": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"path": "./configurations/snippets.json"
|
||||
}
|
||||
],
|
||||
"breakpoints": [
|
||||
"name": "godot-tools",
|
||||
"displayName": "godot-tools",
|
||||
"icon": "icon.png",
|
||||
"version": "0.9.1",
|
||||
"description": "Tools for game development with godot game engine",
|
||||
"repository": "https://github.com/godotengine/godot-vscode-plugin",
|
||||
"author": "The Godot Engine community",
|
||||
"publisher": "geequlim",
|
||||
"engines": {
|
||||
"vscode": "^1.33.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"workspaceContains:project.godot",
|
||||
"onLanguage:gdscript"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"scripts": {
|
||||
"vscode:prepublish": "yarn run compile",
|
||||
"compile": "tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "yarn run compile && node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"language": "gdscript"
|
||||
"command": "godot-tool.open_editor",
|
||||
"title": "Godot Tools: Open workspace with Godot editor"
|
||||
},
|
||||
{
|
||||
"command": "godot-tool.run_project",
|
||||
"title": "Godot Tools: Run workspace as Godot project"
|
||||
}
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Godot Tools configuration",
|
||||
"properties": {
|
||||
"godot_tools.gdscript_lsp_server_port": {
|
||||
"type": "number",
|
||||
"default": 6008,
|
||||
"description": "The websocket server port of the GDScript language server"
|
||||
},
|
||||
"initialConfigurations": "godot.provideInitialDebugConfigurations"
|
||||
"godot_tools.editor_path": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The absolute path to the Godot editor executable"
|
||||
},
|
||||
"godot-tool.check_status": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The absolute path to the Godot editor executable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "gdscript",
|
||||
"aliases": [
|
||||
"GDScript",
|
||||
"gdscript"
|
||||
],
|
||||
"extensions": [
|
||||
".gd"
|
||||
],
|
||||
"configuration": "./configurations/gdscript-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "properties",
|
||||
"extensions": [
|
||||
"cfg",
|
||||
"tres",
|
||||
"tscn",
|
||||
"godot",
|
||||
"gdns",
|
||||
"gdnlib"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"scopeName": "source.gdscript",
|
||||
"path": "./configurations/GDScript.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"snippets": [
|
||||
{
|
||||
"language": "gdscript",
|
||||
"path": "./configurations/snippets.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc -p ./",
|
||||
"compile": "tsc -watch -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"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": "^7.1.1",
|
||||
"node-cmd": "1.2.0",
|
||||
"vscode-debugprotocol": "^1.17.0",
|
||||
"vscode-debugadapter": "^1.17.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/marked": "^0.6.5",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/node": "^10.12.21",
|
||||
"@types/prismjs": "^1.16.0",
|
||||
"@types/ws": "^6.0.1",
|
||||
"tslint": "^5.16.0",
|
||||
"typescript": "^3.4.5",
|
||||
"vscode": "^1.1.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"global": "^4.4.0",
|
||||
"marked": "^0.7.0",
|
||||
"prismjs": "^1.17.1",
|
||||
"vscode-languageclient": "^5.2.1",
|
||||
"ws": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
272
src/config.ts
272
src/config.ts
@@ -1,272 +0,0 @@
|
||||
import GDScriptSymbolParser from './gdscript/symbolparser';
|
||||
import * as fs from 'fs';
|
||||
import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode';
|
||||
|
||||
interface NodeInfo {
|
||||
name: string,
|
||||
type: string,
|
||||
parent: string,
|
||||
instance: string
|
||||
};
|
||||
|
||||
class Config {
|
||||
private symbols;
|
||||
private classes;
|
||||
public bintinSybmolInfoList: CompletionItem[];
|
||||
public parser: GDScriptSymbolParser;
|
||||
// scriptpath : scenepath
|
||||
public scriptSceneMap: Object;
|
||||
// scenepath : NodeInfo[]
|
||||
public nodeInfoMap: Object;
|
||||
// symbolname: {completionItem: CompletionItem, rowDoc: docdata}
|
||||
public builtinSymbolInfoMap: Object;
|
||||
|
||||
constructor() {
|
||||
this.symbols = {};
|
||||
this.bintinSybmolInfoList = [];
|
||||
this.builtinSymbolInfoMap = {};
|
||||
this.nodeInfoMap = {};
|
||||
this.scriptSceneMap = {};
|
||||
this.parser = new GDScriptSymbolParser();
|
||||
}
|
||||
|
||||
loadSymbolsFromFile(path) {
|
||||
var ignoreIndentedVars = false;
|
||||
if(workspace)
|
||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
||||
const script = this.parser.parseFile(path, ignoreIndentedVars);
|
||||
this.setSymbols(path, script);
|
||||
return script;
|
||||
}
|
||||
|
||||
setSymbols(path, s) {
|
||||
this.symbols[this.normalizePath(path)] = s;
|
||||
}
|
||||
|
||||
getSymbols(path) {
|
||||
return this.symbols[this.normalizePath(path)];
|
||||
}
|
||||
|
||||
setAllSymbols(s) {
|
||||
this.symbols = s;
|
||||
}
|
||||
|
||||
getAllSymbols() {
|
||||
return this.symbols;
|
||||
}
|
||||
|
||||
normalizePath(path) {
|
||||
let newpath = path;
|
||||
if( path.indexOf(":") != -1){
|
||||
let parts = path.split(":");
|
||||
newpath = parts[0].toUpperCase();
|
||||
newpath += ":";
|
||||
for(let i=1; i<parts.length; i++)
|
||||
newpath += parts[i];
|
||||
}
|
||||
newpath = newpath.replace(/\\/g, "/");
|
||||
return newpath;
|
||||
}
|
||||
|
||||
loadClasses(docfile: string): boolean {
|
||||
let done: boolean = false;
|
||||
try {
|
||||
if(fs.existsSync(docfile) && fs.statSync(docfile).isFile()) {
|
||||
const content = fs.readFileSync(docfile, "utf-8");
|
||||
const docdata = JSON.parse(content);
|
||||
if(docdata.classes) {
|
||||
this.classes = docdata.classes;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if(done) {
|
||||
for (let key of Object.keys(this.classes)) {
|
||||
const classdoc = this.classes[key];
|
||||
const bintinSybmolInfoList = this.bintinSybmolInfoList;
|
||||
const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
|
||||
// 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);
|
||||
builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc};
|
||||
// methods
|
||||
const methods = classdoc.methods
|
||||
const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name)=>{
|
||||
const mi = new CompletionItem(m.name, kind);
|
||||
mi.insertText = insertAction(m.name) + (m.arguments.length==0?"()":"");
|
||||
mi.filterText = m.name
|
||||
mi.sortText = m.name
|
||||
mi.detail = m.return_type;
|
||||
let argstr = "";
|
||||
m.arguments.map(arg=>{
|
||||
argstr += `${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value.length:''}${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}`;
|
||||
mdoc += " \n\n";
|
||||
mdoc += m.description;
|
||||
mi.documentation = mdoc;
|
||||
bintinSybmolInfoList.push(mi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = {completionItem: mi, rowDoc: m};
|
||||
};
|
||||
methods.map(m=>parsMethod(m, CompletionItemKind.Method));
|
||||
// signals
|
||||
const signals = classdoc.signals;
|
||||
signals.map(s=>parsMethod(s, CompletionItemKind.Interface));
|
||||
// constants
|
||||
const constants = classdoc.constants;
|
||||
constants.map(c=>{
|
||||
const ci = new CompletionItem(c.name, CompletionItemKind.Enum);
|
||||
ci.detail = c.value;
|
||||
ci.documentation = `${classdoc.name}.${c.name} = ${c.value}`;
|
||||
bintinSybmolInfoList.push(ci);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = {completionItem: ci, rowDoc: c};
|
||||
});
|
||||
// properties
|
||||
const properties = classdoc.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};
|
||||
};
|
||||
properties.map(p=>parseProp(p));
|
||||
// theme_properties
|
||||
const theme_properties = classdoc.theme_properties;
|
||||
theme_properties.map(p=>parseProp(p));
|
||||
}
|
||||
}
|
||||
return done;
|
||||
};
|
||||
|
||||
getWorkspaceCompletionItems(): CompletionItem[] {
|
||||
let items: CompletionItem[] = [];
|
||||
for (let path of Object.keys(this.symbols)) {
|
||||
const script = this.symbols[path];
|
||||
const addScriptItems = (items, kind: CompletionItemKind, kindName:string = "Symbol", insertText = (n)=>n)=>{
|
||||
const _items: CompletionItem[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
const signature = (script.signatures && script.signatures[name])?script.signatures[name]:"";
|
||||
const cvalue = (script.constvalues && script.constvalues[name])?script.constvalues[name]:"";
|
||||
const item = new CompletionItem(name+signature, kind);
|
||||
item.sortText = name;
|
||||
item.filterText = name;
|
||||
item.detail = cvalue;
|
||||
item.insertText = insertText(name) + (signature=="()"?"()":"");
|
||||
item.documentation = (script.documents && script.documents[name])?script.documents[name]+"\r\n":"";
|
||||
item.documentation += `${kindName} defined in ${workspace.asRelativePath(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")];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
loadScene(scenePath: string) {
|
||||
if(fs.existsSync(scenePath) && fs.statSync(scenePath).isFile()) {
|
||||
try {
|
||||
const content: string = fs.readFileSync(scenePath, 'utf-8');
|
||||
if(content) {
|
||||
// extern resources
|
||||
const exteres = {};
|
||||
let reg = /ext_resource path="res:\/\/(.*)" type="(.*)" id=(\d+)/g;
|
||||
let match = reg.exec(content);
|
||||
while (match != null) {
|
||||
const path = match[1];
|
||||
const type = match[2];
|
||||
const id = match[3];
|
||||
exteres[id] = {path, type};
|
||||
if (type == "Script") {
|
||||
let workspacescenepath = scenePath;
|
||||
if(workspace)
|
||||
workspacescenepath = workspace.asRelativePath(scenePath);
|
||||
this.scriptSceneMap[path] = workspacescenepath;
|
||||
}
|
||||
match = reg.exec(content);
|
||||
}
|
||||
// nodes
|
||||
const nodes: NodeInfo[] = [];
|
||||
reg = /node\s+name="(.*)"\s+type="(.*)"\s+parent="(.*)"/g;
|
||||
match = reg.exec(content);
|
||||
while (match != null) {
|
||||
nodes.push({
|
||||
name : match[1],
|
||||
type : match[2],
|
||||
parent : match[3],
|
||||
instance: ""
|
||||
});
|
||||
match = reg.exec(content);
|
||||
}
|
||||
// packed scenes
|
||||
reg = /node name="(.*)" parent="(.*)" instance=ExtResource\(\s*(\d+)\s*\)/g;
|
||||
match = reg.exec(content);
|
||||
while (match != null) {
|
||||
const id = match[3];
|
||||
nodes.push({
|
||||
name : match[1],
|
||||
type : exteres[id].type,
|
||||
parent : match[2],
|
||||
instance: exteres[id].path
|
||||
});
|
||||
match = reg.exec(content);
|
||||
}
|
||||
if(workspace)
|
||||
scenePath = workspace.asRelativePath(scenePath);
|
||||
this.nodeInfoMap[scenePath] = nodes;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClass(name: string) {
|
||||
return this.classes[name];
|
||||
}
|
||||
|
||||
getBuiltinClassNameList() {
|
||||
let namelist = null;
|
||||
if(this.classes)
|
||||
namelist = Object.keys(this.classes);
|
||||
if(!namelist)
|
||||
namelist = [];
|
||||
return namelist;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default new Config();
|
||||
@@ -1,162 +0,0 @@
|
||||
import {
|
||||
DebugSession,
|
||||
InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent, Event,
|
||||
Thread, StackFrame, Scope, Source, Handles, Breakpoint
|
||||
} from 'vscode-debugadapter';
|
||||
import {DebugProtocol} from 'vscode-debugprotocol';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
const cmd = require('node-cmd');
|
||||
|
||||
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
||||
godot: string;
|
||||
projectDir: string;
|
||||
runWithEditor: boolean;
|
||||
params: string[];
|
||||
}
|
||||
|
||||
class GodotDebugSession extends DebugSession {
|
||||
|
||||
// we don't support multiple threads, so we can use a hardcoded ID for the default thread
|
||||
private static THREAD_ID = 1;
|
||||
|
||||
/**
|
||||
* Creates a new debug adapter that is used for one debug session.
|
||||
* We configure the default implementation of a debug adapter here.
|
||||
*/
|
||||
public constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'initialize' request is the first request called by the frontend
|
||||
* to interrogate the features the debug adapter provides.
|
||||
*/
|
||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||
|
||||
// since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
|
||||
// we request them early by sending an 'initializeRequest' to the frontend.
|
||||
// The frontend will end the configuration sequence by calling 'configurationDone' request.
|
||||
this.sendEvent(new InitializedEvent());
|
||||
|
||||
// This debug adapter implements the configurationDoneRequest.
|
||||
response.body.supportsConfigurationDoneRequest = true;
|
||||
|
||||
// make VS Code to use 'evaluate' when hovering over source
|
||||
response.body.supportsEvaluateForHovers = true;
|
||||
|
||||
// make VS Code to show a 'step back' button
|
||||
response.body.supportsStepBack = true;
|
||||
|
||||
this.log("initializeRequest");
|
||||
this.log_err("initializeRequest");
|
||||
this.log_console("initializeRequest");
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
for(let key of Object.keys(args))
|
||||
this.log(`${key} : ${args[key]}`);
|
||||
let workspaceValid = false
|
||||
if(args.godot && fs.existsSync(args.godot) && fs.statSync(args.godot).isFile() ) {
|
||||
if(args.projectDir && fs.existsSync(args.projectDir) && fs.statSync(args.projectDir).isDirectory() ) {
|
||||
let cfg = path.join(args.projectDir, "engine.cfg");
|
||||
if( fs.existsSync(cfg) && fs.statSync(cfg).isFile())
|
||||
workspaceValid = true;
|
||||
}
|
||||
}
|
||||
if(workspaceValid) {
|
||||
let params = `-path ${args.projectDir} `;
|
||||
if(args.runWithEditor)
|
||||
params += "-e";
|
||||
if(args.params) {
|
||||
for(let p of args.params)
|
||||
params += " " + p;
|
||||
}
|
||||
let cmdcontent = `${args.godot} ${params}`;
|
||||
this.log(cmdcontent)
|
||||
// TODO: print outputs in terminal console
|
||||
cmd.run(cmdcontent);
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
}
|
||||
else {
|
||||
this.log_err("Invalidate path of projectDir or godot:");
|
||||
this.log_err(JSON.stringify(args, null, '\t'));
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
||||
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||
|
||||
// return the default thread
|
||||
response.body = {
|
||||
threads: [
|
||||
new Thread(GodotDebugSession.THREAD_ID, "thread 1")
|
||||
]
|
||||
};
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line.
|
||||
*/
|
||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
||||
this.sendEvent(new TerminatedEvent());
|
||||
}
|
||||
|
||||
protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
||||
this.sendResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire StoppedEvent if line is not empty.
|
||||
*/
|
||||
private fireStepEvent(response: DebugProtocol.Response, ln: number): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
private log(msg: string) {
|
||||
const e = new OutputEvent(msg, "stdout");
|
||||
this.sendEvent(e);
|
||||
}
|
||||
private log_err(msg: string) {
|
||||
const e = new OutputEvent(msg, "stderr");
|
||||
this.sendEvent(e);
|
||||
}
|
||||
private log_console(msg: string) {
|
||||
const e = new OutputEvent(msg, "console");
|
||||
this.sendEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
DebugSession.run(GodotDebugSession);
|
||||
@@ -1,13 +1,17 @@
|
||||
'use strict';
|
||||
import { workspace, Disposable, ExtensionContext } from 'vscode';
|
||||
import WindowWatch from "./window_watcher";
|
||||
import ToolManager from './tool_manager';
|
||||
|
||||
let tool: ToolManager = null;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
tool = new ToolManager(context);
|
||||
context.subscriptions.push(tool);
|
||||
context.subscriptions.push(new WindowWatch());
|
||||
console.log("[GodotTools]: Extension Activated");
|
||||
}
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { GodotTools } from "./godot-tools";
|
||||
|
||||
|
||||
let tools: GodotTools = null;
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
tools = new GodotTools(context);
|
||||
tools.activate();
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
tools.deactivate();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import {
|
||||
CompletionItemProvider,
|
||||
Position,
|
||||
TextDocument,
|
||||
CancellationToken,
|
||||
CompletionItem,
|
||||
CompletionList,
|
||||
languages,
|
||||
Disposable,
|
||||
CompletionItemKind
|
||||
} from 'vscode';
|
||||
|
||||
import requestGodot from '../request';
|
||||
import config from '../config';
|
||||
|
||||
interface CompleteRequest {
|
||||
path: string,
|
||||
text: string,
|
||||
cursor: {
|
||||
row: number,
|
||||
column: number
|
||||
}
|
||||
}
|
||||
|
||||
interface CompletionResult {
|
||||
suggestions: string[],
|
||||
hint: string,
|
||||
prefix: string,
|
||||
valid: boolean
|
||||
}
|
||||
|
||||
class GDScriptCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
resolveCompletionItem(item : CompletionItem, token : CancellationToken) : CompletionItem | Thenable < CompletionItem > {
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptCompletionItemProvider;
|
||||
@@ -1,86 +0,0 @@
|
||||
import {
|
||||
DefinitionProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
Definition,
|
||||
Location,
|
||||
workspace,
|
||||
Uri,
|
||||
Range
|
||||
} from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import {isStr, getSelectedContent, getStrContent} from './utils';
|
||||
|
||||
class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
||||
}
|
||||
else if(fs.existsSync(content) && fs.statSync(content).isFile()) {
|
||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
||||
}
|
||||
else {
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let locations: Location[] = [];
|
||||
// check from workspace
|
||||
for (let path of Object.keys(workspaceSymbols)) {
|
||||
const script = workspaceSymbols[path];
|
||||
if(path == "autoload" && script.constpathes && script.constpathes[content])
|
||||
path = script.constpathes[content];
|
||||
let scriptitems: Location[] = [];
|
||||
const checkDifinition = (items)=>{
|
||||
const _items: Location[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
if(name == content) {
|
||||
_items.push(new Location(Uri.file(path), items[name]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.variables)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.constants)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.functions)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.signals)];
|
||||
scriptitems = [...scriptitems, ...checkDifinition(script.classes)];
|
||||
locations = [...locations, ...scriptitems];
|
||||
}
|
||||
// check from builtin
|
||||
if(config.getClass(content) != null) {
|
||||
const uri = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${content}`)));
|
||||
locations.push(new Location(Uri.parse(uri), new Range(0,0,0,0)));
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let selStr = getSelectedContent(document, position);
|
||||
if(selStr) {
|
||||
// For strings
|
||||
if(isStr(selStr)) {
|
||||
selStr = getStrContent(selStr);
|
||||
let fpath = path.join(path.dirname(document.uri.fsPath), selStr)
|
||||
if(fs.existsSync(fpath) && fs.statSync(fpath).isFile())
|
||||
selStr = fpath
|
||||
}
|
||||
resolve(getDefinitions(selStr));
|
||||
}
|
||||
else
|
||||
reject(new Error("Empty selection"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptDefinitionProivder;
|
||||
@@ -1,134 +0,0 @@
|
||||
import requestGodot from "../request";
|
||||
import * as vscode from 'vscode';
|
||||
import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
interface GDParseError {
|
||||
message: string,
|
||||
column: number,
|
||||
row: number
|
||||
}
|
||||
|
||||
interface GDScript {
|
||||
members: {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {}
|
||||
},
|
||||
base: string,
|
||||
errors: GDParseError[],
|
||||
valid: boolean,
|
||||
is_tool: boolean,
|
||||
native: string
|
||||
}
|
||||
|
||||
interface ParseRequest {
|
||||
text: string,
|
||||
path: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
class GDScriptDiagnosticSeverity {
|
||||
private _subscription: DiagnosticCollection;
|
||||
|
||||
constructor() {
|
||||
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._subscription.dispose()
|
||||
}
|
||||
|
||||
async validateScript(doc: vscode.TextDocument, script: any) {
|
||||
if(doc.languageId == 'gdscript') {
|
||||
if(script) {
|
||||
let diagnostics = [
|
||||
...(this.validateExpression(doc)),
|
||||
...(this.validateUnusedSymbols(doc, script)),
|
||||
];
|
||||
this._subscription.set(doc.uri, diagnostics);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private validateUnusedSymbols(doc: vscode.TextDocument,script) {
|
||||
let diagnostics = [];
|
||||
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;
|
||||
@@ -1,246 +0,0 @@
|
||||
import {TextDocumentContentProvider, DocumentLinkProvider, Uri, CancellationToken } from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
const linkStyle = `
|
||||
<style>
|
||||
a { color: #6e8ae7; text-decoration: none;}
|
||||
</style>
|
||||
`;
|
||||
|
||||
function genLink(title:string, uri:string, span=true):string {
|
||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
||||
let link = `<a href="${u}">${title}</a>`;
|
||||
if(span)
|
||||
link = `<span>${link}</span>`;
|
||||
return link;
|
||||
};
|
||||
|
||||
function getProp(rawDoc:any, propname: string, action=(s :string)=>s): string {
|
||||
let prop = rawDoc[propname];
|
||||
if(prop && prop.length > 0)
|
||||
prop = action(prop);
|
||||
return prop;
|
||||
}
|
||||
|
||||
class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide textual content for a given uri.
|
||||
*
|
||||
* The editor will use the returned string-content to create a readonly
|
||||
* [document](TextDocument). Resources allocated should be released when
|
||||
* the corresponding document has been [closed](#workspace.onDidCloseTextDocument).
|
||||
*
|
||||
* @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.
|
||||
* @param token A cancellation token.
|
||||
* @return A string or a thenable that resolves to such.
|
||||
*/
|
||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
|
||||
const request = uri.authority;
|
||||
let classname = request;
|
||||
let membername = null;
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
if(request.indexOf(".") != -1) {
|
||||
classname = request.substring(0, request.indexOf("."));
|
||||
if(!request.endsWith("."))
|
||||
membername = request.substring(request.indexOf(".")+1, request.length);
|
||||
}
|
||||
if(classname.length >= 1) {
|
||||
for(let key of config.getBuiltinClassNameList()) {
|
||||
if(key.toLowerCase() == classname) {
|
||||
classname = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(classname && classname.length > 0) {
|
||||
if(membername && membername.length >0 )
|
||||
resolve(self.genMemberDoc(classname, membername)) ;
|
||||
else
|
||||
resolve(self.genClassDoc(config.getClass(classname)));
|
||||
}
|
||||
reject(new Error("Open Documentation Failed!"));
|
||||
});
|
||||
}
|
||||
|
||||
genMethodDoc(mDoc:any):string {
|
||||
let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
|
||||
return `${genLink(type,type)} `;
|
||||
});
|
||||
let args = "";
|
||||
for(let arg of mDoc.arguments){
|
||||
if(mDoc.arguments.indexOf(arg)!=0)
|
||||
args += ", ";
|
||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${ret_type} ${mDoc.name} (${args}) <i>${mDoc.qualifiers}</i></h4>
|
||||
<p>${mDoc.description}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genPropDoc(pDoc:any): string {
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${genLink(pDoc.type,pDoc.type)} ${pDoc.name}</h4>
|
||||
<p>${pDoc.description}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genConstDoc(cDoc:any): string {
|
||||
let doc = `
|
||||
<li>
|
||||
<h4>${cDoc.name} = ${cDoc.value}</h4>
|
||||
<p>${cDoc.description}</p>
|
||||
</li>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genMemberDoc(classname, membername): string {
|
||||
let realDoc = null;
|
||||
const classdoc = config.getClass(classname);
|
||||
if(!classdoc)
|
||||
return null;
|
||||
for(let m of classdoc.methods) {
|
||||
if(m.name.toLowerCase() == membername) {
|
||||
realDoc = this.genMethodDoc(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!realDoc) {
|
||||
for(let s of classdoc.signals) {
|
||||
if(s.name.toLowerCase() == membername) {
|
||||
realDoc = this.genMethodDoc(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let c of classdoc.constants) {
|
||||
if(c.name.toLowerCase() == membername) {
|
||||
realDoc = this.genConstDoc(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let p of classdoc.properties) {
|
||||
if(p.name.toLowerCase() == membername) {
|
||||
realDoc = this.genPropDoc(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc) {
|
||||
for(let p of classdoc.theme_properties) {
|
||||
if(p.name.toLowerCase() == membername) {
|
||||
realDoc = this.genPropDoc(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!realDoc)
|
||||
return null;
|
||||
|
||||
let doc = `
|
||||
${linkStyle}
|
||||
<h2>Documentation of ${genLink(classname, classname)}.${membername}</h2>
|
||||
<ul>${realDoc}</ul>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
|
||||
genClassDoc(rawDoc): string {
|
||||
if(!rawDoc)
|
||||
return null;
|
||||
const classname = rawDoc.name;
|
||||
let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{
|
||||
return "<h4>Inherits: " + genLink(inherits, inherits, true) +"</h4>";
|
||||
});
|
||||
|
||||
let category = getProp(rawDoc, "category", (c:string)=>{
|
||||
return "<h4>Category: " + c +"</h4>";
|
||||
});
|
||||
|
||||
let subclasses = "";
|
||||
for(let key of config.getBuiltinClassNameList()) {
|
||||
let c = config.getClass(key);
|
||||
if(c && c.inherits == classname) {
|
||||
subclasses += genLink(key, key, true) + " "
|
||||
}
|
||||
};
|
||||
if(subclasses && subclasses.length > 0)
|
||||
subclasses = "<h3>Inherited by</h3> " + "<ul><li>" + subclasses + "</li></ul>";
|
||||
|
||||
let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{
|
||||
return "<h3>Brief Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
||||
});
|
||||
let descript = getProp(rawDoc, "description", (dec:string)=>{
|
||||
return "<h3>Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
||||
});
|
||||
|
||||
let methods = "";
|
||||
for(let m of rawDoc.methods) {
|
||||
methods += this.genMethodDoc(m);
|
||||
}
|
||||
if(methods.length >0 )
|
||||
methods = `<h3>Methods</h3><ul>${methods}</ul/>`;
|
||||
|
||||
let signals = "";
|
||||
for(let s of rawDoc.signals) {
|
||||
signals += this.genMethodDoc(s);
|
||||
}
|
||||
if(signals.length >0 )
|
||||
signals = `<h3>Signals</h3><ul>${signals}</ul/>`;
|
||||
|
||||
let props = "";
|
||||
for(let p of rawDoc.properties) {
|
||||
props += this.genPropDoc(p)
|
||||
}
|
||||
for(let p of rawDoc.theme_properties) {
|
||||
props += this.genPropDoc(p)
|
||||
}
|
||||
if(props.length >0 )
|
||||
props = `<h3>Properties</h3><ul>${props}</ul>`
|
||||
|
||||
let constants = "";
|
||||
for(let c of rawDoc.constants) {
|
||||
constants += this.genConstDoc(c);
|
||||
}
|
||||
if(constants.length >0 )
|
||||
constants = `<h3>Constants</h3><ul>${constants}</ul>`
|
||||
|
||||
let doc = `
|
||||
${linkStyle}
|
||||
<h1>Native Class ${classname}</h1>
|
||||
<p>${category}</p>
|
||||
<p>${inherits}</p>
|
||||
<p>${subclasses}</p>
|
||||
<p>${briefDescript}</p>
|
||||
<p>${descript}</p>
|
||||
<p>${methods}</p>
|
||||
<p>${signals}</p>
|
||||
<p>${constants}</p>
|
||||
<p>${props}</p>
|
||||
`;
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptDocumentContentProvider;
|
||||
@@ -1,173 +0,0 @@
|
||||
import {
|
||||
HoverProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
Hover,
|
||||
MarkedString,
|
||||
workspace,
|
||||
Uri,
|
||||
CompletionItem,
|
||||
CompletionItemKind
|
||||
} from 'vscode';
|
||||
import {
|
||||
isStr,
|
||||
getSelectedContent,
|
||||
getStrContent
|
||||
} from './utils';
|
||||
import config from '../config';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
function genLink(title:string, uri:string):string {
|
||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
||||
return `[${title}](${u})`;
|
||||
};
|
||||
|
||||
class GDScriptHoverProvider implements HoverProvider {
|
||||
constructor() {}
|
||||
|
||||
provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover | Thenable < Hover > {
|
||||
let hoverText = getSelectedContent(document, position);
|
||||
if (isStr(hoverText))
|
||||
hoverText = getStrContent(hoverText);
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let tips: MarkedString[] = [];
|
||||
const withMarkdwon = workspace.getConfiguration("GodotTools").get("workspaceDocumentWithMarkdown", false);
|
||||
|
||||
// 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 name of Object.keys(items)) {
|
||||
if (name == hoverText) {
|
||||
let dfile = path;
|
||||
if (workspace && workspace.asRelativePath(dfile))
|
||||
dfile = workspace.asRelativePath(dfile);
|
||||
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];
|
||||
if(!withMarkdwon)
|
||||
doc = "```plaintext\r\n"+doc+"\r\n```";
|
||||
doc = doc?doc+"\r\n\r\n":"";
|
||||
if(path != "autoload")
|
||||
doc += `*Defined in [${dfile}](${Uri.file(path).toString()})*`;
|
||||
_items.push(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)];
|
||||
tips = [...tips, ...scriptips];
|
||||
}
|
||||
};
|
||||
|
||||
// check from scnes
|
||||
const genNodePathTips = ()=> {
|
||||
for (let scnenepath of Object.keys(config.nodeInfoMap)) {
|
||||
const nodes: any[] = config.nodeInfoMap[scnenepath];
|
||||
for (let index = 0; index < nodes.length; index++) {
|
||||
const node:any = nodes[index];
|
||||
const fullpath = node.parent + "/" + node.name;
|
||||
if(fullpath == hoverText || fullpath.endsWith(hoverText)) {
|
||||
let filepath = scnenepath;
|
||||
if(workspace && workspace.rootPath)
|
||||
filepath = path.join(workspace.rootPath, filepath);
|
||||
let instance = "";
|
||||
if(node.instance && node.instance.length > 1) {
|
||||
let instancepath = node.instance;
|
||||
if(workspace && workspace.rootPath)
|
||||
instancepath = path.join(workspace.rootPath, instancepath);
|
||||
instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
|
||||
}
|
||||
tips = [...tips,
|
||||
`${genLink(node.type, node.type)} ${fullpath}`,
|
||||
`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// check from builtin
|
||||
const genBuiltinTips = ()=> {
|
||||
const item2MarkdStrings = (name: string,item: CompletionItem, rowDoc: any):MarkedString[] => {
|
||||
let value = "";
|
||||
let doc = item.documentation;
|
||||
// get class name
|
||||
let classname = name;
|
||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
||||
if(matchs) {
|
||||
classname = matchs[0];
|
||||
if(classname.endsWith("."))
|
||||
classname = classname.substring(0, classname.length -1);
|
||||
}
|
||||
|
||||
const genMethodMarkDown = ():string =>{
|
||||
let content = `${genLink(rowDoc.return_type, rowDoc.return_type)} `;
|
||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
||||
content += `${genLink(classname, classname)}.`;
|
||||
let args = "";
|
||||
for(let arg of rowDoc.arguments){
|
||||
if(rowDoc.arguments.indexOf(arg)!=0)
|
||||
args += ", ";
|
||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
||||
if(arg.default_value && arg.default_value.length > 0)
|
||||
args += `=${arg.default_value}`;
|
||||
}
|
||||
content += `${genLink(rowDoc.name, classname+'.'+rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
|
||||
return content;
|
||||
};
|
||||
|
||||
switch (item.kind) {
|
||||
case CompletionItemKind.Class:
|
||||
return [`Native Class ${genLink(classname, classname)}`, doc];
|
||||
case CompletionItemKind.Method:
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
return [genMethodMarkDown(), doc];
|
||||
case CompletionItemKind.Interface:
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
return ['signal ' + genMethodMarkDown(), doc];
|
||||
case CompletionItemKind.Variable:
|
||||
case CompletionItemKind.Property:
|
||||
return [`${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];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return [name, doc];
|
||||
};
|
||||
for (let name of Object.keys(config.builtinSymbolInfoMap)) {
|
||||
const pattern = `[A-z@_]+[A-z0-9_]*\\.${hoverText}\\b`;
|
||||
if(name == hoverText || name.match(new RegExp(pattern))) {
|
||||
const item: {completionItem: CompletionItem, rowDoc: any} = config.builtinSymbolInfoMap[name];
|
||||
tips = [...tips, ...(item2MarkdStrings(name, item.completionItem, item.rowDoc))];
|
||||
}
|
||||
}
|
||||
};
|
||||
genBuiltinTips();
|
||||
genWorkspaceTips();
|
||||
genNodePathTips();
|
||||
|
||||
if (tips.length > 0)
|
||||
return new Hover(tips);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptHoverProvider;
|
||||
@@ -1,109 +0,0 @@
|
||||
import {
|
||||
SignatureHelpProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
SignatureInformation,
|
||||
SignatureHelp,
|
||||
CompletionItemKind,
|
||||
ParameterInformation,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
import config from '../config';
|
||||
import { countSubStr } from './utils';
|
||||
class GDScriptSignatureHelpProvider implements SignatureHelpProvider {
|
||||
constructor() {}
|
||||
|
||||
provideSignatureHelp(document : TextDocument, position : Position, token : CancellationToken) : SignatureHelp | Thenable < SignatureHelp > {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
const res = self.do_provideSignatureHelp(document, position);
|
||||
resolve(res);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Provide help for the signature at the given position and document.
|
||||
*
|
||||
* @param document The document in which the command was invoked.
|
||||
* @param position The position at which the command was invoked.
|
||||
* @param token A cancellation token.
|
||||
* @return Signature help or a thenable that resolves to such. The lack of a result can be
|
||||
* signaled by returning `undefined` or `null`.
|
||||
*/
|
||||
do_provideSignatureHelp(document : TextDocument, position : Position) : SignatureHelp | Thenable < SignatureHelp > {
|
||||
const range = document.getWordRangeAtPosition(position);
|
||||
let funcname = "";
|
||||
let curparam = 0;
|
||||
const checkPosition = () => {
|
||||
const line = document.lineAt(position);
|
||||
const startPos = line.firstNonWhitespaceCharacterIndex;
|
||||
const endPos = position.character;
|
||||
const queryStr = line.text.substring(startPos, endPos);
|
||||
|
||||
var reg = /([A-z_]+[A-z0-9_]*)\(/g;
|
||||
let match = reg.exec(queryStr);
|
||||
while (match != null) {
|
||||
funcname = match[1];
|
||||
match = reg.exec(queryStr);
|
||||
}
|
||||
if(funcname != "") {
|
||||
const funcrangestr = line.text.substring(line.text.indexOf(queryStr)+queryStr.indexOf(funcname)+funcname.length, endPos);
|
||||
curparam = countSubStr(funcrangestr, ",");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
checkPosition();
|
||||
|
||||
let resultSignatures: SignatureInformation[] = [];
|
||||
|
||||
if (funcname.length > 0) {
|
||||
// Builtin functions
|
||||
for (let key of Object.keys(config.builtinSymbolInfoMap)) {
|
||||
if (key.endsWith(`\.${funcname}`)) {
|
||||
if (config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Method || config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Function) {
|
||||
const rawDoc = config.builtinSymbolInfoMap[key].rowDoc;
|
||||
const item = config.builtinSymbolInfoMap[key].completionItem;
|
||||
let signatureInfor: SignatureInformation = new SignatureInformation(item.documentation.split('\n')[0], rawDoc.description);
|
||||
for(let arg of rawDoc.arguments){
|
||||
let param: ParameterInformation = new ParameterInformation(`${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}`, "");
|
||||
signatureInfor.parameters.push(param);
|
||||
}
|
||||
resultSignatures.push(signatureInfor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// workspace functions
|
||||
for (let path of Object.keys(config.getAllSymbols())) {
|
||||
let script = config.getSymbols(path);
|
||||
if(!script.signatures)
|
||||
continue
|
||||
let relaPath = path;
|
||||
if(workspace && workspace.rootPath)
|
||||
relaPath = workspace.asRelativePath(relaPath);
|
||||
|
||||
for(let f of Object.keys(script.signatures)) {
|
||||
if(f == funcname) {
|
||||
const signatureStr = script.signatures[f];
|
||||
let signature: SignatureInformation = new SignatureInformation(`func ${f}${signatureStr}`, `Method defined in ${relaPath}`);
|
||||
const params = (signatureStr.substring(signatureStr.indexOf("(")+1, signatureStr.indexOf(")"))).split(",");
|
||||
for(let p of params)
|
||||
signature.parameters.push(new ParameterInformation(p, ""));
|
||||
resultSignatures.push(signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(resultSignatures.length > 0) {
|
||||
return ({
|
||||
signatures: resultSignatures,
|
||||
activeSignature: 0,
|
||||
activeParameter: curparam
|
||||
});
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSignatureHelpProvider;
|
||||
@@ -1,198 +0,0 @@
|
||||
import {Range} from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
|
||||
interface GDScript {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {},
|
||||
classes: {},
|
||||
base: string,
|
||||
native: string,
|
||||
signatures: {},
|
||||
// symbol: marked string
|
||||
documents: {},
|
||||
// name : value
|
||||
constvalues: {}
|
||||
}
|
||||
|
||||
class GDScriptSymbolParser {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
parseContent(content: string, ignoreIndentedVars:boolean = false): GDScript {
|
||||
const script: GDScript = {
|
||||
constants: {},
|
||||
functions: {},
|
||||
variables: {},
|
||||
signals: {},
|
||||
classes: {},
|
||||
base: "Object",
|
||||
native: "Object",
|
||||
signatures: {},
|
||||
documents: {},
|
||||
constvalues: {}
|
||||
}
|
||||
const text = content;
|
||||
const lines = text.split(/\r?\n/);
|
||||
|
||||
const getMatches = (regex:RegExp, index=1) => {
|
||||
var matches = [];
|
||||
for(let line of lines) {
|
||||
let match;
|
||||
if (match = regex.exec(line)) {
|
||||
let commentReg = RegExp(/#.*?/.source+regex.source);
|
||||
if(!commentReg.exec(line))
|
||||
matches.push(match[index]);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
// var matches = [];
|
||||
// var match;
|
||||
// while (match = regex.exec(string)) {
|
||||
// matches.push(match[index]);
|
||||
// }
|
||||
// return matches;
|
||||
};
|
||||
|
||||
const findLineRanges = (symbols, reg)=>{
|
||||
const sm = {};
|
||||
symbols.map((name:string)=>{
|
||||
let line = 0;
|
||||
let curline = 0;
|
||||
if(Object.keys(sm).indexOf(name) != -1) return;
|
||||
lines.map(l=>{
|
||||
const nreg = reg.replace("$X$", name);
|
||||
if(l.match(nreg) != null) {
|
||||
line = curline;
|
||||
return;
|
||||
}
|
||||
curline += 1;
|
||||
});
|
||||
sm[name] = line;
|
||||
});
|
||||
return sm;
|
||||
}
|
||||
|
||||
const determRange = (key:string, array: any): Range =>{
|
||||
let line = array[key];
|
||||
let startAt = lines[line].indexOf(key);
|
||||
if(line < 0) line = 0;
|
||||
if(startAt < 0) startAt = 0;
|
||||
return new Range(line, startAt, line, startAt + key.length);
|
||||
};
|
||||
|
||||
const parseSignature = (range: Range):string => {
|
||||
let res = "";
|
||||
const line = lines[range.start.line];
|
||||
if(line.indexOf("(")!= -1 && line.indexOf(")")!=-1) {
|
||||
const signature = line.substring(line.indexOf("("), line.indexOf(")")+1);
|
||||
if(signature && signature.length >0)
|
||||
res = signature;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const parseDocument = (range: Range):string => {
|
||||
let mdoc = ""
|
||||
let line = range.start.line;
|
||||
while( line > 0){
|
||||
const linecontent = lines[line];
|
||||
let match = linecontent.match(/\s*#\s*(.*)/);
|
||||
let commentAtEnd = linecontent.match(/[\w'",\[\{\]\}\(\)]+\s*#\s*(.*)/) != null;
|
||||
if(commentAtEnd && linecontent.match(/^#/))
|
||||
commentAtEnd = false;
|
||||
if(!match && line != range.start.line)
|
||||
break;
|
||||
if(commentAtEnd && line != range.start.line)
|
||||
break;
|
||||
if(match) {
|
||||
let lmcontent = linecontent.substring(linecontent.indexOf("#")+1, linecontent.length);
|
||||
if(lmcontent.startsWith(" ") && lmcontent != " ")
|
||||
lmcontent = lmcontent.substring(1, lmcontent.length);
|
||||
mdoc = lmcontent + "\r\n" + mdoc;
|
||||
}
|
||||
else if(line != range.start.line)
|
||||
break
|
||||
--line;
|
||||
}
|
||||
return mdoc;
|
||||
}
|
||||
|
||||
let funcsnames = getMatches(/func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
||||
const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(");
|
||||
for (let key of Object.keys(funcs)) {
|
||||
let r: Range = determRange(key, funcs);
|
||||
script.functions[key] = r;
|
||||
script.signatures[key] = parseSignature(r);
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
|
||||
let signalnames = getMatches(/signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
||||
const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(");
|
||||
for (let key of Object.keys(signals)) {
|
||||
let r: Range = determRange(key, signals);
|
||||
script.signals[key] = r;
|
||||
script.signatures[key] = parseSignature(r);
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
|
||||
let varreg = /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/;
|
||||
let varreg2 = "var\\s+$X$\\s*";
|
||||
let vargroup = 1;
|
||||
if(ignoreIndentedVars) {
|
||||
varreg = /^((export.*?var)|var)\s+([_A-Za-z]+[_A-Za-z0-9]*)\s?/;
|
||||
varreg2 = "^((export.*?var)|var)\\s+$X$\\s?";
|
||||
vargroup = 3;
|
||||
}
|
||||
let varnames = getMatches(varreg, vargroup);
|
||||
const vars = findLineRanges(varnames, varreg2);
|
||||
for (let key of Object.keys(vars)){
|
||||
const r:Range = determRange(key, vars)
|
||||
script.variables[key] = r;
|
||||
let newdoc = parseDocument(r);
|
||||
if(newdoc == "" && script.documents[key])
|
||||
newdoc = script.documents[key];
|
||||
script.documents[key] = newdoc;
|
||||
}
|
||||
|
||||
let constnames = getMatches(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/, 1);
|
||||
const consts = findLineRanges(constnames, "const\\s+$X$\\s*");
|
||||
for (let key of Object.keys(consts)){
|
||||
const r:Range = determRange(key, consts)
|
||||
script.constants[key] = r;
|
||||
let newdoc = parseDocument(r);
|
||||
if(newdoc == "" && script.documents[key])
|
||||
newdoc = script.documents[key];
|
||||
script.documents[key] = newdoc;
|
||||
|
||||
const linecontent = lines[r.start.line];
|
||||
const match = linecontent.match(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*=\s*([\w+]+\(.*\)|"[^"]*"|\-?\d+\.?\d*|\[.*\]|\{.*\})/);
|
||||
if(match && match.length && match.length >1)
|
||||
script.constvalues[key] = match[2];
|
||||
}
|
||||
|
||||
let classnames = getMatches(/class\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*extends\s+/, 1);
|
||||
const classes = findLineRanges(classnames, "class\\s+$X$\\s*extends\\s+");
|
||||
for (let key of Object.keys(classes)) {
|
||||
const r:Range = determRange(key, classes)
|
||||
script.classes[key] = determRange(key, classes);
|
||||
script.documents[key] = parseDocument(r);
|
||||
}
|
||||
// console.log(script);
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
parseFile(path:string, ignoreIndentedVars:boolean = false): GDScript {
|
||||
const self = this;
|
||||
if(fs.existsSync(path) && fs.statSync(path).isFile()){
|
||||
const content = fs.readFileSync(path, 'utf-8');
|
||||
return this.parseContent(content, ignoreIndentedVars);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSymbolParser;
|
||||
@@ -1,56 +0,0 @@
|
||||
import {
|
||||
DocumentSymbolProvider,
|
||||
TextDocument,
|
||||
SymbolInformation,
|
||||
CancellationToken,
|
||||
SymbolKind,
|
||||
Range,
|
||||
workspace
|
||||
} from 'vscode';
|
||||
|
||||
import GDScriptSymbolParser from '../gdscript/symbolparser';
|
||||
import config from '../config';
|
||||
|
||||
class GDScriptSymbolProvider implements DocumentSymbolProvider {
|
||||
private parser: GDScriptSymbolParser = null;
|
||||
|
||||
constructor() {
|
||||
this.parser = new GDScriptSymbolParser();
|
||||
}
|
||||
|
||||
provideDocumentSymbols(document: TextDocument, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]> {
|
||||
|
||||
const symbols: SymbolInformation[] = [];
|
||||
var ignoreIndentedVars = false;
|
||||
if(workspace)
|
||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
||||
const script = this.parser.parseContent(document.getText(), ignoreIndentedVars);
|
||||
const signatures = script.signatures;
|
||||
config.setSymbols(document.fileName, script);
|
||||
|
||||
const funcs = script.functions;
|
||||
for (let key of Object.keys(funcs))
|
||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Function, funcs[key]));
|
||||
|
||||
const signals = script.signals;
|
||||
for (let key of Object.keys(signals))
|
||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Interface, signals[key]));
|
||||
|
||||
const vars = script.variables;
|
||||
for (let key of Object.keys(vars))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Variable, vars[key]));
|
||||
|
||||
const consts = script.constants;
|
||||
for (let key of Object.keys(consts))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Constant, consts[key]));
|
||||
|
||||
const classes = script.classes;
|
||||
for (let key of Object.keys(classes))
|
||||
symbols.push(new SymbolInformation(key, SymbolKind.Class, classes[key]));
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GDScriptSymbolProvider;
|
||||
@@ -1,41 +0,0 @@
|
||||
import {TextDocument, Position} from 'vscode';
|
||||
|
||||
export function isStr(content:string) {
|
||||
return (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
|
||||
}
|
||||
|
||||
export function getSelectedContent(document: TextDocument, position: Position):string {
|
||||
const line = document.lineAt(position);
|
||||
const wordRange = document.getWordRangeAtPosition(position) ;
|
||||
const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*?"|'.*?'|@".*?"/g)
|
||||
let res = line.text.substring(wordRange.start.character, wordRange.end.character);
|
||||
machs.map(m=>{
|
||||
if(m) {
|
||||
const startPos = line.text.indexOf(m);
|
||||
const endPos = startPos + m.length;
|
||||
if(isStr(m) && startPos != -1 && wordRange.start.character >= startPos && wordRange.end.character <= endPos){
|
||||
res = m;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
export function getStrContent(rawstr: string):string {
|
||||
let ss = rawstr;
|
||||
if(isStr(ss)) {
|
||||
ss = ss.replace(/"|'|@"|"""/g,"")
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
export function countSubStr(str:string, sub:string): number {
|
||||
let count = 0;
|
||||
let pos = str.indexOf(sub);
|
||||
while (pos !== -1) {
|
||||
count++;
|
||||
pos = str.indexOf(sub, pos + sub.length);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import config from '../config';
|
||||
|
||||
class GDScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
|
||||
public provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): vscode.SymbolInformation[] {
|
||||
const scripts = config.getAllSymbols();
|
||||
const symbols: vscode.SymbolInformation[] = [];
|
||||
for (let path of Object.keys(scripts)) {
|
||||
const queryMembers = (query, members, kind: vscode.SymbolKind, path:string, extra=(text)=>text)=> {
|
||||
for (let name of Object.keys(members)) {
|
||||
const range: vscode.Range = members[name];
|
||||
if(name.toLowerCase().indexOf(query.toLowerCase()) != -1) {
|
||||
const symbol: vscode.SymbolInformation = {
|
||||
name: extra(name),
|
||||
kind,
|
||||
containerName: "",
|
||||
location: {
|
||||
uri: vscode.Uri.file(path),
|
||||
range
|
||||
}
|
||||
};
|
||||
symbols.push(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
const scrip = scripts[path];
|
||||
const signatures = scrip.signatures;
|
||||
queryMembers(query, scrip.functions, vscode.SymbolKind.Function, path, (name)=>(name+signatures[name]));
|
||||
queryMembers(query, scrip.signals, vscode.SymbolKind.Interface, path, (name)=>(name+signatures[name]));
|
||||
queryMembers(query, scrip.variables, vscode.SymbolKind.Variable, path);
|
||||
queryMembers(query, scrip.constants, vscode.SymbolKind.Constant, path);
|
||||
queryMembers(query, scrip.classes, vscode.SymbolKind.Class, path);
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptWorkspaceSymbolProvider;
|
||||
165
src/godot-tools.ts
Normal file
165
src/godot-tools.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import GDScriptLanguageClient, { ClientStatus } from "./lsp/GDScriptLanguageClient";
|
||||
import { get_configuration, set_configuration } from "./utils";
|
||||
|
||||
const CONFIG_CONTAINER = "godot_tools";
|
||||
const TOOL_NAME = "GodotTools";
|
||||
|
||||
export class GodotTools {
|
||||
|
||||
private context: vscode.ExtensionContext;
|
||||
private client: GDScriptLanguageClient = null;
|
||||
private workspace_dir = vscode.workspace.rootPath;
|
||||
private project_file = "project.godot";
|
||||
private connection_status: vscode.StatusBarItem = null;
|
||||
|
||||
constructor(p_context: vscode.ExtensionContext) {
|
||||
this.context = p_context;
|
||||
this.client = new GDScriptLanguageClient(p_context);
|
||||
this.client.watch_status(this.on_client_status_changed.bind(this));
|
||||
this.connection_status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
}
|
||||
|
||||
public activate() {
|
||||
vscode.commands.registerCommand("godot-tool.open_editor", ()=>{
|
||||
this.open_workspace_with_editor("-e").catch(err=>vscode.window.showErrorMessage(err));
|
||||
});
|
||||
vscode.commands.registerCommand("godot-tool.run_project", ()=>{
|
||||
this.open_workspace_with_editor().catch(err=>vscode.window.showErrorMessage(err));
|
||||
});
|
||||
vscode.commands.registerCommand("godot-tool.check_status", this.check_client_status.bind(this));
|
||||
|
||||
this.connection_status.text = "$(sync) Initializing";
|
||||
this.connection_status.command = "godot-tool.check_status";
|
||||
this.connection_status.show();
|
||||
this.client.connect_to_server();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public deactivate() {
|
||||
this.client.stop();
|
||||
}
|
||||
|
||||
|
||||
private open_workspace_with_editor(params = "") {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let valid = false
|
||||
if (this.workspace_dir) {
|
||||
let cfg = path.join(this.workspace_dir, this.project_file);
|
||||
valid = (fs.existsSync(cfg) && fs.statSync(cfg).isFile());
|
||||
}
|
||||
if (valid) {
|
||||
this.run_editor(`--path "${this.workspace_dir}" ${params}`).then(()=>resolve()).catch(err=>{
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
reject("Current workspace is not a Godot project");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private run_editor(params = "") {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const run_godot = (path: string, params: string) => {
|
||||
const escape_command = (cmd: string) => {
|
||||
let cmdEsc = `"${cmd}"`;
|
||||
const shell_plugin = vscode.workspace.getConfiguration("terminal.integrated.shell");
|
||||
let shell = shell_plugin ? shell_plugin.get("windows", "") || "" : "";
|
||||
if (shell.endsWith("powershell.exe") && process.platform === "win32") {
|
||||
cmdEsc = `&${cmdEsc}`;
|
||||
}
|
||||
return cmdEsc;
|
||||
};
|
||||
let existingTerminal = vscode.window.terminals.find(t => t.name === TOOL_NAME)
|
||||
if (existingTerminal) {
|
||||
existingTerminal.dispose()
|
||||
}
|
||||
let terminal = vscode.window.createTerminal(TOOL_NAME);
|
||||
let editorPath = escape_command(path);
|
||||
let cmmand = `${editorPath} ${params}`;
|
||||
terminal.sendText(cmmand, true);
|
||||
terminal.show();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let editorPath = get_configuration("editor_path", "")
|
||||
editorPath = editorPath.replace("${workspaceRoot}", this.workspace_dir);
|
||||
if (!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
|
||||
vscode.window.showOpenDialog({
|
||||
openLabel: "Run",
|
||||
filters: process.platform === "win32" ? {"Godot Editor Binary": ["exe", "EXE"]} : undefined
|
||||
}).then((uris: vscode.Uri[])=> {
|
||||
if (!uris) return;
|
||||
let path = uris[0].fsPath;
|
||||
if (!fs.existsSync(path) || !fs.statSync(path).isFile()) {
|
||||
reject("Invalid editor path to run the project");
|
||||
} else {
|
||||
run_godot(path, params);
|
||||
set_configuration("editor_path", path);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
run_godot(editorPath, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private check_client_status() {
|
||||
switch (this.client.status) {
|
||||
case ClientStatus.PENDING:
|
||||
vscode.window.showInformationMessage("Connecting to GDScript language server");
|
||||
break;
|
||||
case ClientStatus.CONNECTED:
|
||||
vscode.window.showInformationMessage("Connected to GDScript language server");
|
||||
break;
|
||||
case ClientStatus.DISCONNECTED:
|
||||
this.retry_connect_client();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private on_client_status_changed(status: ClientStatus) {
|
||||
this.connection_status.color = vscode.ThemeColor;
|
||||
switch (status) {
|
||||
case ClientStatus.PENDING:
|
||||
this.connection_status.text = `$(sync) Connecting`;
|
||||
this.connection_status.tooltip = `Connecting to GDScript Language Server`;
|
||||
break;
|
||||
case ClientStatus.CONNECTED:
|
||||
this.connection_status.text = `$(check) Connected`;
|
||||
this.connection_status.tooltip = `Connected to GDScript Language Server`;
|
||||
if (!this.client.started) {
|
||||
this.context.subscriptions.push(this.client.start());
|
||||
}
|
||||
break;
|
||||
case ClientStatus.DISCONNECTED:
|
||||
this.connection_status.text = `$(x) Disconnected`;
|
||||
this.connection_status.tooltip = `Disconnect to GDScript Language Server`;
|
||||
// retry
|
||||
this.retry_connect_client();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private retry_connect_client() {
|
||||
vscode.window.showErrorMessage(`Failed connect to GDScript Language Server`, 'Open Godot Editor', 'Retry', 'Ignore').then(item=>{
|
||||
if (item == 'Retry') {
|
||||
this.client.connect_to_server();
|
||||
} else if (item == 'Open Godot Editor') {
|
||||
this.client.status = ClientStatus.PENDING;
|
||||
this.open_workspace_with_editor("-e").then(()=>{
|
||||
setTimeout(()=>{
|
||||
this.client.connect_to_server();
|
||||
}, 10 * 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
47
src/loggger.ts
Normal file
47
src/loggger.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export class Logger {
|
||||
protected buffer: string = "";
|
||||
protected tag: string = '';
|
||||
protected time: boolean = false;
|
||||
|
||||
constructor(tag: string, time: boolean) {
|
||||
this.tag = tag;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.buffer = "";
|
||||
}
|
||||
|
||||
log(...messages) {
|
||||
|
||||
let line = '';
|
||||
if (this.tag) {
|
||||
line += `[${this.tag}]`;
|
||||
}
|
||||
if (this.time) {
|
||||
line += `[${new Date().toISOString()}]`;
|
||||
}
|
||||
if (line) {
|
||||
line += ' ';
|
||||
}
|
||||
|
||||
for (let index = 0; index < messages.length; index++) {
|
||||
line += messages[index];
|
||||
if (index < messages.length) {
|
||||
line += " ";
|
||||
} else {
|
||||
line += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
this.buffer += line;
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
get_buffer(): string {
|
||||
return this.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger('godot-tools', true);
|
||||
export default logger;
|
||||
141
src/lsp/GDScriptLanguageClient.ts
Normal file
141
src/lsp/GDScriptLanguageClient.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions, RequestMessage } from "vscode-languageclient";
|
||||
import { is_debug_mode, get_configuration } from "../utils";
|
||||
import { MessageIO, MessageIOReader, MessageIOWriter, Message } from "./MessageIO";
|
||||
import logger from "../loggger";
|
||||
import { EventEmitter } from "events";
|
||||
import NativeDocumentManager from './NativeDocumentManager';
|
||||
|
||||
function getClientOptions(): LanguageClientOptions {
|
||||
return {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [
|
||||
{ scheme: "file", language: "gdscript" },
|
||||
{ scheme: "untitled", language: "gdscript" },
|
||||
],
|
||||
synchronize: {
|
||||
// Notify the server about file changes to '.gd files contain in the workspace
|
||||
// fileEvents: workspace.createFileSystemWatcher("**/*.gd"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function get_server_uri() : string {
|
||||
let port = get_configuration("gdscript_lsp_server_port", 6008);
|
||||
return `ws://localhost:${port}`;
|
||||
}
|
||||
|
||||
const io = new MessageIO(get_server_uri());
|
||||
const serverOptions: ServerOptions = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({reader: new MessageIOReader(io), writer: new MessageIOWriter(io)});
|
||||
});
|
||||
};
|
||||
|
||||
export enum ClientStatus {
|
||||
PENDING,
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
}
|
||||
const CUSTOM_MESSAGE = "gdscrip_client/";
|
||||
|
||||
export default class GDScriptLanguageClient extends LanguageClient {
|
||||
|
||||
public io: MessageIO = io;
|
||||
|
||||
private context: vscode.ExtensionContext;
|
||||
private _started : boolean = false;
|
||||
private _status : ClientStatus;
|
||||
private _status_changed_callbacks: ((v : ClientStatus)=>void)[] = [];
|
||||
private _initialize_request: Message = null;
|
||||
private message_handler: MessageHandler = null;
|
||||
private native_doc_manager: NativeDocumentManager = null;
|
||||
|
||||
public get started() : boolean { return this._started; }
|
||||
public get status() : ClientStatus { return this._status; }
|
||||
public set status(v : ClientStatus) {
|
||||
if (this._status != v) {
|
||||
this._status = v;
|
||||
for (const callback of this._status_changed_callbacks) {
|
||||
callback(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public watch_status(callback: (v : ClientStatus)=>void) {
|
||||
if (this._status_changed_callbacks.indexOf(callback) == -1) {
|
||||
this._status_changed_callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
super(`GDScriptLanguageClient`, serverOptions, getClientOptions());
|
||||
this.context = context;
|
||||
this.status = ClientStatus.PENDING;
|
||||
this.message_handler = new MessageHandler();
|
||||
this.io.on('disconnected', this.on_disconnected.bind(this));
|
||||
this.io.on('connected', this.on_connected.bind(this));
|
||||
this.io.on('message', this.on_message.bind(this));
|
||||
this.io.on('send_message', this.on_send_message.bind(this));
|
||||
this.native_doc_manager = new NativeDocumentManager(this.io);
|
||||
}
|
||||
|
||||
connect_to_server() {
|
||||
this.status = ClientStatus.PENDING;
|
||||
io.connect_to_language_server();
|
||||
}
|
||||
|
||||
start(): vscode.Disposable {
|
||||
this._started = true;
|
||||
return super.start();
|
||||
}
|
||||
|
||||
private on_send_message(message: Message) {
|
||||
if (is_debug_mode()) logger.log("[client]", JSON.stringify(message));
|
||||
if ((message as RequestMessage).method == "initialize") {
|
||||
this._initialize_request = message;
|
||||
}
|
||||
}
|
||||
|
||||
private on_message(message: Message) {
|
||||
if (is_debug_mode()) logger.log("[server]", JSON.stringify(message));
|
||||
this.message_handler.on_message(message);
|
||||
}
|
||||
|
||||
private on_connected() {
|
||||
if (this._initialize_request) {
|
||||
this.io.writer.write(this._initialize_request);
|
||||
}
|
||||
this.status = ClientStatus.CONNECTED;
|
||||
}
|
||||
|
||||
private on_disconnected() {
|
||||
this.status = ClientStatus.DISCONNECTED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MessageHandler extends EventEmitter {
|
||||
|
||||
changeWorkspace(params: {path: string}) {
|
||||
vscode.window.showErrorMessage("The GDScript Language Server can't work properly!\nThe opening workspace is diffrent with the editor's.", 'Reload', 'Ignore').then(item=>{
|
||||
if (item == "Reload") {
|
||||
let folderUrl = vscode.Uri.file(params.path);
|
||||
vscode.commands.executeCommand('vscode.openFolder', folderUrl, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
on_message(message: any) {
|
||||
if (message && message.method && (message.method as string).startsWith(CUSTOM_MESSAGE)) {
|
||||
const method = (message.method as string).substring(CUSTOM_MESSAGE.length, message.method.length);
|
||||
if (this[method]) {
|
||||
let ret = this[method](message.params);
|
||||
if (ret) {
|
||||
io.writer.write(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/lsp/MessageBuffer.ts
Normal file
87
src/lsp/MessageBuffer.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
const DefaultSize: number = 8192;
|
||||
const CR: number = Buffer.from('\r', 'ascii')[0];
|
||||
const LF: number = Buffer.from('\n', 'ascii')[0];
|
||||
const CRLF: string = '\r\n';
|
||||
|
||||
export default class MessageBuffer {
|
||||
|
||||
private encoding: string;
|
||||
private index: number;
|
||||
private buffer: Buffer;
|
||||
|
||||
constructor(encoding: string = 'utf8') {
|
||||
this.encoding = encoding;
|
||||
this.index = 0;
|
||||
this.buffer = Buffer.allocUnsafe(DefaultSize);
|
||||
}
|
||||
|
||||
public append(chunk: Buffer | String): void {
|
||||
var toAppend: Buffer = <Buffer>chunk;
|
||||
if (typeof (chunk) === 'string') {
|
||||
var str = <string>chunk;
|
||||
var bufferLen = Buffer.byteLength(str, this.encoding);
|
||||
toAppend = Buffer.allocUnsafe(bufferLen);
|
||||
toAppend.write(str, 0, bufferLen, this.encoding);
|
||||
}
|
||||
if (this.buffer.length - this.index >= toAppend.length) {
|
||||
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
|
||||
} else {
|
||||
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
|
||||
if (this.index === 0) {
|
||||
this.buffer = Buffer.allocUnsafe(newSize);
|
||||
toAppend.copy(this.buffer, 0, 0, toAppend.length);
|
||||
} else {
|
||||
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
|
||||
}
|
||||
}
|
||||
this.index += toAppend.length;
|
||||
}
|
||||
|
||||
public tryReadHeaders(): { [key: string]: string; } | undefined {
|
||||
let result: { [key: string]: string; } | undefined = undefined;
|
||||
let current = 0;
|
||||
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
|
||||
current++;
|
||||
}
|
||||
// No header / body separator found (e.g CRLFCRLF)
|
||||
if (current + 3 >= this.index) {
|
||||
return result;
|
||||
}
|
||||
result = Object.create(null);
|
||||
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
|
||||
headers.forEach((header) => {
|
||||
let index: number = header.indexOf(':');
|
||||
if (index === -1) {
|
||||
throw new Error('Message header must separate key and value using :');
|
||||
}
|
||||
let key = header.substr(0, index);
|
||||
let value = header.substr(index + 1).trim();
|
||||
result![key] = value;
|
||||
})
|
||||
|
||||
let nextStart = current + 4;
|
||||
this.buffer = this.buffer.slice(nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
}
|
||||
|
||||
public tryReadContent(length: number): string | null {
|
||||
if (this.index < length) {
|
||||
return null;
|
||||
}
|
||||
let result = this.buffer.toString(this.encoding, 0, length);
|
||||
let nextStart = length;
|
||||
this.buffer.copy(this.buffer, 0, nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
}
|
||||
|
||||
public get numberOfBytes(): number {
|
||||
return this.index;
|
||||
}
|
||||
};
|
||||
200
src/lsp/MessageIO.ts
Normal file
200
src/lsp/MessageIO.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { AbstractMessageReader, MessageReader, DataCallback } from "vscode-jsonrpc/lib/messageReader";
|
||||
import { EventEmitter } from "events";
|
||||
import * as WebSocket from 'ws';
|
||||
import MessageBuffer from "./MessageBuffer";
|
||||
import { AbstractMessageWriter, MessageWriter } from "vscode-jsonrpc/lib/messageWriter";
|
||||
import { RequestMessage, ResponseMessage, NotificationMessage } from "vscode-jsonrpc/lib/messages";
|
||||
export type Message = RequestMessage | ResponseMessage | NotificationMessage;
|
||||
|
||||
export class MessageIO extends EventEmitter {
|
||||
|
||||
reader: MessageIOReader = null;
|
||||
writer: MessageIOWriter = null;
|
||||
|
||||
private socket: WebSocket = null;
|
||||
private url: string = "";
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public send_message(message: string) {
|
||||
if (this.socket) {
|
||||
this.socket.send(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected on_message(chunk: WebSocket.Data) {
|
||||
let message = chunk.toString();
|
||||
this.emit('data', message);
|
||||
}
|
||||
|
||||
on_send_message(message: any) {
|
||||
this.emit("send_message", message);
|
||||
}
|
||||
|
||||
on_message_callback(message: any) {
|
||||
this.emit("message", message);
|
||||
}
|
||||
|
||||
connect_to_language_server():Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = null;
|
||||
const ws = new WebSocket(this.url);
|
||||
ws.on('open', ()=>{ this.on_connected(ws); resolve(); });
|
||||
ws.on('message', this.on_message.bind(this));
|
||||
ws.on('error', this.on_disconnected.bind(this));
|
||||
ws.on('close', this.on_disconnected.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
private on_connected(socket: WebSocket) {
|
||||
this.socket = socket;
|
||||
this.emit("connected");
|
||||
}
|
||||
|
||||
private on_disconnected() {
|
||||
this.socket = null;
|
||||
this.emit('disconnected');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export class MessageIOReader extends AbstractMessageReader implements MessageReader {
|
||||
|
||||
private io: MessageIO;
|
||||
private callback: DataCallback;
|
||||
private buffer: MessageBuffer;
|
||||
private nextMessageLength: number;
|
||||
private messageToken: number;
|
||||
private partialMessageTimer: NodeJS.Timer | undefined;
|
||||
private _partialMessageTimeout: number;
|
||||
|
||||
public constructor(io: MessageIO, encoding: string = 'utf8') {
|
||||
super();
|
||||
this.io = io;
|
||||
this.io.reader = this;
|
||||
this.buffer = new MessageBuffer(encoding);
|
||||
this._partialMessageTimeout = 10000;
|
||||
}
|
||||
|
||||
public set partialMessageTimeout(timeout: number) {
|
||||
this._partialMessageTimeout = timeout;
|
||||
}
|
||||
|
||||
public get partialMessageTimeout(): number {
|
||||
return this._partialMessageTimeout;
|
||||
}
|
||||
|
||||
public listen(callback: DataCallback): void {
|
||||
this.nextMessageLength = -1;
|
||||
this.messageToken = 0;
|
||||
this.partialMessageTimer = undefined;
|
||||
this.callback = callback;
|
||||
this.io.on('data', (data: Buffer) => {
|
||||
this.onData(data);
|
||||
});
|
||||
this.io.on('error', (error: any) => this.fireError(error));
|
||||
this.io.on('close', () => this.fireClose());
|
||||
}
|
||||
|
||||
private onData(data: Buffer | String): void {
|
||||
this.buffer.append(data);
|
||||
while (true) {
|
||||
if (this.nextMessageLength === -1) {
|
||||
let headers = this.buffer.tryReadHeaders();
|
||||
if (!headers) {
|
||||
return;
|
||||
}
|
||||
let contentLength = headers['Content-Length'];
|
||||
if (!contentLength) {
|
||||
throw new Error('Header must provide a Content-Length property.');
|
||||
}
|
||||
let length = parseInt(contentLength);
|
||||
if (isNaN(length)) {
|
||||
throw new Error('Content-Length value must be a number.');
|
||||
}
|
||||
this.nextMessageLength = length;
|
||||
// Take the encoding form the header. For compatibility
|
||||
// treat both utf-8 and utf8 as node utf8
|
||||
}
|
||||
var msg = this.buffer.tryReadContent(this.nextMessageLength);
|
||||
if (msg === null) {
|
||||
/** We haven't received the full message yet. */
|
||||
this.setPartialMessageTimer();
|
||||
return;
|
||||
}
|
||||
this.clearPartialMessageTimer();
|
||||
this.nextMessageLength = -1;
|
||||
this.messageToken++;
|
||||
var json = JSON.parse(msg);
|
||||
this.callback(json);
|
||||
// callback
|
||||
this.io.on_message_callback(json);
|
||||
}
|
||||
}
|
||||
|
||||
private clearPartialMessageTimer(): void {
|
||||
if (this.partialMessageTimer) {
|
||||
clearTimeout(this.partialMessageTimer);
|
||||
this.partialMessageTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private setPartialMessageTimer(): void {
|
||||
this.clearPartialMessageTimer();
|
||||
if (this._partialMessageTimeout <= 0) {
|
||||
return;
|
||||
}
|
||||
this.partialMessageTimer = setTimeout((token, timeout) => {
|
||||
this.partialMessageTimer = undefined;
|
||||
if (token === this.messageToken) {
|
||||
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
|
||||
this.setPartialMessageTimer();
|
||||
}
|
||||
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
const ContentLength: string = 'Content-Length: ';
|
||||
const CRLF = '\r\n';
|
||||
export class MessageIOWriter extends AbstractMessageWriter implements MessageWriter {
|
||||
|
||||
private io: MessageIO;
|
||||
private encoding: string;
|
||||
private errorCount: number;
|
||||
|
||||
public constructor(io: MessageIO, encoding: string = 'utf8') {
|
||||
super();
|
||||
this.io = io;
|
||||
this.io.writer = this;
|
||||
this.encoding = encoding;
|
||||
this.errorCount = 0;
|
||||
this.io.on('error', (error: any) => this.fireError(error));
|
||||
this.io.on('close', () => this.fireClose());
|
||||
}
|
||||
|
||||
public write(msg: Message): void {
|
||||
let json = JSON.stringify(msg);
|
||||
let contentLength = Buffer.byteLength(json, this.encoding);
|
||||
|
||||
let headers: string[] = [
|
||||
ContentLength, contentLength.toString(), CRLF,
|
||||
CRLF
|
||||
];
|
||||
try {
|
||||
// callback
|
||||
this.io.on_send_message(msg);
|
||||
// Header must be written in ASCII encoding
|
||||
this.io.send_message(headers.join(''));
|
||||
// Now write the content. This can be written in any encoding
|
||||
this.io.send_message(json);
|
||||
this.errorCount = 0;
|
||||
} catch (error) {
|
||||
this.errorCount++;
|
||||
this.fireError(error, msg, this.errorCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
515
src/lsp/NativeDocumentManager.ts
Normal file
515
src/lsp/NativeDocumentManager.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { EventEmitter } from "events";
|
||||
import { MessageIO } from "./MessageIO";
|
||||
import { NotificationMessage } from "vscode-jsonrpc";
|
||||
import { DocumentSymbol } from "vscode";
|
||||
import * as Prism from "prismjs";
|
||||
import * as marked from "marked";
|
||||
marked.setOptions({
|
||||
highlight: function (code, lang) {
|
||||
return Prism.highlight(code, GDScriptGrammar, lang);
|
||||
}
|
||||
});
|
||||
|
||||
const enum Methods {
|
||||
SHOW_NATIVE_SYMBOL = 'gdscript/show_native_symbol',
|
||||
INSPECT_NATIVE_SYMBOL = 'textDocument/nativeSymbol'
|
||||
}
|
||||
|
||||
interface NativeSymbolInspectParams {
|
||||
native_class: string;
|
||||
symbol_name: string;
|
||||
}
|
||||
|
||||
const enum WebViewMessageType {
|
||||
INSPECT_NATIVE_SYMBOL = 'INSPECT_NATIVE_SYMBOL',
|
||||
};
|
||||
|
||||
class GodotNativeSymbol extends DocumentSymbol {
|
||||
documentation: string;
|
||||
native_class: string;
|
||||
};
|
||||
|
||||
export default class NativeDocumentManager extends EventEmitter {
|
||||
|
||||
private io: MessageIO = null;
|
||||
|
||||
constructor(io: MessageIO) {
|
||||
super();
|
||||
this.io = io;
|
||||
io.on("message", (message: NotificationMessage)=>{
|
||||
if (message.method == Methods.SHOW_NATIVE_SYMBOL) {
|
||||
this.show_native_symbol(message.params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inspect_native_symbol(params: NativeSymbolInspectParams) {
|
||||
this.io.send_message(JSON.stringify({
|
||||
id: -1,
|
||||
jsonrpc: "2.0",
|
||||
method: Methods.INSPECT_NATIVE_SYMBOL,
|
||||
params
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
private show_native_symbol(symbol: GodotNativeSymbol) {
|
||||
// 创建webview
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
'doc',
|
||||
symbol.name,
|
||||
vscode.ViewColumn.Nine,
|
||||
{
|
||||
enableScripts: true, // 启用JS,默认禁用
|
||||
retainContextWhenHidden: false, // webview被隐藏时保持状态,避免被重置
|
||||
}
|
||||
);
|
||||
panel.title = symbol.name;
|
||||
panel.webview.html = this.make_html_content(symbol);
|
||||
panel.webview.onDidReceiveMessage(this.on_webview_message.bind(this));
|
||||
}
|
||||
|
||||
private on_webview_message(msg: any) {
|
||||
switch (msg.type) {
|
||||
case WebViewMessageType.INSPECT_NATIVE_SYMBOL:
|
||||
this.inspect_native_symbol(msg.data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private make_html_content(symbol: GodotNativeSymbol): string {
|
||||
return `
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
${PrismStyleSheet}
|
||||
.codeblock {
|
||||
padding: 0.5em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
!background-color: #fdf6e3;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="line-height: 16pt;">${this.make_symbol_document(symbol)}</body>
|
||||
<script>
|
||||
var vscode = acquireVsCodeApi();
|
||||
function inspect(native_class, symbol_name) {
|
||||
if (typeof(godot_class) != 'undefined' && godot_class == native_class) {
|
||||
document.getElementById(symbol_name).scrollIntoView();
|
||||
} else {
|
||||
vscode.postMessage({
|
||||
type: '${WebViewMessageType.INSPECT_NATIVE_SYMBOL}',
|
||||
data: {
|
||||
native_class: native_class,
|
||||
symbol_name: symbol_name
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
|
||||
private make_symbol_document(symbol: GodotNativeSymbol): string {
|
||||
|
||||
function make_function_signature(s: GodotNativeSymbol) {
|
||||
let parts = /\((.*)?\)\s*\-\>\s*(([A-z0-9]+)?)$/.exec(s.detail);
|
||||
if (!parts) return "";
|
||||
const ret_type = make_link(parts[2] || "void", undefined);
|
||||
let args = (parts[1] || "").replace(/\:\s([A-z0-9_]+)(\,\s*)?/g, `: <a href="" onclick="inspect('$1', '$1')">$1</a>$2`);
|
||||
args = args.replace(/\s=\s(.*?)[\,\)]/g, "")
|
||||
return `${ret_type} ${element("a", s.name, {href: `#${s.name}`})}( ${args} )`;
|
||||
};
|
||||
|
||||
function make_symbol_elements(s: GodotNativeSymbol): {index?: string, body: string} {
|
||||
switch (s.kind) {
|
||||
case vscode.SymbolKind.Property:
|
||||
case vscode.SymbolKind.Variable: {
|
||||
// var Control.anchor_left: float
|
||||
const parts = /\.([A-z_0-9]+)\:\s(.*)$/.exec(s.detail);
|
||||
if (!parts) return;
|
||||
let type = make_link(parts[2], undefined);
|
||||
let name = element("a", s.name, {href: `#${s.name}`});
|
||||
const title = element('h4', type + " " + s.name);
|
||||
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
|
||||
const div = element("div", title + doc);
|
||||
return {
|
||||
index: type + " " + name,
|
||||
body: div,
|
||||
};
|
||||
} break;
|
||||
case vscode.SymbolKind.Constant: {
|
||||
// const Control.FOCUS_ALL: FocusMode = 2
|
||||
// const Control.NOTIFICATION_RESIZED = 40
|
||||
const parts = /\.([A-Za-z_0-9]+)(\:\s*)?([A-z0-9_\.]+)?\s*=\s*(.*)$/.exec(s.detail);
|
||||
if (!parts) return;
|
||||
let type = make_link(parts[3] || 'int', undefined);
|
||||
let name = parts[1];
|
||||
let value = element('code', parts[4]);
|
||||
|
||||
const title = element('p', type + " " + name + " = " + value);
|
||||
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
|
||||
const div = element("div", title + doc);
|
||||
return {
|
||||
body: div
|
||||
};
|
||||
} break;
|
||||
case vscode.SymbolKind.Event: {
|
||||
const parts = /\.([A-z0-9]+)\((.*)?\)/.exec(s.detail);
|
||||
if (!parts) return;
|
||||
const args = (parts[2] || "").replace(/\:\s([A-z0-9_]+)(\,\s*)?/g, `: <a href="" onclick="inspect('$1', '$1')">$1</a>$2`);
|
||||
const title = element('p', `${s.name}( ${args} )`);
|
||||
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
|
||||
const div = element("div", title + doc);
|
||||
return {
|
||||
body: div
|
||||
};
|
||||
} break;
|
||||
case vscode.SymbolKind.Method:
|
||||
case vscode.SymbolKind.Function: {
|
||||
const signature = make_function_signature(s);
|
||||
const title = element("h4", signature);
|
||||
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
|
||||
const div = element("div", title + doc);
|
||||
return {
|
||||
index: signature,
|
||||
body: div
|
||||
};
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (symbol.kind == vscode.SymbolKind.Class) {
|
||||
|
||||
let doc = element("h2", `Native class ${symbol.name}`);
|
||||
const parts = /extends\s+([A-z0-9]+)/.exec(symbol.detail);
|
||||
let inherits = parts && parts.length > 1 ? parts[1] : '';
|
||||
if (inherits) {
|
||||
inherits = `Inherits ${make_link(inherits, undefined)}`;
|
||||
doc += element("p", inherits);
|
||||
}
|
||||
|
||||
let constants = "";
|
||||
let signals = "";
|
||||
let methods_index = "";
|
||||
let methods = "";
|
||||
let properties_index = "";
|
||||
let propertyies = "";
|
||||
let others = "";
|
||||
|
||||
for (let s of symbol.children as GodotNativeSymbol[]) {
|
||||
const elements = make_symbol_elements(s);
|
||||
switch (s.kind) {
|
||||
case vscode.SymbolKind.Property:
|
||||
case vscode.SymbolKind.Variable:
|
||||
properties_index += element("li", elements.index);
|
||||
propertyies += element("li", elements.body, {id: s.name});
|
||||
break;
|
||||
case vscode.SymbolKind.Constant:
|
||||
constants += element("li", elements.body, {id: s.name});
|
||||
break;
|
||||
case vscode.SymbolKind.Event:
|
||||
signals += element("li", elements.body, {id: s.name});
|
||||
break;
|
||||
case vscode.SymbolKind.Method:
|
||||
case vscode.SymbolKind.Function:
|
||||
methods_index += element("li", elements.index);
|
||||
methods += element("li", elements.body, {id: s.name});
|
||||
break;
|
||||
default:
|
||||
others += element("li", elements.body, {id: s.name});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function add_group(title: string, block: string) {
|
||||
if (block) {
|
||||
doc += element('h3', title);
|
||||
doc += element('ul', block);
|
||||
}
|
||||
};
|
||||
add_group("Properties", properties_index);
|
||||
add_group("Constants", constants);
|
||||
add_group("Signals", signals);
|
||||
add_group("Methods", methods_index);
|
||||
add_group("Property Descriptions", propertyies);
|
||||
add_group("Method Descriptions", methods);
|
||||
add_group("Other Members", others);
|
||||
doc += element("script", `var godot_class = "${symbol.native_class}";`);
|
||||
|
||||
return doc;
|
||||
} else {
|
||||
let doc = "";
|
||||
const elements = make_symbol_elements(symbol);
|
||||
if (elements.index) {
|
||||
doc += element("h2", elements.index);
|
||||
}
|
||||
doc += element("div", elements.body);
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function element<K extends keyof HTMLElementTagNameMap>(tag: K, content: string, props = {}, new_line?: boolean, indent?:string) {
|
||||
let props_str = "";
|
||||
for (const key in props) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
props_str += ` ${key}="${props[key]}"`;
|
||||
}
|
||||
}
|
||||
return `${indent || ''}<${tag} ${props_str}>${content}</${tag}>${new_line ? '\n' : ''}`;
|
||||
}
|
||||
function make_link(classname: string, symbol: string) {
|
||||
if (!symbol || symbol == classname) {
|
||||
return element('a', classname, {onclick: `inspect('${classname}', '${classname}')`, href: ''});
|
||||
} else {
|
||||
return element('a', `${classname}.${symbol}`, {onclick: `inspect('${classname}', '${symbol}')`, href: ''});
|
||||
}
|
||||
}
|
||||
|
||||
function make_codeblock(code: string) {
|
||||
const md = marked('```gdscript\n' + code + '\n```');
|
||||
return `<div class="codeblock">${md}</div>`;
|
||||
}
|
||||
|
||||
function format_documentation(p_bbcode: string, classname: string) {
|
||||
let html = p_bbcode.trim();
|
||||
let lines = html.split("\n");
|
||||
let in_code_block = false;
|
||||
let code_block_indent = -1;
|
||||
let cur_code_block = "";
|
||||
|
||||
html = "";
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
let block_start = line.indexOf("[codeblock]");
|
||||
if (block_start != -1) {
|
||||
code_block_indent = block_start;
|
||||
in_code_block = true;
|
||||
line = line.replace("[codeblock]", "");
|
||||
} else if (in_code_block) {
|
||||
line = line.substr(code_block_indent, line.length);
|
||||
}
|
||||
|
||||
if (in_code_block && line.indexOf("[/codeblock]") != -1) {
|
||||
line = line.replace("[/codeblock]", "");
|
||||
in_code_block = false;
|
||||
html += make_codeblock(cur_code_block);
|
||||
cur_code_block = "";
|
||||
}
|
||||
|
||||
if (!in_code_block) {
|
||||
line = line.trim();
|
||||
// [i] [/u] [code] --> <i> </u> <code>
|
||||
line = line.replace(/(\[(\/?)([a-z]+)\])/g, `<$2$3>`);
|
||||
// [Reference] --> <a>Reference</a>
|
||||
line = line.replace(/(\[([A-Z]+[A-Z_a-z0-9]*)\])/g, `<a href="" onclick="inspect('$2', '$2')">$2</a>`);
|
||||
// [method _set] --> <a>_set</a>
|
||||
line = line.replace(/(\[([a-z]+)\s+([A-Z_a-z][A-Z_a-z0-9]*)\])/g, `<a href="" onclick="inspect('${classname}', '$3')">$3</a>`);
|
||||
line += "<br/>";
|
||||
html += line;
|
||||
} else {
|
||||
line += "\n";
|
||||
if (cur_code_block || line.trim()) {
|
||||
cur_code_block += line;
|
||||
}
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
const GDScriptGrammar = {
|
||||
'comment': {
|
||||
pattern: /(^|[^\\])#.*/,
|
||||
lookbehind: true
|
||||
},
|
||||
'string-interpolation': {
|
||||
pattern: /(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,
|
||||
greedy: true,
|
||||
inside: {
|
||||
'interpolation': {
|
||||
// "{" <expression> <optional "!s", "!r", or "!a"> <optional ":" format specifier> "}"
|
||||
pattern: /((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,
|
||||
lookbehind: true,
|
||||
inside: {
|
||||
'format-spec': {
|
||||
pattern: /(:)[^:(){}]+(?=}$)/,
|
||||
lookbehind: true
|
||||
},
|
||||
'conversion-option': {
|
||||
pattern: //,
|
||||
alias: 'punctuation'
|
||||
},
|
||||
rest: null
|
||||
}
|
||||
},
|
||||
'string': /[\s\S]+/
|
||||
}
|
||||
},
|
||||
'triple-quoted-string': {
|
||||
pattern: /(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,
|
||||
greedy: true,
|
||||
alias: 'string'
|
||||
},
|
||||
'string': {
|
||||
pattern: /(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,
|
||||
greedy: true
|
||||
},
|
||||
'function': {
|
||||
pattern: /((?:^|\s)func[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,
|
||||
lookbehind: true
|
||||
},
|
||||
'class-name': {
|
||||
pattern: /(\bclass\s+)\w+/i,
|
||||
lookbehind: true
|
||||
},
|
||||
'decorator': {
|
||||
pattern: /(^\s*)@\w+(?:\.\w+)*/im,
|
||||
lookbehind: true,
|
||||
alias: ['annotation', 'punctuation'],
|
||||
inside: {
|
||||
'punctuation': /\./
|
||||
}
|
||||
},
|
||||
'keyword': /\b(?:if|elif|else|for|while|break|continue|pass|return|match|func|class|class_name|extends|is|onready|tool|static|export|setget|const|var|as|void|enum|preload|assert|yield|signal|breakpoint|rpc|sync|master|puppet|slave|remotesync|mastersync|puppetsync)\b/,
|
||||
'builtin': /\b(?:PI|TAU|NAN|INF|_|sin|cos|tan|sinh|cosh|tanh|asin|acos|atan|atan2|sqrt|fmod|fposmod|floor|ceil|round|abs|sign|pow|log|exp|is_nan|is_inf|ease|decimals|stepify|lerp|dectime|randomize|randi|randf|rand_range|seed|rand_seed|deg2rad|rad2deg|linear2db|db2linear|max|min|clamp|nearest_po2|weakref|funcref|convert|typeof|type_exists|char|str|print|printt|prints|printerr|printraw|var2str|str2var|var2bytes|bytes2var|range|load|inst2dict|dict2inst|hash|Color8|print_stack|instance_from_id|preload|yield|assert|Vector2|Vector3|Color|Rect2|Array|Basis|Dictionary|Plane|Quat|RID|Rect3|Transform|Transform2D|AABB|String|Color|NodePath|RID|Object|Dictionary|Array|PoolByteArray|PoolIntArray|PoolRealArray|PoolStringArray|PoolVector2Array|PoolVector3Array|PoolColorArray)\b/,
|
||||
'boolean': /\b(?:true|false)\b/,
|
||||
'number': /(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
|
||||
'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,
|
||||
'punctuation': /[{}[\];(),.:]/
|
||||
};
|
||||
|
||||
const PrismStyleSheet = `
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #657b83; /* base00 */
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
background: #073642; /* base02 */
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
background: #073642; /* base02 */
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background-color: #fdf6e3; /* base3 */
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #93a1a1; /* base1 */
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #586e75; /* base01 */
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #268bd2; /* blue */
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.url,
|
||||
.token.inserted {
|
||||
color: #2aa198; /* cyan */
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #657b83; /* base00 */
|
||||
background: #eee8d5; /* base2 */
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #859900; /* green */
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #b58900; /* yellow */
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #cb4b16; /* orange */
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
`;
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as http from 'http';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
function requestGodot(body : Object) {
|
||||
let postString = JSON.stringify(body);
|
||||
const port = vscode.workspace.getConfiguration("GodotTools").get("editorServerPort", 6996);
|
||||
const options = {
|
||||
hostname: '127.0.0.1',
|
||||
method: 'POST',
|
||||
port,
|
||||
body,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(postString)
|
||||
}
|
||||
};
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
var req = http.request(options, (res) => {
|
||||
let resultString = "";
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk) => {
|
||||
resultString += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(JSON.parse(resultString));
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
req.write(postString);
|
||||
req.end();
|
||||
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
export default requestGodot;
|
||||
@@ -1,219 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import godotRequest from './request';
|
||||
import GDScriptSymbolProvider from './gdscript/symbolprovider';
|
||||
import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider';
|
||||
import GDScriptCompletionItemProvider from './gdscript/completion';
|
||||
import GDScriptDefinitionProivder from './gdscript/definitionprovider';
|
||||
import GDScriptHoverProvider from './gdscript/hoverprovider';
|
||||
import GDScriptDocumentContentProvider from './gdscript/docprovider';
|
||||
import GDScriptSignatureHelpProvider from './gdscript/signature_helper';
|
||||
var glob = require("glob")
|
||||
import config from './config';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
const cmd = require('node-cmd');
|
||||
class ToolManager {
|
||||
|
||||
private workspaceDir: string = "";
|
||||
private symbolprovider: GDScriptSymbolProvider = null;
|
||||
private workspacesymbolprovider: GDScriptWorkspaceSymbolProvider = null;
|
||||
private _disposable: vscode.Disposable;
|
||||
private _context: vscode.ExtensionContext;
|
||||
|
||||
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();
|
||||
}
|
||||
this.loadClasses();
|
||||
// documentation symbol provider
|
||||
this.symbolprovider = new GDScriptSymbolProvider();
|
||||
vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider);
|
||||
// workspace symbol provider
|
||||
this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider();
|
||||
vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
|
||||
// definition provider
|
||||
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder());
|
||||
// hover provider
|
||||
vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
|
||||
// code completion provider
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
validate() {
|
||||
const self = this;
|
||||
godotRequest({action: "editor", command: "projectdir"}).then((res: any)=>{
|
||||
let path = res.path;
|
||||
if(path && path.length> 0 && path.endsWith("/"))
|
||||
path = path.substring(0, path.length-1)
|
||||
if( path.toLowerCase() == self.workspaceDir.toLowerCase())
|
||||
vscode.window.showInformationMessage("Connected to 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");
|
||||
});
|
||||
}
|
||||
|
||||
loadAllSymbols(): Promise<any> {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
glob( self.workspaceDir +"/**/*.gd", (err, files)=>{
|
||||
if(!err) {
|
||||
const symbols = {};
|
||||
for(let i=0; i< files.length; i++)
|
||||
symbols[config.normalizePath(files[i])] = config.loadSymbolsFromFile(files[i]);
|
||||
// load autoloads from engin.cfg
|
||||
const engincfg = path.join(self.workspaceDir, "engine.cfg");
|
||||
if(fs.existsSync(engincfg) && fs.statSync(engincfg).isFile()) {
|
||||
try {
|
||||
const script = { constants: {}, functions: {}, variables: {}, signals: {}, classes: {}, base: "Object", native: "Object", constpathes: {}, documents: {}, constvalues: {}};
|
||||
let content: string = fs.readFileSync(engincfg, 'utf-8');
|
||||
if(content && content.indexOf("[autoload]") != -1) {
|
||||
content = content.substring(content.indexOf("[autoload]")+"[autoload]".length, content.length);
|
||||
content = content.substring(0, content.indexOf("["));
|
||||
const lines = content.split(/\r?\n/);
|
||||
lines.map((l)=>{
|
||||
if(l.indexOf("=") != 0) {
|
||||
const name = l.substring(0, l.indexOf("="));
|
||||
|
||||
let gdpath = l.substring(l.indexOf("res://")+"res://".length, l.indexOf(".gd")+".gd".length);
|
||||
gdpath = path.join( self.workspaceDir, gdpath);
|
||||
let showgdpath = vscode.workspace.asRelativePath(gdpath);
|
||||
|
||||
let doc = "Auto loaded instance of " + `[${showgdpath}](${vscode.Uri.file(gdpath).toString()})`;
|
||||
doc = doc.replace(/"/g, " ");
|
||||
|
||||
script.constants[name] = new vscode.Range(0, 0, 0, 0);
|
||||
script.constvalues[name] = "autoload";
|
||||
script.documents[name] = doc;
|
||||
script.constpathes[name] = gdpath;
|
||||
}
|
||||
});
|
||||
}
|
||||
symbols["autoload"] = script;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
resolve(symbols);
|
||||
}
|
||||
else
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private loadAllNodesInWorkspace() {
|
||||
glob( this.workspaceDir +"/**/*.tscn", (err, files)=>{
|
||||
if(!err) {
|
||||
const symbols = {};
|
||||
for(let i=0; i< files.length; i++)
|
||||
config.loadScene(files[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadWorkspaceSymbols() {
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
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())
|
||||
workspaceValid = true;
|
||||
}
|
||||
if(workspaceValid)
|
||||
this.runEditor(`-path ${this.workspaceDir} ${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()) {
|
||||
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
|
||||
}]
|
||||
}
|
||||
return JSON.stringify(config, null, '\t');
|
||||
}
|
||||
else {
|
||||
vscode.window.showErrorMessage("Cannot create launch without godot project workspace");
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
loadClasses() {
|
||||
let done :boolean = false;
|
||||
if(this.workspaceDir)
|
||||
done = config.loadClasses(path.join(this.workspaceDir, ".vscode", "classes.json"));
|
||||
if(!done)
|
||||
done = config.loadClasses(path.join(this._context.extensionPath, "doc", "classes.json"));
|
||||
if(!done)
|
||||
vscode.window.showErrorMessage("Load GDScript documentations failed");
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
export default ToolManager;
|
||||
15
src/utils.ts
Normal file
15
src/utils.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
const CONFIG_CONTAINER = "godot_tools";
|
||||
|
||||
export function get_configuration(name: string, default_value: any = null) {
|
||||
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).get(name, default_value) || default_value;
|
||||
}
|
||||
|
||||
export function set_configuration(name: string, value: any) {
|
||||
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).update(name, value);
|
||||
}
|
||||
|
||||
export function is_debug_mode(): boolean {
|
||||
return process.env.VSCODE_DEBUG_MODE === "true";
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import {Disposable, window} from 'vscode';
|
||||
import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
|
||||
import GDScriptCompleter from './gdscript/completion';
|
||||
import config from './config';
|
||||
|
||||
interface DocumentFlag {
|
||||
path: string,
|
||||
version: number
|
||||
}
|
||||
|
||||
class WindowWatcher {
|
||||
|
||||
private _disposable: Disposable;
|
||||
private _diagnosticSeverity: GDScriptDiagnosticSeverity;
|
||||
private _lastText: DocumentFlag;
|
||||
|
||||
constructor() {
|
||||
let subscriptions: Disposable[] = [];
|
||||
window.onDidChangeTextEditorSelection(this.onDidChangeTextEditorSelection.bind(this), this, subscriptions);
|
||||
window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor.bind(this), this, subscriptions);
|
||||
window.onDidChangeTextEditorOptions(this.onDidChangeTextEditorOptions.bind(this), this, subscriptions);
|
||||
window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions);
|
||||
|
||||
this._diagnosticSeverity = new GDScriptDiagnosticSeverity();
|
||||
this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity);
|
||||
this._lastText = {path: "-1", version: -1};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the [active editor](#window.activeTextEditor)
|
||||
* has changed. *Note* that the event also fires when the active editor changes
|
||||
* to `undefined`.
|
||||
*/
|
||||
private onDidChangeActiveTextEditor(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
|
||||
if(window.activeTextEditor != undefined) {
|
||||
const doc = window.activeTextEditor.document;
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
this._lastText = {path: doc.fileName, version: doc.version};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the selection in an editor has changed.
|
||||
*/
|
||||
private onDidChangeTextEditorSelection(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorSelection");
|
||||
const doc = window.activeTextEditor.document;
|
||||
const curText: DocumentFlag= {path: doc.fileName, version: doc.version};
|
||||
// Check content changed
|
||||
if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
|
||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
||||
this._lastText = curText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the options of an editor have changed.
|
||||
*/
|
||||
private onDidChangeTextEditorOptions(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorOptions", event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the view column of an editor has changed.
|
||||
*/
|
||||
private onDidChangeTextEditorViewColumn(event: any) {
|
||||
// console.log("[GodotTools]:onDidChangeTextEditorViewColumn", event);
|
||||
}
|
||||
}
|
||||
|
||||
export default WindowWatcher;
|
||||
@@ -1,18 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test",
|
||||
"src/gdscript/server",
|
||||
"server"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es2020",
|
||||
"dom"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
14
tslint.json
Normal file
14
tslint.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-string-throw": true,
|
||||
"no-unused-expression": true,
|
||||
"no-duplicate-variable": false,
|
||||
"curly": true,
|
||||
"class-name": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
Reference in New Issue
Block a user