Compare commits

...

135 Commits

Author SHA1 Message Date
geequlim
ca3a1e62c4 Fix status icon color bug 2019-12-07 14:03:59 +08:00
geequlim
61e05e7d6e Update dependencies 2019-12-07 14:03:38 +08:00
Geequlim
96b833851d Merge pull request #124 from Calinou/improve-messages
Improve messages for consistency and fix typos
2019-11-06 00:25:49 +08:00
Hugo Locurcio
602cc895c0 Improve messages for consistency and fix typos 2019-11-04 18:52:53 +01:00
geequlim
41a58d76b1 Fix indent in GDScript.tmLaguage.json 2019-10-27 12:04:42 +08:00
Geequlim
8d6992822e Merge pull request #123 from OrenjiAkira/master
Fix class_name statement bug
2019-10-27 12:00:41 +08:00
orenjiakira
f0914742e1 Fix class_name statement bug 2019-10-26 16:07:09 -03:00
Geequlim
a1e020560d Merge pull request #120 from Calinou/improve-readme
Improve the README and fix various typos
2019-10-10 23:31:51 +08:00
Hugo Locurcio
cf9e478b12 Improve the README and fix various typos 2019-10-10 17:29:43 +02:00
Geequlim
d155016b08 Merge pull request #121 from Calinou/optimize-images
Optimize images losslessly using `oxipng -o6 --strip --zopfli`
2019-10-10 23:28:36 +08:00
Hugo Locurcio
40b09491ac Optimize images losslessly using oxipng -o6 --strip --zopfli 2019-10-10 17:04:21 +02:00
Geequlim
24c29452c7 Fix typo in readme 2019-10-09 15:15:25 +08:00
Geequlim
ecffd631a8 Update version to 1.0.0 ! 2019-10-09 14:47:21 +08:00
Geequlim
3645e431d3 Add command to list godot native classes 2019-10-09 13:30:21 +08:00
Geequlim
e2febb81b1 Setup class inherit tree and render in native documentation page 2019-10-09 13:14:04 +08:00
Geequlim
f07e1154ef Improve native documentation webview renderer
Make prism as a custom lib instead of node module to reduce the binary size
2019-10-08 19:34:47 +08:00
geequlim
a563a3584a Bump to 0.9.1 2019-10-06 17:09:09 +08:00
geequlim
758aafc570 Render docs of native symbols in webview mostly works now 2019-10-06 17:06:49 +08:00
geequlim
eba90dbbf9 Jump between native documentations 2019-10-04 20:54:47 +08:00
geequlim
47647a05ae [WIP] Add NativeDocumentManager to show native symbol informations 2019-10-04 19:22:02 +08:00
geequlim
28e284f0ad Improve code hlighting 2019-10-04 14:15:42 +08:00
Geequlim
c26320ec03 Merge pull request #118 from pduzinki:fix-string-highlighting
fix string literal highlighting
2019-10-04 13:05:26 +08:00
pawel duzinkiewicz
7d20df3b35 fix string literal highlighting 2019-10-03 19:58:38 +02:00
Geequlim
a84548aeac Merge pull request #116 from anoadragon453/patch-2
typo fix
2019-09-27 11:12:04 +08:00
Andrew Morgan
0ae80d6bcd typo fix
small typo fix
2019-09-26 23:54:47 +01:00
geequlim
325b17a29c transfer to godotengine/godot-vscode-plugin 2019-08-28 22:36:45 +08:00
geequlim
0938b6384a Fix crash with detect shell configurations of VSCode settings 2019-08-28 22:15:42 +08:00
Geequlim
3064849452 Fix error with restart godot editor
Move more client related code to GDScriptLanguageClient
2019-06-26 17:29:06 +08:00
Geequlim
c416ea6789 Allow retry connect to editor after ignored retry 2019-06-26 12:08:10 +08:00
Geequlim
1e22ac0d9a Fix release build 2019-06-25 17:33:25 +08:00
Geequlim
61e1cafdfe The GDScript LSP Client is works correctly ! 2019-06-25 17:16:59 +08:00
Geequlim
1844dd570b Merge pull request #102 from ankitpriyarup/lsp-client
minor addition in configuration and grammar files
2019-05-31 19:17:31 +08:00
Ankit Priyarup
ff7f31776a minor addition in configuration and grammar files
comment folding, addition of new keywords in grammar, removing wrong comments from snippets which was causing problem
2019-05-31 15:13:40 +05:30
geequlim
fafabc3b34 improve syntax highlight of GDScript 2019-05-22 22:20:54 +08:00
Geequlim
d6331fee89 Implement MessageTransports for the websocket connection
Drop stream wrap fro the websocket connection
2019-05-21 10:47:34 +08:00
geequlim
41b36e6e95 Warp the websocket connection to a mock stream 2019-05-19 18:46:14 +08:00
geequlim
ece1f3118d Checkout new GDScriptLanguageClient branch 2019-05-04 19:48:36 +08:00
Geequlim
2ba776dc40 Merge pull request #94 from anoadragon453/patch-1
Fix small grammar issue
2019-03-13 20:40:28 +08:00
Andrew Morgan
abaa5d32d0 Fix some grammar issue
Lint configuration, not configurations.
2019-03-12 15:24:17 +00:00
Geequlim
06817de78e Merge pull request #84 from lleaff/feat-reuse-term
Close existing terminal when executing "run" commands
2018-12-03 13:06:03 +08:00
lleaff
6aab0be1d4 "run" commands close existing launched instances 2018-10-10 02:03:04 +02:00
Geequlim
307e29a7ec Merge pull request #79 from AlienGamesLLC/master
single line if-else statement
2018-09-08 12:04:47 +08:00
Geequlim
58e8626cac Merge pull request #77 from fractile81/deps-updates
Dependencies all updated to latest versions.
2018-09-08 11:55:57 +08:00
Geequlim
3ac359af86 Merge branch 'master' into deps-updates 2018-09-08 11:55:40 +08:00
Akira Kido
c20909fdb3 fixed regex to reflect my issue 2018-09-04 17:30:40 +09:00
Geequlim
ec9fe7fdc1 Merge pull request #80 from fractile81/update-readme
Further README refinements.  Updates to strings for consistency.
2018-09-04 09:20:58 +08:00
Craig A. Hancock
c84d7f9c12 Further README refinements. Updates to strings in package.json and tool_manager.ts to match. 2018-09-03 10:26:22 -06:00
Akira Kido
492fe5663c single if-else statement 2018-09-03 18:58:47 +09:00
Geequlim
8f5da41a41 Merge pull request #78 from fractile81/update-readme
README updated for consistency.
2018-09-03 00:12:39 +08:00
Craig A. Hancock
e4d86f35be README updated for consistency. 2018-09-02 07:37:37 -06:00
Craig A. Hancock
eed2d4f516 Dependencies all updated to latest versions. Only affects development packages. 2018-09-02 07:07:31 -06:00
geequlim
63b10b1d72 Sync docs with godot 3.0.4
Release 0.3.7
2018-06-29 14:03:42 +08:00
geequlim
fdc07e4743 fix syntax checing with inline condition expression fix #58 2018-06-29 13:19:14 +08:00
geequlim
80ce466d53 fix #54 2018-06-29 13:09:49 +08:00
geequlim
7e3e95086b Enable/Disable unused symbol checking 2018-06-29 13:04:25 +08:00
geequlim
7c8696abc1 Fix bracket checing in conditions fix #59 2018-06-29 12:53:32 +08:00
geequlim
8ddc7dd310 Indentifier checking for keywords bug fix
fix #65
2018-06-29 12:47:11 +08:00
geequlim
e76c06a31a Don't compains with assert checking fix #66 2018-06-29 12:35:10 +08:00
geequlim
4c0f864cf8 Add lint configuration to enable or disable some feature for syntax check 2018-06-29 12:20:09 +08:00
Geequlim
462a7bdbd7 Merge pull request #56 from RivenCode/master
Added handling of paths with spaces in them
2018-03-18 19:48:33 +08:00
RivenCode
e2dbf8146f Added handling of paths with spaces in them.
For the editor path, we use PowerShell syntax, when required.
2018-03-18 21:14:14 +11:00
geequlim
7b21267d07 Update to 0.3.6 2018-02-01 23:06:28 +08:00
geequlim
ef3a70f417 Set default project file to project.godot 2018-02-01 22:52:22 +08:00
geequlim
fc027ea9ad Update to 0.3.5 to works fine with Godot 3.0 2018-01-30 22:47:57 +08:00
geequlim
4c47fff9af Add option to disable syntax checking for GDScript 2018-01-01 15:22:29 +08:00
geequlim
d7ff45edac Add more text resource type support with syntax highlight. 2017-12-25 21:28:43 +08:00
Geequlim
38b0898649 Fix syntax checking for inline if else statement 2017-12-22 15:23:10 +08:00
geequlim
a3b062e242 Release 0.3.4 2017-10-28 11:53:27 +08:00
Geequlim
071e59364f Show window progress when parse workspace symbols 2017-10-16 15:33:25 +08:00
Geequlim
a47980981b Show relative script path in code completion and hover documentation 2017-10-16 15:00:47 +08:00
Geequlim
edcb8b96d1 Fix builtin documentation parsing for godot 2.1 fix #44 2017-10-16 14:17:43 +08:00
geequlim
1248d8e25a release 0.3.3 2017-10-15 15:23:57 +08:00
geequlim
ae0830638b Update documentations with latest 2017-10-15 15:11:39 +08:00
geequlim
bab6fed5cf Fix run editor with space conained path close #42 2017-10-15 14:58:33 +08:00
geequlim
27d7501672 Fix syntax checking error with for close #43 and #39 2017-10-15 14:51:42 +08:00
geequlim
4dff47d97b Fix errors with run current scene 2017-10-15 14:41:45 +08:00
Geequlim
7a8f00422b Update readme 2017-10-13 13:31:11 +08:00
geequlim
447af0183f Improved documentation page. 2017-10-09 23:06:24 +08:00
geequlim
1cb8a5724d Fix hover highlight with latest VSCode version 2017-10-09 21:45:55 +08:00
geequlim
0a9a0117f0 release 0.3.2 2017-09-23 02:07:58 +08:00
geequlim
d8b9dee510 improve hover documentations for code blocks 2017-09-23 02:05:07 +08:00
geequlim
0f31c66c5a MarkedString to MarkdonwString 2017-09-23 01:52:28 +08:00
geequlim
638a5362e6 Fix error complain about match statement 2017-09-23 01:31:07 +08:00
geequlim
4f0550ce86 release 0.3.1 2017-09-22 23:57:26 +08:00
geequlim
bc96a9462b Add constants back with completion 2017-09-09 23:00:57 +08:00
geequlim
749148841d Fix errors with run script
Fix build error on linux
2017-09-09 22:43:32 +08:00
Geequlim
2384004ef9 Implements base class and native class of script parsing 2017-09-06 21:30:28 +08:00
Geequlim
927297ad03 Show symbols in code completion in current document 2017-09-06 21:29:54 +08:00
Geequlim
5b1bee0c68 Merge branch 'master' of github.com:GodotExplorer/godot-tools 2017-08-31 19:17:43 +08:00
Alvaro
7f5b244113 Update package.json (#38)
fix strng to string
2017-08-29 20:01:44 -05:00
Geequlim
670f46014b release 0.3.0 2017-08-29 22:12:34 +08:00
Geequlim
203e32c3ed release 0.3.0 2017-08-29 22:10:03 +08:00
Geequlim
b7dbbcc589 Use ${workspaceRoot} in the settings
Cleanup code for debugger
2017-08-29 22:02:16 +08:00
Konstantin Zaitcev
ec7e1013aa Add project root configuration settings (#37)
* Add project root configuration settings

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

7
.editorconfig Normal file
View File

@@ -0,0 +1,7 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = tab
insert_final_newline = true

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ test
*.vsix
configurations/tmp.txt
configurations/test.py
.vscode-test

46
.vscode/launch.json vendored
View File

@@ -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
}
},
]
}

View File

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

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

View File

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

View File

@@ -1,5 +1,124 @@
# Change Log
### 1.0.0
* Refactor the whole plugin with gdscript language server support
* Add webview renderer to show documentations of native symbols.
* Only support godot 3.2 and above
### 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 configuration 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 paths
* 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 conflict 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 variable checking
* Move workspace symbols state notice to status bar
### 0.2.4
* Add code checking for asignments and comparisons
* Improved builtin documentation preview page
* Fix bugs with unused variable checking
### 0.2.3
* Fix known errors with code syntax checking
* Add configuration `ignoreIndentedVars` to allow ignore indented variables in scripts
@@ -8,7 +127,7 @@
### 0.2.2
* Better Syntax validating for code blocks
* More waring for non-python liked expression
* More warning for non-python liked expression
### 0.2.1
* Support markdown render in hover tips for documentations in workspace symbols
@@ -16,14 +135,14 @@
### 0.2.0
* Show autoloads informations in hover tips and go to autoloads' definitions are supported now
* Show autoloads information in hover tips and go to autoloads' definitions are supported now
* Fix the bug that workspace symbols resoved twice on Windows
### 0.1.9
* Show workspace constant value in hover tips and completion items
* More readable style for links in documentation preview page
* Improve code completion sort order and auto insert `()` for functions without paramaters
* Improve code completion sort order and auto insert `()` for functions without parameters
* Fix bugs with workspace documentation parsing
### 0.1.8
@@ -40,7 +159,7 @@
* Reorder mouse hover tips, builtin methods are at top of workspace methods
* Show callabel signatures with documente symbols and workspace symbols
* Syntax highlight support for signal paramaters
* Syntax highlight support for signal parameters
### 0.1.5

View File

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

106
README.md
View File

@@ -1,79 +1,79 @@
A complete set of tools to code games with the [Godot game engine](http://www.godotengine.org/) in Visual Studio Code.
# Godot Tools
A complete set of tools to code games with
[Godot Engine](http://www.godotengine.org/) in Visual Studio Code.
**IMPORTANT NOTE:** Versions 1.0.0 and later of this plugin only support
Godot 3.2 or later.
## 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
- 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)
- Syntax highlighting for the GDScript (`.gd`) language
- Syntax highlighting for the `.tscn` and `.tres` scene formats
- Full typed GDScript support
- Optional "Smart Mode" to improve productivity with dynamically typed scripts
- Function definitions and documentation display on hover (see image below)
- Rich autocompletion
- Display script warnings and errors
- Ctrl + click on a variable or method call to jump to its definition
- Full documentation of the Godot Engine's API supported
- Run a Godot project from VS Code
![Showing the documentation on hover feature](https://raw.githubusercontent.com/GodotExplorer/godot-tools/master/img/documentation-on-hover.png "Method definition and docs on hover")
![Showing the documentation on hover feature](img/godot-tools.png)
## 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 "Godot Tools":
- Update Workspace Symbols
- Run workspace as godot project
- Open workspace with godot editor
- Run current scene
- Open workspace with Godot editor
- Run the workspace as a Godot project
- List Godot's native classes
## 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:
## Extra functionality
1. Open the **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}`
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:
### VS Code
### Godot modules
You can use the following settings to configure Godot Tools:
These are modules for the goats editor itself, programmed in C++. In order to use them, you have to create a [custom build](http://docs.godotengine.org/en/stable/reference/compiling_for_windows.html) of the engine. Only do that if you know what you're doing.
- [EditorServer](https://github.com/GodotExplorer/editor-server/tree/master/editor_server): Using HTTP requests, this module gets extra information from Godot to improve autocompletion.
- [VSCode](https://github.com/GodotExplorer/editor-server/tree/master/vscode_tools): The VS code module generates a setting file that Visual Studio code can use to generate Tasks automatically.
### VScode extensions
- [TOML language](https://marketplace.visualstudio.com/items?itemName=be5invis.toml): Godot uses this minimal language to store settings. For example in your project config file. If you want to get syntax highlighting for these files, you will have to install the TOML language extension.
- `editor_path` - The absolute path to the Godot editor executable.
- `gdscript_lsp_server_port` - The WebSocket server port of the GDScript language server.
- `check_status` - Check the GDScript language server connection status.
## 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.
The [Godot Tools](https://github.com/godotengine/godot-vscode-plugin) extension
is an open source project from the Godot orgnization. Feel free to open issues
and create pull requests anytime.
## Release Notes
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
for the latest changes.
### 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
## FAQ
### 0.2.2
* Better Syntax validating for code blocks
* More waring for non-python liked expression
### Why does it fail to connect to the language server?
### 0.2.1
* Support markdown render in hover tips for documentations in workspace symbols
* Add configuration `GodotTools.workspaceDocumentWithMarkdown` to control workspace documentation rendering
- Godot 3.2 or later is required.
- Make sure to open the project in the Godot editor first. If you opened
the editor after opening VS Code, you can click the **Retry** button
in the bottom-right corner in VS Code.
[Read more from the full change log](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
### Why isn't IntelliSense displaying script members?
## 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
- GDScript is a dynamically typed script language. The language server can't
infer all variable types.
- To increase the number of results displayed, open the **Editor Settings**,
go to the **Language Server** section then check **Enable Smart Resolve**.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

File diff suppressed because it is too large Load Diff

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

BIN
img/godot-tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -1,174 +1,117 @@
{
"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": "1.0.0",
"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": "npm run compile",
"compile": "tsc -p ./",
"lint": "tslint -p ./",
"watch": "tsc -watch -p ./"
},
"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"
},
{
"command": "godot-tool.list_native_classes",
"title": "Godot Tools: List native classes of godot"
}
],
"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": "Check the gdscript language server connection status"
}
}
},
"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.5.1",
"@types/vscode": "^1.40.0"
},
"dependencies": {
"global": "^4.4.0",
"marked": "^0.7.0",
"vscode-languageclient": "^5.2.1",
"ws": "^7.0.0"
}
}

View File

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

View File

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

573
src/deps/prism/prism.js Normal file
View File

@@ -0,0 +1,573 @@
/* PrismJS 1.17.1
https://prismjs.com/download.html#themes=prism */
var _self = (typeof window !== 'undefined')
? window // if in browser
: (
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
? self // if in worker
: {} // if in node js
);
/**
* Prism: Lightweight, robust, elegant syntax highlighting
* MIT license http://www.opensource.org/licenses/mit-license.php/
* @author Lea Verou http://lea.verou.me
*/
var Prism = (function (_self){
// Private helper vars
var lang = /\blang(?:uage)?-([\w-]+)\b/i;
var uniqueId = 0;
/**
* Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.
*
* If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.
*
* @param {Element} element
* @returns {string}
*/
function getLanguage(element) {
while (element && !lang.test(element.className)) {
element = element.parentNode;
}
if (element) {
return (element.className.match(lang) || [, 'none'])[1].toLowerCase();
}
return 'none';
}
var _ = {
manual: _self.Prism && _self.Prism.manual,
disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
util: {
encode: function (tokens) {
if (tokens instanceof Token) {
return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
} else if (Array.isArray(tokens)) {
return tokens.map(_.util.encode);
} else {
return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
}
},
type: function (o) {
return Object.prototype.toString.call(o).slice(8, -1);
},
objId: function (obj) {
if (!obj['__id']) {
Object.defineProperty(obj, '__id', { value: ++uniqueId });
}
return obj['__id'];
},
// Deep clone a language definition (e.g. to extend it)
clone: function deepClone(o, visited) {
var clone, id, type = _.util.type(o);
visited = visited || {};
switch (type) {
case 'Object':
id = _.util.objId(o);
if (visited[id]) {
return visited[id];
}
clone = {};
visited[id] = clone;
for (var key in o) {
if (o.hasOwnProperty(key)) {
clone[key] = deepClone(o[key], visited);
}
}
return clone;
case 'Array':
id = _.util.objId(o);
if (visited[id]) {
return visited[id];
}
clone = [];
visited[id] = clone;
o.forEach(function (v, i) {
clone[i] = deepClone(v, visited);
});
return clone;
default:
return o;
}
}
},
languages: {
extend: function (id, redef) {
var lang = _.util.clone(_.languages[id]);
for (var key in redef) {
lang[key] = redef[key];
}
return lang;
},
/**
* Insert a token before another token in a language literal
* As this needs to recreate the object (we cannot actually insert before keys in object literals),
* we cannot just provide an object, we need an object and a key.
* @param inside The key (or language id) of the parent
* @param before The key to insert before.
* @param insert Object with the key/value pairs to insert
* @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
*/
insertBefore: function (inside, before, insert, root) {
root = root || _.languages;
var grammar = root[inside];
var ret = {};
for (var token in grammar) {
if (grammar.hasOwnProperty(token)) {
if (token == before) {
for (var newToken in insert) {
if (insert.hasOwnProperty(newToken)) {
ret[newToken] = insert[newToken];
}
}
}
// Do not insert token which also occur in insert. See #1525
if (!insert.hasOwnProperty(token)) {
ret[token] = grammar[token];
}
}
}
var old = root[inside];
root[inside] = ret;
// Update references in other language definitions
_.languages.DFS(_.languages, function(key, value) {
if (value === old && key != inside) {
this[key] = ret;
}
});
return ret;
},
// Traverse a language definition with Depth First Search
DFS: function DFS(o, callback, type, visited) {
visited = visited || {};
var objId = _.util.objId;
for (var i in o) {
if (o.hasOwnProperty(i)) {
callback.call(o, i, o[i], type || i);
var property = o[i],
propertyType = _.util.type(property);
if (propertyType === 'Object' && !visited[objId(property)]) {
visited[objId(property)] = true;
DFS(property, callback, null, visited);
}
else if (propertyType === 'Array' && !visited[objId(property)]) {
visited[objId(property)] = true;
DFS(property, callback, i, visited);
}
}
}
}
},
plugins: {},
highlightAll: function(async, callback) {
_.highlightAllUnder(document, async, callback);
},
highlightAllUnder: function(container, async, callback) {
var env = {
callback: callback,
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
};
_.hooks.run('before-highlightall', env);
var elements = container.querySelectorAll(env.selector);
for (var i=0, element; element = elements[i++];) {
_.highlightElement(element, async === true, env.callback);
}
},
highlightElement: function(element, async, callback) {
// Find language
var language = getLanguage(element);
var grammar = _.languages[language];
// Set language on the element, if not present
element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
// Set language on the parent, for styling
var parent = element.parentNode;
if (parent && parent.nodeName.toLowerCase() === 'pre') {
parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
}
var code = element.textContent;
var env = {
element: element,
language: language,
grammar: grammar,
code: code
};
function insertHighlightedCode(highlightedCode) {
env.highlightedCode = highlightedCode;
_.hooks.run('before-insert', env);
env.element.innerHTML = env.highlightedCode;
_.hooks.run('after-highlight', env);
_.hooks.run('complete', env);
callback && callback.call(env.element);
}
_.hooks.run('before-sanity-check', env);
if (!env.code) {
_.hooks.run('complete', env);
callback && callback.call(env.element);
return;
}
_.hooks.run('before-highlight', env);
if (!env.grammar) {
insertHighlightedCode(_.util.encode(env.code));
return;
}
if (async && _self.Worker) {
var worker = new Worker(_.filename);
worker.onmessage = function(evt) {
insertHighlightedCode(evt.data);
};
worker.postMessage(JSON.stringify({
language: env.language,
code: env.code,
immediateClose: true
}));
}
else {
insertHighlightedCode(_.highlight(env.code, env.grammar, env.language));
}
},
highlight: function (text, grammar, language) {
var env = {
code: text,
grammar: grammar,
language: language
};
_.hooks.run('before-tokenize', env);
env.tokens = _.tokenize(env.code, env.grammar);
_.hooks.run('after-tokenize', env);
return Token.stringify(_.util.encode(env.tokens), env.language);
},
matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
for (var token in grammar) {
if (!grammar.hasOwnProperty(token) || !grammar[token]) {
continue;
}
var patterns = grammar[token];
patterns = Array.isArray(patterns) ? patterns : [patterns];
for (var j = 0; j < patterns.length; ++j) {
if (target && target == token + ',' + j) {
return;
}
var pattern = patterns[j],
inside = pattern.inside,
lookbehind = !!pattern.lookbehind,
greedy = !!pattern.greedy,
lookbehindLength = 0,
alias = pattern.alias;
if (greedy && !pattern.pattern.global) {
// Without the global flag, lastIndex won't work
var flags = pattern.pattern.toString().match(/[imsuy]*$/)[0];
pattern.pattern = RegExp(pattern.pattern.source, flags + 'g');
}
pattern = pattern.pattern || pattern;
// Dont cache length as it changes during the loop
for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
var str = strarr[i];
if (strarr.length > text.length) {
// Something went terribly wrong, ABORT, ABORT!
return;
}
if (str instanceof Token) {
continue;
}
if (greedy && i != strarr.length - 1) {
pattern.lastIndex = pos;
var match = pattern.exec(text);
if (!match) {
break;
}
var from = match.index + (lookbehind && match[1] ? match[1].length : 0),
to = match.index + match[0].length,
k = i,
p = pos;
for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
p += strarr[k].length;
// Move the index i to the element in strarr that is closest to from
if (from >= p) {
++i;
pos = p;
}
}
// If strarr[i] is a Token, then the match starts inside another Token, which is invalid
if (strarr[i] instanceof Token) {
continue;
}
// Number of tokens to delete and replace with the new match
delNum = k - i;
str = text.slice(pos, p);
match.index -= pos;
} else {
pattern.lastIndex = 0;
var match = pattern.exec(str),
delNum = 1;
}
if (!match) {
if (oneshot) {
break;
}
continue;
}
if(lookbehind) {
lookbehindLength = match[1] ? match[1].length : 0;
}
var from = match.index + lookbehindLength,
match = match[0].slice(lookbehindLength),
to = from + match.length,
before = str.slice(0, from),
after = str.slice(to);
var args = [i, delNum];
if (before) {
++i;
pos += before.length;
args.push(before);
}
var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
args.push(wrapped);
if (after) {
args.push(after);
}
Array.prototype.splice.apply(strarr, args);
if (delNum != 1)
_.matchGrammar(text, strarr, grammar, i, pos, true, token + ',' + j);
if (oneshot)
break;
}
}
}
},
tokenize: function(text, grammar) {
var strarr = [text];
var rest = grammar.rest;
if (rest) {
for (var token in rest) {
grammar[token] = rest[token];
}
delete grammar.rest;
}
_.matchGrammar(text, strarr, grammar, 0, 0, false);
return strarr;
},
hooks: {
all: {},
add: function (name, callback) {
var hooks = _.hooks.all;
hooks[name] = hooks[name] || [];
hooks[name].push(callback);
},
run: function (name, env) {
var callbacks = _.hooks.all[name];
if (!callbacks || !callbacks.length) {
return;
}
for (var i=0, callback; callback = callbacks[i++];) {
callback(env);
}
}
},
Token: Token
};
_self.Prism = _;
function Token(type, content, alias, matchedStr, greedy) {
this.type = type;
this.content = content;
this.alias = alias;
// Copy of the full string this token was created from
this.length = (matchedStr || '').length|0;
this.greedy = !!greedy;
}
Token.stringify = function(o, language) {
if (typeof o == 'string') {
return o;
}
if (Array.isArray(o)) {
return o.map(function(element) {
return Token.stringify(element, language);
}).join('');
}
var env = {
type: o.type,
content: Token.stringify(o.content, language),
tag: 'span',
classes: ['token', o.type],
attributes: {},
language: language
};
if (o.alias) {
var aliases = Array.isArray(o.alias) ? o.alias : [o.alias];
Array.prototype.push.apply(env.classes, aliases);
}
_.hooks.run('wrap', env);
var attributes = Object.keys(env.attributes).map(function(name) {
return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
}).join(' ');
return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
};
if (!_self.document) {
if (!_self.addEventListener) {
// in Node.js
return _;
}
if (!_.disableWorkerMessageHandler) {
// In worker
_self.addEventListener('message', function (evt) {
var message = JSON.parse(evt.data),
lang = message.language,
code = message.code,
immediateClose = message.immediateClose;
_self.postMessage(_.highlight(code, _.languages[lang], lang));
if (immediateClose) {
_self.close();
}
}, false);
}
return _;
}
//Get current script and highlight
var script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop();
if (script) {
_.filename = script.src;
if (script.hasAttribute('data-manual')) {
_.manual = true;
}
}
if (!_.manual) {
function highlightAutomaticallyCallback() {
if (!_.manual) {
_.highlightAll();
}
}
if(document.readyState !== 'loading') {
if (window.requestAnimationFrame) {
window.requestAnimationFrame(highlightAutomaticallyCallback);
} else {
window.setTimeout(highlightAutomaticallyCallback, 16);
}
}
else {
document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);
}
}
return _;
})(_self);
if (typeof module !== 'undefined' && module.exports) {
module.exports = Prism;
}
// hack for components to work correctly in node.js
if (typeof global !== 'undefined') {
global.Prism = Prism;
}
;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

164
src/godot-tools.ts Normal file
View File

@@ -0,0 +1,164 @@
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 the GDScript language server...");
break;
case ClientStatus.CONNECTED:
vscode.window.showInformationMessage("Connected to the GDScript language server.");
break;
case ClientStatus.DISCONNECTED:
this.retry_connect_client();
break;
}
}
private on_client_status_changed(status: ClientStatus) {
switch (status) {
case ClientStatus.PENDING:
this.connection_status.text = `$(sync) Connecting`;
this.connection_status.tooltip = `Connecting to the GDScript language server...`;
break;
case ClientStatus.CONNECTED:
this.connection_status.text = `$(check) Connected`;
this.connection_status.tooltip = `Connected to the 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 = `Disconnected from the GDScript language server.`;
// retry
this.retry_connect_client();
break;
default:
break;
}
}
private retry_connect_client() {
vscode.window.showErrorMessage(`Couldn't connect to the 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
View 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;

View 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 open workspace is different from 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
View 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
View 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);
}
}
}

View File

@@ -0,0 +1,547 @@
import * as vscode from 'vscode';
import { EventEmitter } from "events";
import { MessageIO } from "./MessageIO";
import { NotificationMessage } from "vscode-jsonrpc";
import * as Prism from "../deps/prism/prism";
import * as marked from "marked";
import { Methods, NativeSymbolInspectParams, GodotNativeSymbol, GodotNativeClassInfo, GodotCapabilities } from './gdscript.capabilities';
marked.setOptions({
highlight: function (code, lang) {
return Prism.highlight(code, GDScriptGrammar, lang);
}
});
const enum WebViewMessageType {
INSPECT_NATIVE_SYMBOL = 'INSPECT_NATIVE_SYMBOL',
};
const LIST_NATIVE_CLASS_COMMAND = 'godot-tool.list_native_classes';
export default class NativeDocumentManager extends EventEmitter {
private io: MessageIO = null;
private native_classes: {[key: string]: GodotNativeClassInfo } = {};
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);
} else if (message.method == Methods.GDSCRIPT_CAPABILITIES) {
for (const gdclass of (message.params as GodotCapabilities).native_classes) {
this.native_classes[gdclass.name] = gdclass;
}
for (const gdclass of (message.params as GodotCapabilities).native_classes) {
if (gdclass.inherits) {
const extended_classes = this.native_classes[gdclass.inherits].extended_classes || [];
extended_classes.push(gdclass.name);
this.native_classes[gdclass.inherits].extended_classes = extended_classes;
}
}
}
});
vscode.commands.registerCommand(LIST_NATIVE_CLASS_COMMAND, this.list_native_classes.bind(this));
}
private async list_native_classes() {
let classname = await vscode.window.showQuickPick(
Object.keys(this.native_classes).sort(),
{
placeHolder: 'Type godot class name here',
canPickMany: false
}
);
if (classname) {
this.inspect_native_symbol({native_class: classname, symbol_name: classname});
}
}
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 {
const classlink = make_link(symbol.native_class, undefined);
const classinfo = this.native_classes[symbol.native_class];
function make_function_signature(s: GodotNativeSymbol, with_class = false) {
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} ${with_class?`${classlink}.`:''}${element("a", s.name, {href: `#${s.name}`})}( ${args} )`;
};
function make_symbol_elements(s: GodotNativeSymbol, with_class = false): {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} ${with_class?`${classlink}.`:''}${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} ${with_class?`${classlink}.`:''}${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', `${with_class?`signal ${with_class?`${classlink}.`:''}`:''}${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, with_class);
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) {
let inherits_chian = '';
let base_class = this.native_classes[inherits];
while(base_class) {
inherits_chian += `${inherits_chian?' >':''} ${make_link(base_class.name, undefined)}`;
base_class = this.native_classes[base_class.inherits];
}
inherits = `Inherits: ${inherits_chian}`;
doc += element("p", inherits);
}
if (classinfo && classinfo.extended_classes) {
let inherited = "";
for (const c of classinfo.extended_classes) {
inherited += (inherited ? ', ' : ' ') + make_link(c, c);
}
doc += element("p", `Inherited by:${inherited}`);
}
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);
}
};
doc += element("p", format_documentation(symbol.documentation, symbol.native_class));
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, true);
if (elements.index) {
if ([vscode.SymbolKind.Function, vscode.SymbolKind.Method].indexOf(symbol.kind) == -1) {
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: /![sra](?=[:}]$)/,
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;
}
`;

View File

@@ -0,0 +1,27 @@
import { DocumentSymbol } from "vscode";
export const enum Methods {
GDSCRIPT_CAPABILITIES = 'gdscript/capabilities',
SHOW_NATIVE_SYMBOL = 'gdscript/show_native_symbol',
INSPECT_NATIVE_SYMBOL = 'textDocument/nativeSymbol'
}
export interface NativeSymbolInspectParams {
native_class: string;
symbol_name: string;
}
export class GodotNativeSymbol extends DocumentSymbol {
documentation: string;
native_class: string;
};
export interface GodotNativeClassInfo {
name: string;
inherits: string;
extended_classes?: string[];
}
export interface GodotCapabilities {
native_classes: GodotNativeClassInfo[];
}

View File

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

View File

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

View File

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

View File

@@ -1,18 +1,19 @@
{
"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,
"allowJs": true
},
"exclude": [
"node_modules",
"out"
]
}

14
tslint.json Normal file
View 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"
}