mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2026-01-04 10:09:58 +03:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca3a1e62c4 | ||
|
|
61e05e7d6e | ||
|
|
96b833851d | ||
|
|
602cc895c0 | ||
|
|
41a58d76b1 | ||
|
|
8d6992822e | ||
|
|
f0914742e1 | ||
|
|
a1e020560d | ||
|
|
cf9e478b12 | ||
|
|
d155016b08 | ||
|
|
40b09491ac | ||
|
|
24c29452c7 | ||
|
|
ecffd631a8 | ||
|
|
3645e431d3 | ||
|
|
e2febb81b1 | ||
|
|
f07e1154ef | ||
|
|
a563a3584a | ||
|
|
758aafc570 | ||
|
|
eba90dbbf9 | ||
|
|
47647a05ae | ||
|
|
28e284f0ad | ||
|
|
c26320ec03 | ||
|
|
7d20df3b35 | ||
|
|
a84548aeac | ||
|
|
0ae80d6bcd | ||
|
|
325b17a29c | ||
|
|
0938b6384a | ||
|
|
3064849452 | ||
|
|
c416ea6789 | ||
|
|
1e22ac0d9a | ||
|
|
61e1cafdfe | ||
|
|
1844dd570b | ||
|
|
ff7f31776a | ||
|
|
fafabc3b34 | ||
|
|
d6331fee89 | ||
|
|
41b36e6e95 | ||
|
|
ece1f3118d |
7
.editorconfig
Normal file
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
46
.vscode/launch.json
vendored
46
.vscode/launch.json
vendored
@@ -1,28 +1,24 @@
|
|||||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
// 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",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [{
|
||||||
{
|
"name": "Run Extension",
|
||||||
"name": "Launch Extension",
|
"type": "extensionHost",
|
||||||
"type": "extensionHost",
|
"request": "launch",
|
||||||
"request": "launch",
|
"runtimeExecutable": "${execPath}",
|
||||||
"runtimeExecutable": "${execPath}",
|
"args": [
|
||||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||||
"stopOnEntry": false,
|
],
|
||||||
"sourceMaps": true,
|
"outFiles": [
|
||||||
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
|
"${workspaceFolder}/out/**/*.js"
|
||||||
"preLaunchTask": "npm"
|
],
|
||||||
},
|
"preLaunchTask": "npm: watch",
|
||||||
{
|
"env": {
|
||||||
"name": "Launch Tests",
|
"VSCODE_DEBUG_MODE": true
|
||||||
"type": "extensionHost",
|
}
|
||||||
"request": "launch",
|
},
|
||||||
"runtimeExecutable": "${execPath}",
|
]
|
||||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
|
||||||
"stopOnEntry": false,
|
|
||||||
"sourceMaps": true,
|
|
||||||
"outFiles": ["${workspaceRoot}/out/test"],
|
|
||||||
"preLaunchTask": "npm"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -6,5 +6,6 @@
|
|||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"out": true // set this to false to include "out" folder in search results
|
"out": true // set this to false to include "out" folder in search results
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||||
|
"typescript.tsc.autoDetect": "off"
|
||||||
}
|
}
|
||||||
48
.vscode/tasks.json
vendored
48
.vscode/tasks.json
vendored
@@ -1,30 +1,20 @@
|
|||||||
// Available variables which can be used inside of strings.
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
// ${workspaceRoot}: the root folder of the team
|
// for the documentation about the tasks.json format
|
||||||
// ${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.
|
|
||||||
{
|
{
|
||||||
"version": "0.1.0",
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
// we want to run npm
|
{
|
||||||
"command": "npm",
|
"type": "npm",
|
||||||
|
"script": "watch",
|
||||||
// the command is a shell script
|
"problemMatcher": "$tsc-watch",
|
||||||
"isShellCommand": true,
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
// show the output window only if unrecognized errors occur.
|
"reveal": "never"
|
||||||
"showOutput": "silent",
|
},
|
||||||
|
"group": {
|
||||||
// we run the custom script "compile" as defined in package.json
|
"kind": "build",
|
||||||
"args": ["run", "compile", "--loglevel", "silent"],
|
"isDefault": true
|
||||||
|
}
|
||||||
// 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"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
.vscode/**
|
.vscode/**
|
||||||
.vscode-test/**
|
.vscode-test/**
|
||||||
out/test/**
|
out/test/**
|
||||||
test/**
|
|
||||||
src/**
|
src/**
|
||||||
**/*.map
|
|
||||||
.gitignore
|
.gitignore
|
||||||
tsconfig.json
|
|
||||||
vsc-extension-quickstart.md
|
vsc-extension-quickstart.md
|
||||||
|
**/tsconfig.json
|
||||||
|
**/tslint.json
|
||||||
|
**/*.map
|
||||||
|
**/*.ts
|
||||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,5 +1,10 @@
|
|||||||
# Change Log
|
# 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
|
### 0.3.7
|
||||||
* Add `lint` configuration to control the behaviors of syntax checking
|
* Add `lint` configuration to control the behaviors of syntax checking
|
||||||
* Fix error with run godot editor when the editor contains spaces
|
* Fix error with run godot editor when the editor contains spaces
|
||||||
@@ -16,7 +21,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 0.3.6
|
### 0.3.6
|
||||||
* Fix project configuartion file path
|
* Fix project configuration file path
|
||||||
|
|
||||||
### 0.3.5
|
### 0.3.5
|
||||||
* Add option to disable syntax checking for GDScript
|
* Add option to disable syntax checking for GDScript
|
||||||
@@ -56,7 +61,7 @@
|
|||||||
* Limited code completions
|
* Limited code completions
|
||||||
|
|
||||||
### 0.2.9
|
### 0.2.9
|
||||||
* Add configuration `GodotTools.completeNodePath` to switch is complete node pathes
|
* Add configuration `GodotTools.completeNodePath` to switch is complete node paths
|
||||||
* Enhanced syntax highlight with GDScript
|
* Enhanced syntax highlight with GDScript
|
||||||
* Enhanced code completion with GDScript
|
* Enhanced code completion with GDScript
|
||||||
|
|
||||||
@@ -69,7 +74,7 @@
|
|||||||
|
|
||||||
* Fix some error with syntax checking
|
* Fix some error with syntax checking
|
||||||
* Add symbol support for enumerations
|
* Add symbol support for enumerations
|
||||||
* Remove key bindings for `F5`~`F8` as it might be confict with other functionalities of VSCode
|
* 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
|
* You can bind the key bindings back by add following configurations
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -105,14 +110,14 @@
|
|||||||
|
|
||||||
* Run games within VSCode terminals
|
* Run games within VSCode terminals
|
||||||
* Add key bindings for `F5 to run the workspace` and `F6 to run the edting scene`
|
* Add key bindings for `F5 to run the workspace` and `F6 to run the edting scene`
|
||||||
* Fix a lot of bugs with unused vaiable cheching
|
* Fix a lot of bugs with unused variable checking
|
||||||
* Move workspace symbols state notice to status bar
|
* Move workspace symbols state notice to status bar
|
||||||
|
|
||||||
### 0.2.4
|
### 0.2.4
|
||||||
|
|
||||||
* Add code cheching for asignments and comparations
|
* Add code checking for asignments and comparisons
|
||||||
* Impoved builtin documentation preview page
|
* Improved builtin documentation preview page
|
||||||
* Fix bugs with unused vaiable cheching
|
* Fix bugs with unused variable checking
|
||||||
|
|
||||||
### 0.2.3
|
### 0.2.3
|
||||||
* Fix known errors with code syntax checking
|
* Fix known errors with code syntax checking
|
||||||
@@ -122,7 +127,7 @@
|
|||||||
|
|
||||||
### 0.2.2
|
### 0.2.2
|
||||||
* Better Syntax validating for code blocks
|
* Better Syntax validating for code blocks
|
||||||
* More waring for non-python liked expression
|
* More warning for non-python liked expression
|
||||||
|
|
||||||
### 0.2.1
|
### 0.2.1
|
||||||
* Support markdown render in hover tips for documentations in workspace symbols
|
* Support markdown render in hover tips for documentations in workspace symbols
|
||||||
@@ -130,14 +135,14 @@
|
|||||||
|
|
||||||
### 0.2.0
|
### 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
|
* Fix the bug that workspace symbols resoved twice on Windows
|
||||||
|
|
||||||
### 0.1.9
|
### 0.1.9
|
||||||
|
|
||||||
* Show workspace constant value in hover tips and completion items
|
* Show workspace constant value in hover tips and completion items
|
||||||
* More readable style for links in documentation preview page
|
* 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
|
* Fix bugs with workspace documentation parsing
|
||||||
|
|
||||||
### 0.1.8
|
### 0.1.8
|
||||||
@@ -154,7 +159,7 @@
|
|||||||
|
|
||||||
* Reorder mouse hover tips, builtin methods are at top of workspace methods
|
* Reorder mouse hover tips, builtin methods are at top of workspace methods
|
||||||
* Show callabel signatures with documente symbols and workspace symbols
|
* Show callabel signatures with documente symbols and workspace symbols
|
||||||
* Syntax highlight support for signal paramaters
|
* Syntax highlight support for signal parameters
|
||||||
|
|
||||||
### 0.1.5
|
### 0.1.5
|
||||||
|
|
||||||
|
|||||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|||||||
89
README.md
89
README.md
@@ -1,64 +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
|
## Features
|
||||||
|
|
||||||
The extension comes with a wealth of features to make your Godot 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 (`.gd`) language
|
- Syntax highlighting for the GDScript (`.gd`) language
|
||||||
- Syntax highlighting for the `.tscn` and `.tres` scene formats
|
- 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)
|
- Function definitions and documentation display on hover (see image below)
|
||||||
- Rich auto-completion
|
- Rich autocompletion
|
||||||
- Static code validation
|
- Display script warnings and errors
|
||||||
- Open projects and scenes in Godot from VS Code
|
- Ctrl + click on a variable or method call to jump to its definition
|
||||||
- Ctrl-click on a variable or method call to jump to its definition
|
- Full documentation of the Godot Engine's API supported
|
||||||
- Full documentation of the Godot engine's API supported
|
- Run a Godot project from VS Code
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Available Commands
|
## Available commands
|
||||||
|
|
||||||
The extension adds a few entries to the VS Code Command Palette under "GodotTools":
|
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
|
- Open workspace with Godot editor
|
||||||
- Run current scene
|
- Run the workspace as a Godot project
|
||||||
|
- List Godot's native classes
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
### Godot
|
### Godot
|
||||||
|
|
||||||
If you like this extension, you can set VS Code as your default script editor for Godot by following these steps:
|
If you like this extension, you can set VS Code as your default script editor
|
||||||
1. Open editor settings
|
for Godot by following these steps:
|
||||||
2. Select `Text Editor / External`
|
|
||||||
3. Make sure the `Use External Editor` box is checked
|
1. Open the **Editor Settings**
|
||||||
4. Fill `Exec Path` with the path to your VS Code executable
|
2. Select **Text Editor > External**
|
||||||
5. Fill `Exec Flags` with `{project} --goto {file}:{line}:{col}`
|
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}`
|
||||||
|
|
||||||
### VS Code
|
### VS Code
|
||||||
|
|
||||||
You can use the following settings to configure Godot Tools:
|
You can use the following settings to configure Godot Tools:
|
||||||
- **GodotTools.godotVersion** - The Godot version of your project.
|
|
||||||
- **GodotTools.editorPath** - The absolute path to the Godot executable. Required to run the project and test scenes directly from VS Code.
|
|
||||||
- **GodotTools.workspaceDocumentWithMarkdown** - Control how the documentation of workspace symbols should be rendered: as plain text or as HTML from Markdown.
|
|
||||||
- **GodotTools.ignoreIndentedVars** - Only parse variables defined on lines without an indentation.
|
|
||||||
- **GodotTools.parseTextScene** - Parse a file as a Godot scene when the file name ends with `.tscn`.
|
|
||||||
- **GodotTools.completeNodePath** - Show node paths within a workspace as part of code completion.
|
|
||||||
- **GodotTools.godotProjectRoot** - Your Godot project's directory, which contains `project.godot` or `engine.cfg`.
|
|
||||||
|
|
||||||
## Issues and Contributions
|
- `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.
|
||||||
|
|
||||||
The [Godot Tools](https://github.com/GodotExplorer/godot-tools) extension and [engine modules](https://github.com/GodotExplorer/editor-server) are both hosted on GitHub. Feel free to open issues there and create pull requests anytime.
|
## Issues and contributions
|
||||||
|
|
||||||
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md) for the latest changes.
|
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.
|
||||||
|
|
||||||
|
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
|
||||||
|
for the latest changes.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Why isn't Intellisense showing up for me?
|
|
||||||
|
|
||||||
Make sure you save your `.gd` file, then run "GodotTools: Update Workspace Symbols" from the Command Palette.
|
### Why does it fail to connect to the language server?
|
||||||
|
|
||||||
## TODO:
|
- Godot 3.2 or later is required.
|
||||||
* Convert official BBCode documentation into Markdown and render it into HTML with documentation previewer pages
|
- Make sure to open the project in the Godot editor first. If you opened
|
||||||
* Add mermaid support with documentation
|
the editor after opening VS Code, you can click the **Retry** button
|
||||||
* Undefined variable checking
|
in the bottom-right corner in VS Code.
|
||||||
|
|
||||||
|
### Why isn't IntelliSense displaying script members?
|
||||||
|
|
||||||
|
- 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
@@ -6,36 +6,28 @@
|
|||||||
"name": "GDScript",
|
"name": "GDScript",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{ "include": "#base_expression" },
|
{ "include": "#base_expression" },
|
||||||
{ "include": "#self" },
|
|
||||||
{ "include": "#logic_op" },
|
{ "include": "#logic_op" },
|
||||||
{ "include": "#compare_op" },
|
{ "include": "#compare_op" },
|
||||||
{ "include": "#arithmetic_op" },
|
{ "include": "#arithmetic_op" },
|
||||||
{ "include": "#assignment_op" },
|
{ "include": "#assignment_op" },
|
||||||
{ "include": "#keywords" },
|
{ "include": "#keywords" },
|
||||||
|
{ "include": "#self" },
|
||||||
{ "include": "#const_def" },
|
{ "include": "#const_def" },
|
||||||
{ "include": "#var_def" },
|
{ "include": "#type_declear"},
|
||||||
{ "include": "#class_def" },
|
{ "include": "#class_def" },
|
||||||
{ "match": "\\b(?i:export|tool)\\b", "name": "storage.modifier.static.gdscript" },
|
{ "include": "#class_name"},
|
||||||
{ "include": "#builtinFuncs" },
|
{ "include": "#builtin_func" },
|
||||||
{
|
{ "include": "#builtin_classes" },
|
||||||
"match": "\\b([A-Za-z_]\\w*)\\b(?=\\s*(?:[(]))",
|
{ "include": "#const_vars" },
|
||||||
"name": "support.function.any-method.gdscript"
|
{ "include": "#class_new"},
|
||||||
},
|
{ "include": "#class_is"},
|
||||||
{
|
{ "include": "#class_enum"},
|
||||||
"match": "(?<=[^.]\\.)\\b([A-Za-z_]\\w*)\\b(?![(])",
|
|
||||||
"name": "variable.other.property.gdscript"
|
|
||||||
},
|
|
||||||
{ "include": "#function-declaration" },
|
{ "include": "#function-declaration" },
|
||||||
|
{ "include": "#function-return-type" },
|
||||||
{
|
{ "include": "#any-method" },
|
||||||
"match": "(?<=extends)\\s+[a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?",
|
{ "include": "#any-property" },
|
||||||
"name": "entity.other.inherited-class.gdscript"
|
{ "include": "#extends" },
|
||||||
},
|
{ "include": "#parscal_class" }
|
||||||
|
|
||||||
{ "include": "#builtinClasses" },
|
|
||||||
{ "include": "#builtinProps" },
|
|
||||||
{ "include": "#builtinConsts" },
|
|
||||||
{ "include": "#const_vars" }
|
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"comment": {
|
"comment": {
|
||||||
@@ -50,17 +42,32 @@
|
|||||||
"strings": {
|
"strings": {
|
||||||
"patterns": [{
|
"patterns": [{
|
||||||
"begin": "\"",
|
"begin": "\"",
|
||||||
"end": "(?<!\\\\)\"",
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{ "name": "constant.character.escape.untitled",
|
||||||
|
"match": "\\."
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "string.quoted.double.gdscript"
|
"name": "string.quoted.double.gdscript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"begin": "'",
|
"begin": "'",
|
||||||
"end": "(?<!\\\\)'",
|
"end": "'",
|
||||||
|
"patterns": [
|
||||||
|
{ "name": "constant.character.escape.untitled",
|
||||||
|
"match": "\\."
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "string.quoted.single.gdscript"
|
"name": "string.quoted.single.gdscript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"begin": "@\"",
|
"begin": "@\"",
|
||||||
"end": "(?<!\\\\)\"",
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{ "name": "constant.character.escape.untitled",
|
||||||
|
"match": "\\."
|
||||||
|
}
|
||||||
|
],
|
||||||
"name": "string.nodepath.gdscript"
|
"name": "string.nodepath.gdscript"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -96,8 +103,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|in|is|return|onready|setget|enum|match|breakpoint|tool|extends|signal|class)\\b",
|
"match": "\\b(?i: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",
|
||||||
"name": "keyword.control.gdscript"
|
"name": "keyword.language.gdscript"
|
||||||
},
|
},
|
||||||
"letter": {
|
"letter": {
|
||||||
"match": "\\b(?i:true|false|null)\\b",
|
"match": "\\b(?i:true|false|null)\\b",
|
||||||
@@ -130,14 +137,26 @@
|
|||||||
"match": "\\b(?i:(const))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
|
"match": "\\b(?i:(const))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
|
||||||
"captures": {
|
"captures": {
|
||||||
"1": { "name": "storage.type.const.gdscript" },
|
"1": { "name": "storage.type.const.gdscript" },
|
||||||
"2": { "name": "constant.other.gdscript" }
|
"2": { "name": "constant.language.gdscript" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"var_def": {
|
"var_def": {
|
||||||
"match": "\\b(?i:(var))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
|
"match": "\\b(?i:(var))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
|
||||||
"captures": {
|
"captures": {
|
||||||
"1": { "name": "storage.type.var.gdscript" },
|
"1": { "name": "storage.type.var.gdscript" },
|
||||||
"2": { "name": "support.member.gdscript" }
|
"2": { "name": "variable.language.gdscript" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type_declear": {
|
||||||
|
"match": "\\:\\s*([a-zA-Z_][a-zA-Z_0-9]*)",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"function-return-type": {
|
||||||
|
"match": "\\)\\s*\\-\\>\\s*([a-zA-Z_][a-zA-Z_0-9]*)\\s*\\:",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"class_def": {
|
"class_def": {
|
||||||
@@ -147,19 +166,50 @@
|
|||||||
},
|
},
|
||||||
"match": "(?<=^class)\\s+([a-zA-Z_]\\w*)\\s*(?=:)"
|
"match": "(?<=^class)\\s+([a-zA-Z_]\\w*)\\s*(?=:)"
|
||||||
},
|
},
|
||||||
|
"class_new": {
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" },
|
||||||
|
"2": { "name": "storage.type.new.gdscript" }
|
||||||
|
},
|
||||||
|
"match": "\\b([a-zA-Z_][a-zA-Z_0-9]*).(new)\\("
|
||||||
|
},
|
||||||
|
"class_is": {
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "storage.type.is.gdscript" },
|
||||||
|
"2": { "name": "entity.name.type.class.gdscript" }
|
||||||
|
},
|
||||||
|
"match": "\\s+(is)\\s+([a-zA-Z_][a-zA-Z_0-9]*)"
|
||||||
|
},
|
||||||
|
"class_enum": {
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" },
|
||||||
|
"2": { "name": "constant.language.gdscript" }
|
||||||
|
},
|
||||||
|
"match": "\\b([A-Z][a-zA-Z_0-9]*)\\.([A-Z_0-9]+)"
|
||||||
|
},
|
||||||
|
"class_name": {
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" },
|
||||||
|
"2": { "name": "class.other.gdscript" }
|
||||||
|
},
|
||||||
|
"match": "(?<=class_name)\\s+([a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?)"
|
||||||
|
},
|
||||||
|
"extends": {
|
||||||
|
"match": "(?<=extends)\\s+[a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?",
|
||||||
|
"name": "entity.other.inherited-class.gdscript"
|
||||||
|
},
|
||||||
"builtin_func": {
|
"builtin_func": {
|
||||||
"match": "(?<![^.]\\.|:)\\b(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)\\b(?=(\\()([^)]*)(\\)))",
|
"match": "(?<![^.]\\.|:)\\b(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)\\b(?=(\\()([^)]*)(\\)))",
|
||||||
"name": "support.function.builtin.gdscript"
|
"name": "support.function.builtin.gdscript"
|
||||||
},
|
},
|
||||||
"builtinClasses": {
|
"builtin_classes": {
|
||||||
"match": "(?<![^.]\\.|:)\\b(Vector2|Vector3|Color|Rect2|Array|Basis|Dictionary|Plane|Quat|RID|Rect3|Transform|Transform2D)\\b",
|
"match": "(?<![^.]\\.|:)\\b(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",
|
||||||
"name": "support.class.library.gdscript"
|
"name": "support.class.library.gdscript"
|
||||||
},
|
},
|
||||||
"const_vars": {
|
"const_vars": {
|
||||||
"match": "\\b([A-Z_0-9]+)\\b",
|
"match": "\\b([A-Z_0-9]+)\\b",
|
||||||
"name": "constant.other.gdscript"
|
"name": "constant.language.gdscript"
|
||||||
},
|
},
|
||||||
|
|
||||||
"function-declaration": {
|
"function-declaration": {
|
||||||
"name": "meta.function.gdscript",
|
"name": "meta.function.gdscript",
|
||||||
"begin": "(?x)\n \\s*\n (?:\\b(static) \\s+)? \\b(func|signal)\\s+\n (?=\n [[:alpha:]_][[:word:]]* \\s* \\(\n )\n",
|
"begin": "(?x)\n \\s*\n (?:\\b(static) \\s+)? \\b(func|signal)\\s+\n (?=\n [[:alpha:]_][[:word:]]* \\s* \\(\n )\n",
|
||||||
@@ -213,6 +263,14 @@
|
|||||||
{ "include": "#annotated-parameter" }
|
{ "include": "#annotated-parameter" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"any-method": {
|
||||||
|
"match": "\\b([A-Za-z_]\\w*)\\b(?=\\s*(?:[(]))",
|
||||||
|
"name": "support.function.any-method.gdscript"
|
||||||
|
},
|
||||||
|
"any-property": {
|
||||||
|
"match": "(?<=[^.]\\.)\\b([A-Za-z_]\\w*)\\b(?![(])",
|
||||||
|
"name": "variable.other.property.gdscript"
|
||||||
|
},
|
||||||
"parameter-special": {
|
"parameter-special": {
|
||||||
"match": "(?x)\n \\b ((self)|(cls)) \\b \\s*(?:(,)|(?=\\)))\n",
|
"match": "(?x)\n \\b ((self)|(cls)) \\b \\s*(?:(,)|(?=\\)))\n",
|
||||||
"captures": {
|
"captures": {
|
||||||
@@ -269,6 +327,12 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"parscal_class": {
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.class.gdscript" }
|
||||||
|
},
|
||||||
|
"match": "([A-Z][a-zA-Z_0-9]*)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,5 +25,12 @@
|
|||||||
"indentationRules": {
|
"indentationRules": {
|
||||||
"increaseIndentPattern": "^\\s*((class|func|else|elif|for|if|match|while|enum)|(.*\\sdo\\b))\\b[^\\{;]*$",
|
"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)"
|
"decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(else|elif)\\b)"
|
||||||
}
|
},
|
||||||
|
"folding": {
|
||||||
|
"offSide": true,
|
||||||
|
"markers": {
|
||||||
|
"start": "^\\s*#\\s*region\\b",
|
||||||
|
"end": "^\\s*#\\s*endregion\\b"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
// 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:
|
|
||||||
"Inner class": {
|
"Inner class": {
|
||||||
"prefix": "class",
|
"prefix": "class",
|
||||||
"body": [
|
"body": [
|
||||||
|
|||||||
2
doc/.gitignore
vendored
2
doc/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
classes/*.xml
|
|
||||||
classes-*.xml
|
|
||||||
83763
doc/classes-2.1.json
83763
doc/classes-2.1.json
File diff suppressed because it is too large
Load Diff
90257
doc/classes-3.0.json
90257
doc/classes-3.0.json
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
import sys
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import json
|
|
||||||
|
|
||||||
def parseClass(data):
|
|
||||||
dictCls = dict(data.attrib)
|
|
||||||
dictCls['brief_description'] = data.find("brief_description").text.strip()
|
|
||||||
dictCls['description'] = data.find("description").text.strip()
|
|
||||||
dictCls['methods'] = []
|
|
||||||
for m in data.find("methods"):
|
|
||||||
dictCls['methods'].append(parseMethod(m))
|
|
||||||
dictCls['signals'] = []
|
|
||||||
for s in (data.find("signals") if data.find("signals") is not None else []):
|
|
||||||
dictCls['signals'].append(parseMethod(s))
|
|
||||||
dictCls['constants'] = []
|
|
||||||
for c in (data.find("constants") if data.find("constants") is not None else []):
|
|
||||||
dictCls['constants'].append(parseConstant(c))
|
|
||||||
dictCls['properties'] = []
|
|
||||||
for m in (data.find("members") if data.find("members") is not None else []):
|
|
||||||
dictCls['properties'].append(parseProperty(m))
|
|
||||||
dictCls['theme_properties'] = []
|
|
||||||
for thi in (data.find("theme_items") if data.find("theme_items") is not None else []):
|
|
||||||
dictCls['theme_properties'].append(parseProperty(thi))
|
|
||||||
return dictCls
|
|
||||||
|
|
||||||
def parseMethod(data):
|
|
||||||
dictMethod = dict(data.attrib)
|
|
||||||
dictMethod['description'] = data.find("description").text.strip()
|
|
||||||
dictMethod['return_type'] = data.find("return").attrib["type"] if data.find("return") is not None else ""
|
|
||||||
if "qualifiers" not in dictMethod: dictMethod["qualifiers"] = ""
|
|
||||||
dictMethod["arguments"] = []
|
|
||||||
for arg in data.iter('argument'):
|
|
||||||
dictMethod["arguments"].append(parseArgument(arg))
|
|
||||||
return dictMethod
|
|
||||||
|
|
||||||
def parseArgument(data):
|
|
||||||
dictArg = dict(data.attrib)
|
|
||||||
if "dictArg" in dictArg: dictArg.pop("index")
|
|
||||||
dictArg["default_value"] = dictArg["default"] if "default" in dictArg else ""
|
|
||||||
if "default" in dictArg: dictArg.pop("default")
|
|
||||||
return dictArg
|
|
||||||
|
|
||||||
def parseConstant(data):
|
|
||||||
dictConst = dict(data.attrib)
|
|
||||||
dictConst["description"] = data.text.strip()
|
|
||||||
return dictConst
|
|
||||||
|
|
||||||
def parseProperty(data):
|
|
||||||
dictProp = dict(data.attrib)
|
|
||||||
dictProp["description"] = data.text.strip()
|
|
||||||
return dictProp
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) >=2 :
|
|
||||||
tree = ET.parse(open(sys.argv[1], 'r'))
|
|
||||||
classes = {}
|
|
||||||
for cls in tree.getroot():
|
|
||||||
dictCls = parseClass(cls)
|
|
||||||
classes[dictCls['name']] = dictCls
|
|
||||||
jsonContent = json.dumps({"classes": classes, "version": "2.1.4"}, ensure_ascii=False, indent=2)
|
|
||||||
print(jsonContent)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
import sys
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
def glob_path(path, pattern):
|
|
||||||
import os, fnmatch
|
|
||||||
result = []
|
|
||||||
for root, _, files in os.walk(path):
|
|
||||||
for filename in files:
|
|
||||||
if fnmatch.fnmatch(filename, pattern):
|
|
||||||
result.append(os.path.join(root, filename))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def parseClass(data):
|
|
||||||
dictCls = dict(data.attrib)
|
|
||||||
dictCls['brief_description'] = data.find("brief_description").text.strip()
|
|
||||||
dictCls['description'] = data.find("description").text.strip()
|
|
||||||
dictCls['methods'] = []
|
|
||||||
for m in data.find("methods"):
|
|
||||||
dictCls['methods'].append(parseMethod(m))
|
|
||||||
dictCls['signals'] = []
|
|
||||||
for s in (data.find("signals") if data.find("signals") is not None else []):
|
|
||||||
dictCls['signals'].append(parseMethod(s))
|
|
||||||
dictCls['constants'] = []
|
|
||||||
for c in (data.find("constants") if data.find("constants") is not None else []):
|
|
||||||
dictCls['constants'].append(parseConstant(c))
|
|
||||||
dictCls['properties'] = []
|
|
||||||
for m in (data.find("members") if data.find("members") is not None else []):
|
|
||||||
dictCls['properties'].append(parseProperty(m))
|
|
||||||
dictCls['theme_properties'] = []
|
|
||||||
for thi in (data.find("theme_items") if data.find("theme_items") is not None else []):
|
|
||||||
dictCls['theme_properties'].append(parseProperty(thi))
|
|
||||||
return dictCls
|
|
||||||
|
|
||||||
def parseMethod(data):
|
|
||||||
dictMethod = dict(data.attrib)
|
|
||||||
dictMethod['description'] = data.find("description").text.strip()
|
|
||||||
dictMethod['return_type'] = data.find("return").attrib["type"] if data.find("return") is not None else ""
|
|
||||||
if "qualifiers" not in dictMethod: dictMethod["qualifiers"] = ""
|
|
||||||
dictMethod["arguments"] = []
|
|
||||||
for arg in data.iter('argument'):
|
|
||||||
dictMethod["arguments"].append(parseArgument(arg))
|
|
||||||
return dictMethod
|
|
||||||
|
|
||||||
def parseArgument(data):
|
|
||||||
dictArg = dict(data.attrib)
|
|
||||||
if "dictArg" in dictArg: dictArg.pop("index")
|
|
||||||
dictArg["default_value"] = dictArg["default"] if "default" in dictArg else ""
|
|
||||||
if "default" in dictArg: dictArg.pop("default")
|
|
||||||
return dictArg
|
|
||||||
|
|
||||||
def parseConstant(data):
|
|
||||||
dictConst = dict(data.attrib)
|
|
||||||
dictConst["description"] = data.text.strip()
|
|
||||||
return dictConst
|
|
||||||
|
|
||||||
def parseProperty(data):
|
|
||||||
dictProp = dict(data.attrib)
|
|
||||||
dictProp["description"] = data.text.strip()
|
|
||||||
return dictProp
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) >=2 :
|
|
||||||
if os.path.isdir(sys.argv[1]):
|
|
||||||
classes = {}
|
|
||||||
for f in glob_path(sys.argv[1], "**.xml"):
|
|
||||||
if f.find("/classes/") == -1 and f.find("/doc_classes/") == -1:
|
|
||||||
continue
|
|
||||||
tree = ET.parse(open(f, 'r'))
|
|
||||||
cls = tree.getroot()
|
|
||||||
dictCls = parseClass(cls)
|
|
||||||
classes[dictCls['name']] = dictCls
|
|
||||||
jsonContent = json.dumps({"classes": classes, "version": "3.0.4"}, ensure_ascii=False, indent=2)
|
|
||||||
print(jsonContent)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
BIN
icon.png
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 194 KiB |
BIN
img/godot-tools.png
Normal file
BIN
img/godot-tools.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
2236
package-lock.json
generated
2236
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
270
package.json
270
package.json
@@ -1,157 +1,117 @@
|
|||||||
{
|
{
|
||||||
"name": "godot-tools",
|
"name": "godot-tools",
|
||||||
"displayName": "Godot Tools",
|
"displayName": "godot-tools",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"description": "Tools for game development with godot game engine",
|
"version": "1.0.0",
|
||||||
"version": "0.3.7",
|
"description": "Tools for game development with godot game engine",
|
||||||
"publisher": "geequlim",
|
"repository": "https://github.com/godotengine/godot-vscode-plugin",
|
||||||
"repository": "https://github.com/GodotExplorer/godot-tools",
|
"author": "The Godot Engine community",
|
||||||
"license": "MIT",
|
"publisher": "geequlim",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.1.21"
|
"vscode": "^1.33.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Other"
|
"Other"
|
||||||
],
|
],
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"workspaceContains:engine.cfg",
|
"workspaceContains:project.godot",
|
||||||
"workspaceContains:project.godot",
|
"onLanguage:gdscript"
|
||||||
"onLanguage:gdscript"
|
],
|
||||||
],
|
"main": "./out/extension.js",
|
||||||
"main": "./out/src/extension",
|
"scripts": {
|
||||||
"contributes": {
|
"vscode:prepublish": "npm run compile",
|
||||||
"commands": [
|
"compile": "tsc -p ./",
|
||||||
{
|
"lint": "tslint -p ./",
|
||||||
"command": "godot.updateWorkspaceSymbols",
|
"watch": "tsc -watch -p ./"
|
||||||
"title": "GodotTools: Update workspace symbols"
|
},
|
||||||
},
|
"contributes": {
|
||||||
{
|
"commands": [
|
||||||
"command": "godot.runWorkspace",
|
{
|
||||||
"title": "GodotTools: Run workspace as Godot project"
|
"command": "godot-tool.open_editor",
|
||||||
},
|
"title": "Godot Tools: Open workspace with Godot editor"
|
||||||
{
|
},
|
||||||
"command": "godot.openWithEditor",
|
{
|
||||||
"title": "GodotTools: Open workspace with Godot editor"
|
"command": "godot-tool.run_project",
|
||||||
},
|
"title": "Godot Tools: Run workspace as Godot project"
|
||||||
{
|
},
|
||||||
"command": "godot.runCurrentScene",
|
{
|
||||||
"title": "GodotTools: Run current scene"
|
"command": "godot-tool.list_native_classes",
|
||||||
}
|
"title": "Godot Tools: List native classes of godot"
|
||||||
],
|
}
|
||||||
"configuration": {
|
],
|
||||||
"type": "object",
|
"configuration": {
|
||||||
"title": "Godot Tools configuration",
|
"type": "object",
|
||||||
"properties": {
|
"title": "Godot Tools configuration",
|
||||||
"GodotTools.maxNumberOfProblems": {
|
"properties": {
|
||||||
"type": "number",
|
"godot_tools.gdscript_lsp_server_port": {
|
||||||
"default": 100,
|
"type": "number",
|
||||||
"description": "Controls the maximum number of problems produced by the server."
|
"default": 6008,
|
||||||
},
|
"description": "The websocket server port of the GDScript language server"
|
||||||
"GodotTools.editorPath": {
|
},
|
||||||
"type": "string",
|
"godot_tools.editor_path": {
|
||||||
"default": "",
|
"type": "string",
|
||||||
"description": "The absolute path to the Godot executable"
|
"default": "",
|
||||||
},
|
"description": "The absolute path to the Godot editor executable"
|
||||||
"GodotTools.workspaceDocumentWithMarkdown": {
|
},
|
||||||
"type": "boolean",
|
"godot-tool.check_status": {
|
||||||
"default": false,
|
"type": "string",
|
||||||
"description": "Render workspace documentations as Markdown content"
|
"default": "",
|
||||||
},
|
"description": "Check the gdscript language server connection status"
|
||||||
"GodotTools.ignoreIndentedVars": {
|
}
|
||||||
"type": "boolean",
|
}
|
||||||
"default": false,
|
},
|
||||||
"description": "Only parse variables without indents in GDScript"
|
"languages": [
|
||||||
},
|
{
|
||||||
"GodotTools.godotVersion": {
|
"id": "gdscript",
|
||||||
"type": "number",
|
"aliases": [
|
||||||
"default": 3.0,
|
"GDScript",
|
||||||
"description": "The Godot version of your project"
|
"gdscript"
|
||||||
},
|
],
|
||||||
"GodotTools.parseTextScene": {
|
"extensions": [
|
||||||
"type": "boolean",
|
".gd"
|
||||||
"default": true,
|
],
|
||||||
"description": "Parse a file as a Godot scene when the file name ends with tscn"
|
"configuration": "./configurations/gdscript-configuration.json"
|
||||||
},
|
},
|
||||||
"GodotTools.completeNodePath": {
|
{
|
||||||
"type": "boolean",
|
"id": "properties",
|
||||||
"default": false,
|
"extensions": [
|
||||||
"description": "Show node paths within a workspace as part of code completion"
|
"cfg",
|
||||||
},
|
"tres",
|
||||||
"GodotTools.godotProjectRoot": {
|
"tscn",
|
||||||
"type": "string",
|
"godot",
|
||||||
"default": "${workspaceRoot}",
|
"gdns",
|
||||||
"description": "Your Godot project's directory"
|
"gdnlib"
|
||||||
},
|
]
|
||||||
"GodotTools.enableSyntaxChecking": {
|
}
|
||||||
"type": "boolean",
|
],
|
||||||
"default": true,
|
"grammars": [
|
||||||
"description": "Turn on/off syntax checking for GDScript"
|
{
|
||||||
},
|
"language": "gdscript",
|
||||||
"GodotTools.lint": {
|
"scopeName": "source.gdscript",
|
||||||
"type": "object",
|
"path": "./configurations/GDScript.tmLanguage.json"
|
||||||
"default": {
|
}
|
||||||
"semicolon": false,
|
],
|
||||||
"conditionBrackets": false,
|
"snippets": [
|
||||||
"unusedSymbols": true
|
{
|
||||||
},
|
"language": "gdscript",
|
||||||
"description": "Lint configuration"
|
"path": "./configurations/snippets.json"
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"languages": [
|
"devDependencies": {
|
||||||
{
|
"@types/marked": "^0.6.5",
|
||||||
"id": "gdscript",
|
"@types/mocha": "^2.2.42",
|
||||||
"aliases": [
|
"@types/node": "^10.12.21",
|
||||||
"GDScript",
|
"@types/prismjs": "^1.16.0",
|
||||||
"gdscript"
|
"@types/ws": "^6.0.1",
|
||||||
],
|
"tslint": "^5.16.0",
|
||||||
"extensions": [
|
"typescript": "^3.5.1",
|
||||||
".gd"
|
"@types/vscode": "^1.40.0"
|
||||||
],
|
},
|
||||||
"configuration": "./configurations/gdscript-configuration.json"
|
"dependencies": {
|
||||||
},
|
"global": "^4.4.0",
|
||||||
{
|
"marked": "^0.7.0",
|
||||||
"id": "properties",
|
"vscode-languageclient": "^5.2.1",
|
||||||
"extensions": [
|
"ws": "^7.0.0"
|
||||||
"cfg",
|
}
|
||||||
"tres",
|
|
||||||
"tscn",
|
|
||||||
"godot",
|
|
||||||
"gdns",
|
|
||||||
"gdnlib"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "gdscript",
|
|
||||||
"scopeName": "source.gdscript",
|
|
||||||
"path": "./configurations/GDScript.tmLanguage.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"snippets": [
|
|
||||||
{
|
|
||||||
"language": "gdscript",
|
|
||||||
"path": "./configurations/snippets.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "tsc -p ./",
|
|
||||||
"compile": "node ./node_modules/typescript/bin/tsc -p ./",
|
|
||||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
|
||||||
"test": "node ./node_modules/vscode/bin/test"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mocha": "^5.2.5",
|
|
||||||
"@types/node": "^10.9.4",
|
|
||||||
"mocha": "^5.2.0",
|
|
||||||
"typescript": "^3.0.3",
|
|
||||||
"vscode": "^1.1.21"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"glob": "^7.1.1",
|
|
||||||
"vscode-debugprotocol": "^1.17.0",
|
|
||||||
"vscode-debugadapter": "^1.17.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
331
src/config.ts
331
src/config.ts
@@ -1,331 +0,0 @@
|
|||||||
import GDScriptSymbolParser from './gdscript/symbolparser';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode';
|
|
||||||
|
|
||||||
interface NodeInfo {
|
|
||||||
name: string,
|
|
||||||
type: string,
|
|
||||||
parent: string,
|
|
||||||
instance: string
|
|
||||||
};
|
|
||||||
|
|
||||||
interface CompletionSymbols {
|
|
||||||
classes : CompletionItem[],
|
|
||||||
functions : CompletionItem[],
|
|
||||||
signals : CompletionItem[],
|
|
||||||
constants : CompletionItem[],
|
|
||||||
properties : CompletionItem[],
|
|
||||||
nodes : CompletionItem[],
|
|
||||||
builtinConstants: CompletionItem[]
|
|
||||||
};
|
|
||||||
|
|
||||||
class Config {
|
|
||||||
|
|
||||||
private workspaceSymbols; // filePath: GDScript in symbolparser.ts
|
|
||||||
private builtinCompletions : CompletionSymbols;
|
|
||||||
private builtinClassDoc;
|
|
||||||
public parser: GDScriptSymbolParser;
|
|
||||||
// scriptpath : scenepath
|
|
||||||
public scriptSceneMap: Object;
|
|
||||||
// scenepath : NodeInfo[]
|
|
||||||
public nodeInfoMap: Object;
|
|
||||||
// symbolname: {completionItem: CompletionItem, rowDoc: docdata}
|
|
||||||
public builtinSymbolInfoMap: Object;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.builtinCompletions = {
|
|
||||||
classes : [],
|
|
||||||
functions : [],
|
|
||||||
signals : [],
|
|
||||||
constants : [],
|
|
||||||
properties : [],
|
|
||||||
nodes : [],
|
|
||||||
builtinConstants: []
|
|
||||||
};
|
|
||||||
this.workspaceSymbols = {};
|
|
||||||
this.builtinSymbolInfoMap = {};
|
|
||||||
this.nodeInfoMap = {};
|
|
||||||
this.scriptSceneMap = {};
|
|
||||||
this.parser = new GDScriptSymbolParser();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSymbolsFromFile(path) {
|
|
||||||
var ignoreIndentedVars = false;
|
|
||||||
if(workspace)
|
|
||||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
|
||||||
const script = this.parser.parseFile(path, ignoreIndentedVars);
|
|
||||||
this.setSymbols(path, script);
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSymbols(path, s) {
|
|
||||||
this.workspaceSymbols[this.normalizePath(path)] = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSymbols(path) {
|
|
||||||
return this.workspaceSymbols[this.normalizePath(path)];
|
|
||||||
}
|
|
||||||
|
|
||||||
setAllSymbols(s) {
|
|
||||||
this.workspaceSymbols = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllSymbols() {
|
|
||||||
return this.workspaceSymbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizePath(path) {
|
|
||||||
let newpath = path;
|
|
||||||
if( path.indexOf(":") != -1){
|
|
||||||
let parts = path.split(":");
|
|
||||||
newpath = parts[0].toUpperCase();
|
|
||||||
newpath += ":";
|
|
||||||
for(let i=1; i<parts.length; i++)
|
|
||||||
newpath += parts[i];
|
|
||||||
}
|
|
||||||
newpath = newpath.replace(/\\/g, "/");
|
|
||||||
return newpath;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadClasses(docfile: string): boolean {
|
|
||||||
let done: boolean = false;
|
|
||||||
try {
|
|
||||||
if(fs.existsSync(docfile) && fs.statSync(docfile).isFile()) {
|
|
||||||
const content = fs.readFileSync(docfile, "utf-8");
|
|
||||||
const docdata = JSON.parse(content);
|
|
||||||
if(docdata.classes) {
|
|
||||||
this.builtinClassDoc = docdata.classes;
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
if(done) {
|
|
||||||
for (let key of Object.keys(this.builtinClassDoc)) {
|
|
||||||
const classdoc = this.builtinClassDoc[key];
|
|
||||||
const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
|
|
||||||
// ---------------------- class -----------------
|
|
||||||
const item: CompletionItem = new CompletionItem(classdoc.name, CompletionItemKind.Class);
|
|
||||||
item.detail = 'Native Class';
|
|
||||||
item.documentation = classdoc.brief_description + " \n" +classdoc.description;
|
|
||||||
this.builtinCompletions.classes.push(item);
|
|
||||||
builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc};
|
|
||||||
// ----------------------- functions -----------------------
|
|
||||||
const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name)=>{
|
|
||||||
const mi = new CompletionItem(m.name, kind);
|
|
||||||
mi.insertText = insertAction(m.name) + (m.arguments.length==0?"()":"");
|
|
||||||
mi.filterText = m.name
|
|
||||||
mi.sortText = m.name
|
|
||||||
mi.detail = m.return_type;
|
|
||||||
let argstr = "";
|
|
||||||
m.arguments.map(arg=>{
|
|
||||||
argstr += `${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}${m.arguments.indexOf(arg)==m.arguments.length-1?'':', '}`;
|
|
||||||
});
|
|
||||||
// mi.label=`${m.name}(${argstr}) ${m.qualifiers}`;
|
|
||||||
let methodName = `${classdoc.name}.${m.name}`;
|
|
||||||
if (classdoc.name == m.name) methodName = m.name;
|
|
||||||
let mdoc = `${m.return_type} ${methodName}(${argstr}) ${m.qualifiers}`;
|
|
||||||
mdoc += " \n\n";
|
|
||||||
mdoc += m.description;
|
|
||||||
mi.documentation = mdoc;
|
|
||||||
if(CompletionItemKind.Interface == kind)
|
|
||||||
this.builtinCompletions.signals.push(mi);
|
|
||||||
else
|
|
||||||
this.builtinCompletions.functions.push(mi);
|
|
||||||
builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = {completionItem: mi, rowDoc: m};
|
|
||||||
};
|
|
||||||
// methods
|
|
||||||
const methods = classdoc.methods
|
|
||||||
methods.map(m=>parsMethod(m, CompletionItemKind.Method));
|
|
||||||
// signals
|
|
||||||
const signals = classdoc.signals;
|
|
||||||
signals.map(s=>parsMethod(s, CompletionItemKind.Interface));
|
|
||||||
// ------------------------------ constants ---------------------
|
|
||||||
const constants = classdoc.constants;
|
|
||||||
constants.map(c=>{
|
|
||||||
const ci = new CompletionItem(c.name, CompletionItemKind.Enum);
|
|
||||||
ci.detail = c.value;
|
|
||||||
ci.documentation = `${classdoc.name}.${c.name} = ${c.value}`;
|
|
||||||
if(key[0] == "@" || key == "Node" || key == "Control")
|
|
||||||
this.builtinCompletions.builtinConstants.push(ci);
|
|
||||||
else
|
|
||||||
this.builtinCompletions.constants.push(ci);
|
|
||||||
builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = {completionItem: ci, rowDoc: c};
|
|
||||||
});
|
|
||||||
// ----------------------- properties -----------------------
|
|
||||||
const parseProp = (p) => {
|
|
||||||
const pi = new CompletionItem(p.name, CompletionItemKind.Property);
|
|
||||||
pi.detail = `${p.type} of ${classdoc.name}`;
|
|
||||||
pi.documentation = p.description;
|
|
||||||
this
|
|
||||||
.builtinCompletions
|
|
||||||
.properties
|
|
||||||
.push(pi);
|
|
||||||
builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = {
|
|
||||||
completionItem: pi,
|
|
||||||
rowDoc: p
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// properties
|
|
||||||
const properties = classdoc.properties;
|
|
||||||
properties.map(p=>parseProp(p));
|
|
||||||
// theme_properties
|
|
||||||
const theme_properties = classdoc.theme_properties;
|
|
||||||
theme_properties.map(p=>parseProp(p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return done;
|
|
||||||
};
|
|
||||||
|
|
||||||
getWorkspaceCompletionItems(script_files = []) : CompletionSymbols {
|
|
||||||
const symbols = {
|
|
||||||
classes: [],
|
|
||||||
functions: [],
|
|
||||||
signals: [],
|
|
||||||
constants: [],
|
|
||||||
properties: [],
|
|
||||||
nodes: [],
|
|
||||||
builtinConstants: []
|
|
||||||
};
|
|
||||||
if (script_files.length == 0)
|
|
||||||
script_files = Object.keys(this.workspaceSymbols);
|
|
||||||
for (let path of script_files) {
|
|
||||||
const script = this.workspaceSymbols[path];
|
|
||||||
if (workspace) {
|
|
||||||
const root = this.normalizePath(workspace.rootPath) + "/";
|
|
||||||
if (path.startsWith(root))
|
|
||||||
path = path.replace(root, "");
|
|
||||||
}
|
|
||||||
const addScriptItems = (items, kind: CompletionItemKind, kindName:string = "Symbol", insertText = (n)=>n)=>{
|
|
||||||
const _items: CompletionItem[] = [];
|
|
||||||
for (let name of Object.keys(items)) {
|
|
||||||
const signature = (script.signatures && script.signatures[name])?script.signatures[name]:"";
|
|
||||||
const cvalue = (script.constvalues && script.constvalues[name])?script.constvalues[name]:"";
|
|
||||||
const item = new CompletionItem(name+signature, kind);
|
|
||||||
item.sortText = name;
|
|
||||||
item.filterText = name;
|
|
||||||
item.detail = cvalue;
|
|
||||||
item.insertText = insertText(name) + (signature=="()"?"()":"");
|
|
||||||
item.documentation = (script.documents && script.documents[name])?script.documents[name]+"\r\n":"";
|
|
||||||
item.documentation += `${kindName} defined in ${path}`;
|
|
||||||
_items.push(item);
|
|
||||||
}
|
|
||||||
return _items;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbols.classes = [ ...(symbols.classes), ...(addScriptItems(script.classes, CompletionItemKind.Class, "Class"))]
|
|
||||||
symbols.functions = [ ...(symbols.functions), ...(addScriptItems(script.functions, CompletionItemKind.Method, "Method"))]
|
|
||||||
symbols.signals = [ ...(symbols.signals), ...(addScriptItems(script.signals, CompletionItemKind.Interface, "Signal"))]
|
|
||||||
symbols.properties = [ ...(symbols.properties), ...(addScriptItems(script.variables, CompletionItemKind.Variable, "Variable"))]
|
|
||||||
symbols.constants = [ ...(symbols.constants), ...(addScriptItems(script.constants, CompletionItemKind.Enum, "Constant"))]
|
|
||||||
|
|
||||||
if(script.enumerations)
|
|
||||||
symbols.constants = [...(symbols.constants), ...(addScriptItems(script.enumerations, CompletionItemKind.Enum, "Enumeration"))];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(workspace.getConfiguration("GodotTools").get("completeNodePath", false)) {
|
|
||||||
const addSceneNodes = ()=>{
|
|
||||||
const _items: CompletionItem[] = [];
|
|
||||||
for (let scnenepath of Object.keys(this.nodeInfoMap)) {
|
|
||||||
const nodes: NodeInfo[] = this.nodeInfoMap[scnenepath];
|
|
||||||
nodes.map((n=>{
|
|
||||||
const item = new CompletionItem(n.name, CompletionItemKind.Reference);
|
|
||||||
item.detail = n.type;
|
|
||||||
item.documentation = `${n.parent}/${n.name} in ${scnenepath}`;
|
|
||||||
_items.push(item);
|
|
||||||
|
|
||||||
const fullitem = new CompletionItem(`${n.parent}/${n.name}`, CompletionItemKind.Reference);
|
|
||||||
fullitem.detail = n.type;
|
|
||||||
fullitem.filterText = n.name;
|
|
||||||
fullitem.sortText = n.name;
|
|
||||||
fullitem.documentation = `${n.parent}/${n.name} in ${scnenepath}`;
|
|
||||||
_items.push(fullitem);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return _items;
|
|
||||||
};
|
|
||||||
symbols.nodes = [...(symbols.nodes), ...(addSceneNodes())];
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadScene(scenePath: string) {
|
|
||||||
if(fs.existsSync(scenePath) && fs.statSync(scenePath).isFile()) {
|
|
||||||
try {
|
|
||||||
const content: string = fs.readFileSync(scenePath, 'utf-8');
|
|
||||||
if(content) {
|
|
||||||
// extern resources
|
|
||||||
const exteres = {};
|
|
||||||
let reg = /ext_resource path="res:\/\/(.*)" type="(.*)" id=(\d+)/g;
|
|
||||||
let match = reg.exec(content);
|
|
||||||
while (match != null) {
|
|
||||||
const path = match[1];
|
|
||||||
const type = match[2];
|
|
||||||
const id = match[3];
|
|
||||||
exteres[id] = {path, type};
|
|
||||||
if (type == "Script") {
|
|
||||||
let workspacescenepath = scenePath;
|
|
||||||
if(workspace)
|
|
||||||
workspacescenepath = workspace.asRelativePath(scenePath);
|
|
||||||
this.scriptSceneMap[path] = workspacescenepath;
|
|
||||||
}
|
|
||||||
match = reg.exec(content);
|
|
||||||
}
|
|
||||||
// nodes
|
|
||||||
const nodes: NodeInfo[] = [];
|
|
||||||
reg = /node\s+name="(.*)"\s+type="(.*)"\s+parent="(.*)"/g;
|
|
||||||
match = reg.exec(content);
|
|
||||||
while (match != null) {
|
|
||||||
nodes.push({
|
|
||||||
name : match[1],
|
|
||||||
type : match[2],
|
|
||||||
parent : match[3],
|
|
||||||
instance: ""
|
|
||||||
});
|
|
||||||
match = reg.exec(content);
|
|
||||||
}
|
|
||||||
// packed scenes
|
|
||||||
reg = /node name="(.*)" parent="(.*)" instance=ExtResource\(\s*(\d+)\s*\)/g;
|
|
||||||
match = reg.exec(content);
|
|
||||||
while (match != null) {
|
|
||||||
const id = match[3];
|
|
||||||
nodes.push({
|
|
||||||
name : match[1],
|
|
||||||
type : exteres[id].type,
|
|
||||||
parent : match[2],
|
|
||||||
instance: exteres[id].path
|
|
||||||
});
|
|
||||||
match = reg.exec(content);
|
|
||||||
}
|
|
||||||
if(workspace)
|
|
||||||
scenePath = workspace.asRelativePath(scenePath);
|
|
||||||
this.nodeInfoMap[scenePath] = nodes;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getClass(name: string) {
|
|
||||||
return this.builtinClassDoc[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
getBuiltinCompletions() {
|
|
||||||
return this.builtinCompletions;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBuiltinClassNameList() {
|
|
||||||
let namelist = null;
|
|
||||||
if (this.builtinClassDoc)
|
|
||||||
namelist = Object.keys(this.builtinClassDoc);
|
|
||||||
if(!namelist)
|
|
||||||
namelist = [];
|
|
||||||
return namelist;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default new Config();
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
// import {
|
|
||||||
// DebugSession,
|
|
||||||
// InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent, Event,
|
|
||||||
// Thread, StackFrame, Scope, Source, Handles, Breakpoint
|
|
||||||
// } from 'vscode-debugadapter';
|
|
||||||
// import {DebugProtocol} from 'vscode-debugprotocol';
|
|
||||||
// import * as fs from 'fs';
|
|
||||||
// import * as path from 'path';
|
|
||||||
// const cmd = require('node-cmd');
|
|
||||||
|
|
||||||
// export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
|
|
||||||
// godot: string;
|
|
||||||
// projectDir: string;
|
|
||||||
// runWithEditor: boolean;
|
|
||||||
// params: string[];
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class GodotDebugSession extends DebugSession {
|
|
||||||
|
|
||||||
// // we don't support multiple threads, so we can use a hardcoded ID for the default thread
|
|
||||||
// private static THREAD_ID = 1;
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Creates a new debug adapter that is used for one debug session.
|
|
||||||
// * We configure the default implementation of a debug adapter here.
|
|
||||||
// */
|
|
||||||
// public constructor() {
|
|
||||||
// super();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * The 'initialize' request is the first request called by the frontend
|
|
||||||
// * to interrogate the features the debug adapter provides.
|
|
||||||
// */
|
|
||||||
// protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
|
||||||
|
|
||||||
// // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
|
|
||||||
// // we request them early by sending an 'initializeRequest' to the frontend.
|
|
||||||
// // The frontend will end the configuration sequence by calling 'configurationDone' request.
|
|
||||||
// this.sendEvent(new InitializedEvent());
|
|
||||||
|
|
||||||
// // This debug adapter implements the configurationDoneRequest.
|
|
||||||
// response.body.supportsConfigurationDoneRequest = true;
|
|
||||||
|
|
||||||
// // make VS Code to use 'evaluate' when hovering over source
|
|
||||||
// response.body.supportsEvaluateForHovers = true;
|
|
||||||
|
|
||||||
// // make VS Code to show a 'step back' button
|
|
||||||
// response.body.supportsStepBack = true;
|
|
||||||
|
|
||||||
// this.log("initializeRequest");
|
|
||||||
// this.log_err("initializeRequest");
|
|
||||||
// this.log_console("initializeRequest");
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
|
||||||
// for(let key of Object.keys(args))
|
|
||||||
// this.log(`${key} : ${args[key]}`);
|
|
||||||
// let workspaceValid = false
|
|
||||||
// if(args.godot && fs.existsSync(args.godot) && fs.statSync(args.godot).isFile() ) {
|
|
||||||
// if(args.projectDir && fs.existsSync(args.projectDir) && fs.statSync(args.projectDir).isDirectory() ) {
|
|
||||||
// let cfg = path.join(args.projectDir, "engine.cfg");
|
|
||||||
// if( fs.existsSync(cfg) && fs.statSync(cfg).isFile())
|
|
||||||
// workspaceValid = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if(workspaceValid) {
|
|
||||||
// let params = `-path ${args.projectDir} `;
|
|
||||||
// if(args.runWithEditor)
|
|
||||||
// params += "-e";
|
|
||||||
// if(args.params) {
|
|
||||||
// for(let p of args.params)
|
|
||||||
// params += " " + p;
|
|
||||||
// }
|
|
||||||
// let cmdcontent = `${args.godot} ${params}`;
|
|
||||||
// this.log(cmdcontent)
|
|
||||||
// // TODO: print outputs in terminal console
|
|
||||||
// cmd.run(cmdcontent);
|
|
||||||
// this.sendEvent(new TerminatedEvent());
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// this.log_err("Invalidate path of projectDir or godot:");
|
|
||||||
// this.log_err(JSON.stringify(args, null, '\t'));
|
|
||||||
// this.sendEvent(new TerminatedEvent());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
|
||||||
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
|
||||||
|
|
||||||
// // return the default thread
|
|
||||||
// response.body = {
|
|
||||||
// threads: [
|
|
||||||
// new Thread(GodotDebugSession.THREAD_ID, "thread 1")
|
|
||||||
// ]
|
|
||||||
// };
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line.
|
|
||||||
// */
|
|
||||||
// protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
|
|
||||||
// this.sendEvent(new TerminatedEvent());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
|
||||||
// this.sendResponse(response);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Fire StoppedEvent if line is not empty.
|
|
||||||
// */
|
|
||||||
// private fireStepEvent(response: DebugProtocol.Response, ln: number): boolean {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private log(msg: string) {
|
|
||||||
// const e = new OutputEvent(msg, "stdout");
|
|
||||||
// this.sendEvent(e);
|
|
||||||
// }
|
|
||||||
// private log_err(msg: string) {
|
|
||||||
// const e = new OutputEvent(msg, "stderr");
|
|
||||||
// this.sendEvent(e);
|
|
||||||
// }
|
|
||||||
// private log_console(msg: string) {
|
|
||||||
// const e = new OutputEvent(msg, "console");
|
|
||||||
// this.sendEvent(e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// DebugSession.run(GodotDebugSession);
|
|
||||||
573
src/deps/prism/prism.js
Normal file
573
src/deps/prism/prism.js
Normal 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, '&').replace(/</g, '<').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;
|
||||||
|
|
||||||
|
// Don’t 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, '"') + '"';
|
||||||
|
}).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;
|
||||||
|
}
|
||||||
|
;
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
'use strict';
|
import { ExtensionContext } from "vscode";
|
||||||
import { workspace, Disposable, ExtensionContext } from 'vscode';
|
import { GodotTools } from "./godot-tools";
|
||||||
import WindowWatch from "./window_watcher";
|
|
||||||
import ToolManager from './tool_manager';
|
|
||||||
|
let tools: GodotTools = null;
|
||||||
let tool: ToolManager = null;
|
|
||||||
|
export function activate(context: ExtensionContext) {
|
||||||
export function activate(context: ExtensionContext) {
|
tools = new GodotTools(context);
|
||||||
tool = new ToolManager(context);
|
tools.activate();
|
||||||
context.subscriptions.push(tool);
|
}
|
||||||
context.subscriptions.push(new WindowWatch());
|
|
||||||
console.log("[GodotTools]: Extension Activated");
|
export function deactivate(): Thenable<void> {
|
||||||
}
|
return new Promise((resolve, reject) => {
|
||||||
|
tools.deactivate();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
import {
|
|
||||||
CompletionItemProvider,
|
|
||||||
Position,
|
|
||||||
TextDocument,
|
|
||||||
CancellationToken,
|
|
||||||
CompletionItem,
|
|
||||||
CompletionList,
|
|
||||||
languages,
|
|
||||||
Disposable,
|
|
||||||
CompletionItemKind
|
|
||||||
} from 'vscode';
|
|
||||||
|
|
||||||
import requestGodot from '../request';
|
|
||||||
import config from '../config';
|
|
||||||
|
|
||||||
interface CompleteRequest {
|
|
||||||
path: string,
|
|
||||||
text: string,
|
|
||||||
cursor: {
|
|
||||||
row: number,
|
|
||||||
column: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CompletionResult {
|
|
||||||
suggestions: string[],
|
|
||||||
hint: string,
|
|
||||||
prefix: string,
|
|
||||||
valid: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
class GDScriptCompletionItemProvider implements CompletionItemProvider {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private get_previous_flag(document : TextDocument, position : Position): string {
|
|
||||||
const line = document.lineAt(position).text;
|
|
||||||
let res = "";
|
|
||||||
for (let index = position.character; index >= 0; index--) {
|
|
||||||
res = line[index];
|
|
||||||
if (['.', '$', '"', "'"].indexOf(res) != -1 )
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
provideCompletionItems(document : TextDocument, position : Position, token : CancellationToken) : CompletionItem[] | Thenable < CompletionItem[] > | CompletionList | Thenable < CompletionList > {
|
|
||||||
|
|
||||||
const lastFlag = this.get_previous_flag(document, position);
|
|
||||||
const builtins = config.getBuiltinCompletions();
|
|
||||||
|
|
||||||
let items:CompletionItem[] = [...(builtins.builtinConstants)];
|
|
||||||
if(!lastFlag || lastFlag.trim().length == 0) {
|
|
||||||
const workspaces = config.getWorkspaceCompletionItems([config.normalizePath(document.fileName)]);
|
|
||||||
items = [
|
|
||||||
...items,
|
|
||||||
...(workspaces.functions),
|
|
||||||
...(workspaces.classes),
|
|
||||||
...(workspaces.constants),
|
|
||||||
...(workspaces.properties),
|
|
||||||
...(builtins.functions),
|
|
||||||
...(builtins.classes),
|
|
||||||
...(builtins.constants),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const workspaces = config.getWorkspaceCompletionItems();
|
|
||||||
if(lastFlag.trim() == ".") {
|
|
||||||
items = [
|
|
||||||
...items,
|
|
||||||
...(workspaces.functions),
|
|
||||||
...(workspaces.constants),
|
|
||||||
...(workspaces.properties),
|
|
||||||
...(workspaces.classes),
|
|
||||||
...(builtins.functions),
|
|
||||||
...(builtins.constants),
|
|
||||||
...(builtins.properties)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
else if(lastFlag.trim() == "'" || lastFlag.trim() == '"') {
|
|
||||||
items = [
|
|
||||||
...items,
|
|
||||||
...(workspaces.signals),
|
|
||||||
...(workspaces.functions),
|
|
||||||
...(workspaces.properties),
|
|
||||||
...(builtins.signals),
|
|
||||||
...(builtins.functions),
|
|
||||||
...(builtins.properties),
|
|
||||||
...(workspaces.nodes),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
else if(lastFlag.trim() == "$") {
|
|
||||||
items = [ ...(workspaces.nodes) ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveCompletionItem(item : CompletionItem, token : CancellationToken) : CompletionItem | Thenable < CompletionItem > {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptCompletionItemProvider;
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import {
|
|
||||||
DefinitionProvider,
|
|
||||||
TextDocument,
|
|
||||||
Position,
|
|
||||||
CancellationToken,
|
|
||||||
Definition,
|
|
||||||
Location,
|
|
||||||
workspace,
|
|
||||||
Uri,
|
|
||||||
Range
|
|
||||||
} from 'vscode';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import config from '../config';
|
|
||||||
import {isStr, getSelectedContent, getStrContent} from './utils';
|
|
||||||
|
|
||||||
class GDScriptDefinitionProivder implements DefinitionProvider {
|
|
||||||
private _rootFolder : string = "";
|
|
||||||
|
|
||||||
constructor(rootFolder: string) {
|
|
||||||
this._rootFolder = rootFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Definition | Thenable < Definition > {
|
|
||||||
const getDefinitions = (content: string):Location[]| Location => {
|
|
||||||
if(content.startsWith("res://")) {
|
|
||||||
content = content.replace("res://", "");
|
|
||||||
if(workspace && workspace.rootPath) {
|
|
||||||
content = path.join(this._rootFolder, content);
|
|
||||||
}
|
|
||||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
|
||||||
}
|
|
||||||
else if(fs.existsSync(content) && fs.statSync(content).isFile()) {
|
|
||||||
return new Location(Uri.file(content), new Range(0,0,0,0));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const workspaceSymbols = config.getAllSymbols();
|
|
||||||
let locations: Location[] = [];
|
|
||||||
// check from workspace
|
|
||||||
for (let path of Object.keys(workspaceSymbols)) {
|
|
||||||
const script = workspaceSymbols[path];
|
|
||||||
if(path == "autoload" && script.constpathes && script.constpathes[content])
|
|
||||||
path = script.constpathes[content];
|
|
||||||
let scriptitems: Location[] = [];
|
|
||||||
const checkDifinition = (items)=>{
|
|
||||||
const _items: Location[] = [];
|
|
||||||
for (let name of Object.keys(items)) {
|
|
||||||
if(name == content) {
|
|
||||||
_items.push(new Location(Uri.file(path), items[name]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _items;
|
|
||||||
}
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.variables)];
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.constants)];
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.functions)];
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.signals)];
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.classes)];
|
|
||||||
if(script.enumerations)
|
|
||||||
scriptitems = [...scriptitems, ...checkDifinition(script.enumerations)];
|
|
||||||
locations = [...locations, ...scriptitems];
|
|
||||||
}
|
|
||||||
// check from builtin
|
|
||||||
if(config.getClass(content) != null) {
|
|
||||||
const uri = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${content}`)));
|
|
||||||
locations.push(new Location(Uri.parse(uri), new Range(0,0,0,0)));
|
|
||||||
}
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let selStr = getSelectedContent(document, position);
|
|
||||||
if(selStr) {
|
|
||||||
// For strings
|
|
||||||
if(isStr(selStr)) {
|
|
||||||
selStr = getStrContent(selStr);
|
|
||||||
let fpath = path.join(path.dirname(document.uri.fsPath), selStr)
|
|
||||||
if(fs.existsSync(fpath) && fs.statSync(fpath).isFile())
|
|
||||||
selStr = fpath
|
|
||||||
}
|
|
||||||
resolve(getDefinitions(selStr));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
reject(new Error("Empty selection"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptDefinitionProivder;
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
import requestGodot from "../request";
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
|
|
||||||
import config from '../config';
|
|
||||||
|
|
||||||
interface GDParseError {
|
|
||||||
message : string,
|
|
||||||
column : number,
|
|
||||||
row : number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GDScript {
|
|
||||||
members : {
|
|
||||||
constants: {},
|
|
||||||
functions: {},
|
|
||||||
variables: {},
|
|
||||||
signals: {}
|
|
||||||
},
|
|
||||||
base : string,
|
|
||||||
errors : GDParseError[],
|
|
||||||
valid : boolean,
|
|
||||||
is_tool : boolean,
|
|
||||||
native : string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParseRequest {
|
|
||||||
text : string,
|
|
||||||
path : string
|
|
||||||
}
|
|
||||||
|
|
||||||
class GDScriptDiagnosticSeverity {
|
|
||||||
private _subscription : DiagnosticCollection;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this._subscription.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
async validateScript(doc : vscode.TextDocument, script : any) {
|
|
||||||
if (doc.languageId == 'gdscript') {
|
|
||||||
if (script) {
|
|
||||||
let diagnostics = [ ...(this.validateExpression(doc)), ...(this.validateUnusedSymbols(doc, script)) ];
|
|
||||||
this._subscription.set(doc.uri, diagnostics);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private validateUnusedSymbols(doc : vscode.TextDocument, script) {
|
|
||||||
let diagnostics = [];
|
|
||||||
let cfg : any = vscode.workspace.getConfiguration("GodotTools").get("lint");
|
|
||||||
if (!cfg.unusedSymbols)
|
|
||||||
return diagnostics
|
|
||||||
|
|
||||||
const text = doc.getText();
|
|
||||||
const check = (name : string, range : vscode.Range) => {
|
|
||||||
var matchs = text.match(new RegExp(`([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
|
||||||
let count = matchs ? matchs.length : 0;
|
|
||||||
var incomment = text.match(new RegExp(`#.*?([^\\w]|\\[|\\{)\\s*${name}\\s*([^\\w]|\\[|\\{)`, 'g'));
|
|
||||||
count -= incomment ? incomment.length : 0;
|
|
||||||
if (count <= 1)
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning));
|
|
||||||
};
|
|
||||||
// Unused variables
|
|
||||||
for (let key of Object.keys(script.variables))
|
|
||||||
check(key, script.variables[key]);
|
|
||||||
for (let key of Object.keys(script.constants))
|
|
||||||
check(key, script.constants[key]);
|
|
||||||
return diagnostics;
|
|
||||||
}
|
|
||||||
|
|
||||||
private validateExpression(doc : vscode.TextDocument) {
|
|
||||||
let cfg : any = vscode.workspace.getConfiguration("GodotTools").get("lint");
|
|
||||||
|
|
||||||
let diagnostics = [];
|
|
||||||
let expectEndOfLine = false;
|
|
||||||
const text = doc.getText();
|
|
||||||
const lines = text.split(/\r?\n/);
|
|
||||||
lines.map((line : string, i : number) => {
|
|
||||||
let matchstart = /[^\s]+.*/.exec(line);
|
|
||||||
let curLineStartAt = 0;
|
|
||||||
if (matchstart)
|
|
||||||
curLineStartAt = matchstart.index;
|
|
||||||
|
|
||||||
// ignore comments
|
|
||||||
if (line.match(/^\s*#.*/) || line.match(/^#.*/))
|
|
||||||
return
|
|
||||||
// normalize line content
|
|
||||||
line = "\t" + line + "\t";
|
|
||||||
var range = new vscode.Range(i, curLineStartAt, i, line.length);
|
|
||||||
|
|
||||||
if (cfg.semicolon && line.match(/[^#].*?\;/) && !line.match(/[#].*?\;/)) {
|
|
||||||
const semicolonIndex = line.indexOf(';');
|
|
||||||
diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex + 1), "Statement contains a semicolon.", DiagnosticSeverity.Warning));
|
|
||||||
}
|
|
||||||
if (line.match(/[^#].*?/) && expectEndOfLine) {
|
|
||||||
if (!line.match(/.*?(\\|\:)/)) {
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
|
||||||
expectEndOfLine = false;
|
|
||||||
}
|
|
||||||
if (line.match(/.*?\:/))
|
|
||||||
expectEndOfLine = false;
|
|
||||||
}
|
|
||||||
const colonKeywords = /\b(if|elif|else|for|while|func|class|match)\b/;
|
|
||||||
let keywords = line.match(colonKeywords)
|
|
||||||
if (keywords) {
|
|
||||||
if(line.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || line.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?\'`)))
|
|
||||||
return
|
|
||||||
if(line.match(new RegExp(`.*?#.*?\\s${keywords[1]}\\s.*?`)))
|
|
||||||
return
|
|
||||||
if(line.match(/.*?\sif\s+(\!|\[|\{|\w|").*?\s+else\s+[^\s]+/))
|
|
||||||
return
|
|
||||||
if (line.match(/.*?\\/))
|
|
||||||
expectEndOfLine = true;
|
|
||||||
else if (line.match(/.*?\:[\s+]+[^#\s]+/))
|
|
||||||
return
|
|
||||||
else if (!line.match(/.*?(\\|\:)/))
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
|
|
||||||
else if (line.match(/\s(if|elif|while|func|class|match)\s*\:/))
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Indentifier expected before ':'", DiagnosticSeverity.Error));
|
|
||||||
else if (line.match(/[^\w]for[^\w]/) && !line.match(/\s+for\s\w+\s+in\s+|[\w+]|\{.*?\}|\[.*?\]|\(.*?\)/)){
|
|
||||||
if(!(line.match(/".*?for.*?"/) || line.match(/'.*?for.*?'/)))
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
|
|
||||||
}
|
|
||||||
else if (cfg.conditionBrackets && line.match(/\s(if|elif|while|match)\s*\(.*\)\s*:\s*$/))
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Extra brackets in condition expression.", DiagnosticSeverity.Warning));
|
|
||||||
const blockIndetCheck = function() {
|
|
||||||
const err = new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error);
|
|
||||||
if (i < lines.length - 1) {
|
|
||||||
let next = i + 1;
|
|
||||||
let nextline = lines[next];
|
|
||||||
// changes nextline until finds a line containg text or comes to the last line
|
|
||||||
while (((!nextline || !nextline.trim().length) || nextline.match(/^\s*#/)) && next < lines.length - 1) {
|
|
||||||
++next;
|
|
||||||
nextline = lines[next];
|
|
||||||
}
|
|
||||||
let nextLineStartAt = -1;
|
|
||||||
let match = /[^\s]+.*/.exec(nextline);
|
|
||||||
if (match)
|
|
||||||
nextLineStartAt = match.index;
|
|
||||||
|
|
||||||
if (nextLineStartAt <= curLineStartAt)
|
|
||||||
diagnostics.push(err);
|
|
||||||
}
|
|
||||||
else if(line.match(/\:\s*$/))
|
|
||||||
diagnostics.push(err);
|
|
||||||
};
|
|
||||||
if(!expectEndOfLine)
|
|
||||||
blockIndetCheck();
|
|
||||||
}
|
|
||||||
// Do not check : for end of statement as it breaks match statment
|
|
||||||
let endOfStateMentWithComma = false;
|
|
||||||
if(endOfStateMentWithComma && !line.match(colonKeywords) && line.match(/\:\s*$/)) {
|
|
||||||
let showErr = true;
|
|
||||||
if( i >= 1 ) {
|
|
||||||
let previous = i - 1;
|
|
||||||
let previousline = lines[previous];
|
|
||||||
while(previousline.match(/\\\s*$/) && previous>=1) {
|
|
||||||
--previous;
|
|
||||||
const ppreviousline = lines[previous];
|
|
||||||
if(ppreviousline.match(/\\\s*$/))
|
|
||||||
previousline = ppreviousline;
|
|
||||||
}
|
|
||||||
const keywords = previousline.match(colonKeywords);
|
|
||||||
if(keywords && !(previousline.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || previousline.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?'`)) ))
|
|
||||||
showErr = false
|
|
||||||
}
|
|
||||||
if(showErr)
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Expected end of statement after expression", DiagnosticSeverity.Error));
|
|
||||||
}
|
|
||||||
if (line.match(/(if|elif|while|return)\s+\w+\s*=\s*\w+/))
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Assignment in condition or return expressions", DiagnosticSeverity.Warning));
|
|
||||||
else if (line.indexOf("==") > 0 && !line.match(/\:\s*/)) {
|
|
||||||
const endAt = line.indexOf("==");
|
|
||||||
const precontent = line.substring(0, endAt);
|
|
||||||
if (!precontent.match(/\s(if|elif|while|return)\s/) && !precontent.match(/=[^=]/) && !precontent.match(/assert\s*\(/) && !expectEndOfLine) {
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Unhandled comparation expression contains", DiagnosticSeverity.Warning));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let match = /var\s+(\w+)\s*=\s*(\w+)/.exec(line);
|
|
||||||
if (match && match.length > 2 && match[1].length > 0 && match[1] == match[2]) {
|
|
||||||
diagnostics.push(new vscode.Diagnostic(range, "Self Assignment may cause error.", DiagnosticSeverity.Warning));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return diagnostics;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptDiagnosticSeverity;
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
import {TextDocumentContentProvider, DocumentLinkProvider, Uri, CancellationToken } from 'vscode';
|
|
||||||
import config from '../config';
|
|
||||||
|
|
||||||
const linkStyle = `
|
|
||||||
<style>
|
|
||||||
a { color: #6e8ae7; text-decoration: none;}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function genLink(title:string, uri:string, span=true):string {
|
|
||||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
|
||||||
let link = `<a href="${u}">${title}</a>`;
|
|
||||||
if(span)
|
|
||||||
link = `<span>${link}</span>`;
|
|
||||||
return link;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getProp(rawDoc:any, propname: string, action=(s :string)=>s): string {
|
|
||||||
let prop = rawDoc[propname];
|
|
||||||
return action(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide textual content for a given uri.
|
|
||||||
*
|
|
||||||
* The editor will use the returned string-content to create a readonly
|
|
||||||
* [document](TextDocument). Resources allocated should be released when
|
|
||||||
* the corresponding document has been [closed](#workspace.onDidCloseTextDocument).
|
|
||||||
*
|
|
||||||
* @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.
|
|
||||||
* @param token A cancellation token.
|
|
||||||
* @return A string or a thenable that resolves to such.
|
|
||||||
*/
|
|
||||||
provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
|
|
||||||
const request = uri.authority;
|
|
||||||
let classname = request;
|
|
||||||
let membername = null;
|
|
||||||
const self = this;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if(request.indexOf(".") != -1) {
|
|
||||||
classname = request.substring(0, request.indexOf("."));
|
|
||||||
if(!request.endsWith("."))
|
|
||||||
membername = request.substring(request.indexOf(".")+1, request.length);
|
|
||||||
}
|
|
||||||
if(classname.length >= 1) {
|
|
||||||
for(let key of config.getBuiltinClassNameList()) {
|
|
||||||
if(key.toLowerCase() == classname) {
|
|
||||||
classname = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(classname && classname.length > 0) {
|
|
||||||
if(membername && membername.length >0 )
|
|
||||||
resolve(self.genMemberDoc(classname, membername)) ;
|
|
||||||
else
|
|
||||||
resolve(self.genClassDoc(config.getClass(classname)));
|
|
||||||
}
|
|
||||||
reject(new Error("Open Documentation Failed!"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
format_documentation(text: string): string {
|
|
||||||
let doc = text.replace(/\[code\]/g, "<code>").replace(/\[\/code\]/g, "</code>");
|
|
||||||
doc = doc.replace(/\[codeblock\]/g, '<pre><code class="gdscript">').replace(/\[\/codeblock]/g, "</code></pre>");
|
|
||||||
doc = doc.replace(/\[i\]/g, "<i>").replace(/\[\/i\]/g, "</i>");
|
|
||||||
doc = doc.replace(/\[b\]/g, "<b>").replace(/\[\/b\]/g, "</b>");
|
|
||||||
doc = doc.replace(/\[u\]/g, "<u>").replace(/\[\/u\]/g, "</u>");
|
|
||||||
doc = doc.replace(/\n\t\t\t\t/g, "\n\t");
|
|
||||||
return doc;
|
|
||||||
};
|
|
||||||
|
|
||||||
genMethodDoc(mDoc:any):string {
|
|
||||||
let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
|
|
||||||
if(type.length > 0)
|
|
||||||
return `${genLink(type,type)} `;
|
|
||||||
else
|
|
||||||
return "void";
|
|
||||||
});
|
|
||||||
let args = "";
|
|
||||||
for(let arg of mDoc.arguments){
|
|
||||||
if(mDoc.arguments.indexOf(arg)!=0)
|
|
||||||
args += ", ";
|
|
||||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
|
||||||
if(arg.default_value && arg.default_value.length > 0)
|
|
||||||
args += `=${arg.default_value}`;
|
|
||||||
}
|
|
||||||
var docContent = mDoc.description;
|
|
||||||
if (!docContent) {
|
|
||||||
docContent = `There is currently no description for this method. Please help us by <span><a href="http://docs.godotengine.org/en/latest/community/contributing/updating_the_class_reference.html">contributing one</a></span>!`;
|
|
||||||
}
|
|
||||||
let doc = `
|
|
||||||
<li>
|
|
||||||
<h4 id="${mDoc.name}">${ret_type} ${mDoc.name} (${args}) <i>${mDoc.qualifiers}</i></h4>
|
|
||||||
<p>${docContent}</p>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
genPropHeader(mDoc:any, classname:string): string {
|
|
||||||
let type = getProp(mDoc, "type", (type:string):string => `${genLink(type,type)} `);
|
|
||||||
return `<li>${type} ${genLink(mDoc.name, classname+"."+mDoc.name)}</li>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
genMethodHeader(mDoc:any, classname:string):string {
|
|
||||||
let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
|
|
||||||
if(type.length > 0)
|
|
||||||
return `${genLink(type,type)} `;
|
|
||||||
else
|
|
||||||
return "void";
|
|
||||||
});
|
|
||||||
let args = "";
|
|
||||||
for(let arg of mDoc.arguments){
|
|
||||||
if(mDoc.arguments.indexOf(arg)!=0)
|
|
||||||
args += ", ";
|
|
||||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
|
||||||
if(arg.default_value && arg.default_value.length > 0)
|
|
||||||
args += `=${arg.default_value}`;
|
|
||||||
}
|
|
||||||
let doc = `
|
|
||||||
<li>
|
|
||||||
${ret_type} ${genLink(mDoc.name, classname+"."+mDoc.name)} (${args}) <i>${mDoc.qualifiers}</i>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
genPropDoc(pDoc:any): string {
|
|
||||||
let setter = pDoc.setter;
|
|
||||||
if(setter) setter = `<li>Setter: ${setter}(value)</li>`; else setter = "";
|
|
||||||
let getter = pDoc.getter;
|
|
||||||
if(getter) getter = `<li>Getter: ${getter}()</li>`; else getter = "";
|
|
||||||
let descContent = pDoc.description;
|
|
||||||
if(!descContent) {
|
|
||||||
descContent = `There is currently no description for this property. Please help us by <span><a href="http://docs.godotengine.org/en/latest/community/contributing/updating_the_class_reference.html">contributing one</a></span>!`;
|
|
||||||
}
|
|
||||||
let doc = `
|
|
||||||
<li>
|
|
||||||
<h4>${genLink(pDoc.type,pDoc.type)} ${pDoc.name}</h4>
|
|
||||||
<ul>
|
|
||||||
${setter}
|
|
||||||
${getter}
|
|
||||||
</ul>
|
|
||||||
<p>${descContent}</p>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
genConstDoc(cDoc:any): string {
|
|
||||||
let doc = `
|
|
||||||
<li>
|
|
||||||
<h4>${cDoc.name} = ${cDoc.value}</h4>
|
|
||||||
<p>${cDoc.description}</p>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
genMemberDoc(classname, membername): string {
|
|
||||||
let realDoc = null;
|
|
||||||
const classdoc = config.getClass(classname);
|
|
||||||
if(!classdoc)
|
|
||||||
return null;
|
|
||||||
for(let m of classdoc.methods) {
|
|
||||||
if(m.name.toLowerCase() == membername) {
|
|
||||||
realDoc = this.genMethodDoc(m);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!realDoc) {
|
|
||||||
for(let s of classdoc.signals) {
|
|
||||||
if(s.name.toLowerCase() == membername) {
|
|
||||||
realDoc = this.genMethodDoc(s);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!realDoc) {
|
|
||||||
for(let c of classdoc.constants) {
|
|
||||||
if(c.name.toLowerCase() == membername) {
|
|
||||||
realDoc = this.genConstDoc(c);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!realDoc) {
|
|
||||||
for(let p of classdoc.properties) {
|
|
||||||
if(p.name.toLowerCase() == membername) {
|
|
||||||
realDoc = this.genPropDoc(p);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!realDoc) {
|
|
||||||
for(let p of classdoc.theme_properties) {
|
|
||||||
if(p.name.toLowerCase() == membername) {
|
|
||||||
realDoc = this.genPropDoc(p);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!realDoc)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let doc = `
|
|
||||||
${linkStyle}
|
|
||||||
<h2>Documentation of ${genLink(classname, classname)}.${membername}</h2>
|
|
||||||
<ul>${realDoc}</ul>
|
|
||||||
`;
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
genClassDoc(rawDoc): string {
|
|
||||||
if(!rawDoc)
|
|
||||||
return null;
|
|
||||||
const classname = rawDoc.name;
|
|
||||||
let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{
|
|
||||||
if (!inherits) return "";
|
|
||||||
return "<h4>Inherits: " + genLink(inherits, inherits, true) +"</h4>";
|
|
||||||
});
|
|
||||||
|
|
||||||
let category = getProp(rawDoc, "category", (c:string)=>{
|
|
||||||
return "<h4>Category: " + c +"</h4>";
|
|
||||||
});
|
|
||||||
|
|
||||||
let subclasses = "";
|
|
||||||
for(let key of config.getBuiltinClassNameList()) {
|
|
||||||
let c = config.getClass(key);
|
|
||||||
if(c && c.inherits == classname) {
|
|
||||||
subclasses += genLink(key, key, true) + " "
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if(subclasses && subclasses.length > 0)
|
|
||||||
subclasses = "<h3>Inherited by</h3> " + "<ul><li>" + subclasses + "</li></ul>";
|
|
||||||
|
|
||||||
let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{
|
|
||||||
return dec;
|
|
||||||
});
|
|
||||||
let descript = getProp(rawDoc, "description", (dec:string)=>{
|
|
||||||
if(dec)
|
|
||||||
return "<h3>Description</h3>" + "<ul><li>" + dec + "</li></ul>";
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
const setter_getters = {};
|
|
||||||
let propHeaders = ""
|
|
||||||
for(let p of rawDoc.properties) {
|
|
||||||
propHeaders += this.genPropHeader(p, classname);
|
|
||||||
if(p.setter)
|
|
||||||
setter_getters[p.setter] = true;
|
|
||||||
if(p.getter)
|
|
||||||
setter_getters[p.getter] = true;
|
|
||||||
}
|
|
||||||
if(propHeaders.length >0)
|
|
||||||
propHeaders = `<h3>Member List</h3><ul>${propHeaders}</ul/>`;
|
|
||||||
|
|
||||||
let methodHeaders = ""
|
|
||||||
let methods = "";
|
|
||||||
for(let m of rawDoc.methods) {
|
|
||||||
if(setter_getters[m.name]) continue;
|
|
||||||
methodHeaders += this.genMethodHeader(m, classname);
|
|
||||||
methods += this.genMethodDoc(m);
|
|
||||||
}
|
|
||||||
if(methodHeaders.length >0)
|
|
||||||
methodHeaders = `<h3>Public Methods</h3><ul>${methodHeaders}</ul/>`;
|
|
||||||
if(methods.length >0 )
|
|
||||||
methods = `<h3>Public Methods</h3><ul>${methods}</ul/>`;
|
|
||||||
|
|
||||||
let signals = "";
|
|
||||||
for(let s of rawDoc.signals) {
|
|
||||||
signals += this.genMethodDoc(s);
|
|
||||||
}
|
|
||||||
if(signals.length >0 )
|
|
||||||
signals = `<h3>Signals</h3><ul>${signals}</ul/>`;
|
|
||||||
|
|
||||||
let props = "";
|
|
||||||
for(let p of rawDoc.properties) {
|
|
||||||
props += this.genPropDoc(p)
|
|
||||||
}
|
|
||||||
for(let p of rawDoc.theme_properties) {
|
|
||||||
props += this.genPropDoc(p)
|
|
||||||
}
|
|
||||||
if(props.length >0 )
|
|
||||||
props = `<h3>Properties</h3><ul>${props}</ul>`
|
|
||||||
|
|
||||||
let constants = "";
|
|
||||||
for(let c of rawDoc.constants) {
|
|
||||||
constants += this.genConstDoc(c);
|
|
||||||
}
|
|
||||||
if(constants.length >0 )
|
|
||||||
constants = `<h3>Constants</h3><ul>${constants}</ul>`
|
|
||||||
|
|
||||||
let doc = `
|
|
||||||
${linkStyle}
|
|
||||||
<h1>Native Class ${classname}</h1>
|
|
||||||
<h4>${briefDescript}</h4>
|
|
||||||
<p>${category}</p>
|
|
||||||
<p>${inherits}</p>
|
|
||||||
<p>${subclasses}</p>
|
|
||||||
<p>${descript}</p>
|
|
||||||
<p>${propHeaders}</p>
|
|
||||||
<p>${methodHeaders}</p>
|
|
||||||
<p>${signals}</p>
|
|
||||||
<p>${constants}</p>
|
|
||||||
<p>${props}</p>
|
|
||||||
<p>${methods}</p>
|
|
||||||
`;
|
|
||||||
return this.format_documentation(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptDocumentContentProvider;
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
import {
|
|
||||||
HoverProvider,
|
|
||||||
TextDocument,
|
|
||||||
Position,
|
|
||||||
CancellationToken,
|
|
||||||
Hover,
|
|
||||||
MarkdownString,
|
|
||||||
workspace,
|
|
||||||
Uri,
|
|
||||||
CompletionItem,
|
|
||||||
CompletionItemKind
|
|
||||||
} from 'vscode';
|
|
||||||
import {
|
|
||||||
isStr,
|
|
||||||
getSelectedContent,
|
|
||||||
getStrContent
|
|
||||||
} from './utils';
|
|
||||||
import config from '../config';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
|
|
||||||
function genLink(title:string, uri:string):string {
|
|
||||||
const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
|
|
||||||
return `[${title}](${u})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GDScriptHoverProvider implements HoverProvider {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover | Thenable < Hover > {
|
|
||||||
let hoverText = getSelectedContent(document, position);
|
|
||||||
if (isStr(hoverText))
|
|
||||||
hoverText = getStrContent(hoverText);
|
|
||||||
const workspaceSymbols = config.getAllSymbols();
|
|
||||||
let tips: MarkdownString[] = [];
|
|
||||||
const withMarkdwon = workspace.getConfiguration("GodotTools").get("workspaceDocumentWithMarkdown", false);
|
|
||||||
|
|
||||||
|
|
||||||
const makeMarkdown = (content): MarkdownString => {
|
|
||||||
let md = new MarkdownString(content);
|
|
||||||
md.isTrusted = true;
|
|
||||||
return md;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check from workspace
|
|
||||||
const genWorkspaceTips = ()=> {
|
|
||||||
for (let filepath of Object.keys(workspaceSymbols)) {
|
|
||||||
const script = workspaceSymbols[filepath];
|
|
||||||
let scriptips: MarkdownString[] = [];
|
|
||||||
const getHoverText = (items, type, gdpath): MarkdownString[] => {
|
|
||||||
const _items: MarkdownString[] = [];
|
|
||||||
for (let name of Object.keys(items)) {
|
|
||||||
if (name == hoverText) {
|
|
||||||
let dfile = gdpath;
|
|
||||||
if (workspace) {
|
|
||||||
const root = config.normalizePath(workspace.rootPath) + "/";
|
|
||||||
if (gdpath.startsWith(root))
|
|
||||||
dfile = gdpath.replace(root, "");
|
|
||||||
}
|
|
||||||
let signature = "";
|
|
||||||
if(type == "func"|| type == "signal" && script.signatures[name])
|
|
||||||
signature = script.signatures[name];
|
|
||||||
if(type == "const" && script.constvalues[name])
|
|
||||||
signature = ` = ${script.constvalues[name]}`;
|
|
||||||
let doc ='```gdscript\n' + `${type} ${name}${signature}` + '\n```\n';
|
|
||||||
let rowDoc = script.documents[name];
|
|
||||||
if(!withMarkdwon)
|
|
||||||
rowDoc += "```plaintext\r\n"+rowDoc+"\r\n```";
|
|
||||||
doc += rowDoc;
|
|
||||||
doc = doc?doc+"\r\n\r\n":"";
|
|
||||||
if(gdpath != "autoload")
|
|
||||||
doc += `*Defined in [${dfile}](${Uri.file(gdpath).toString()})*`;
|
|
||||||
_items.push(makeMarkdown(doc));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _items;
|
|
||||||
}
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.variables, 'var', filepath)];
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.constants, 'const', filepath)];
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', filepath)];
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', filepath)];
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', filepath)];
|
|
||||||
if(script.enumerations)
|
|
||||||
scriptips = [...scriptips, ...getHoverText(script.enumerations, 'const', filepath)];
|
|
||||||
tips = [...tips, ...scriptips];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// check from scnes
|
|
||||||
const genNodePathTips = ()=> {
|
|
||||||
for (let scnenepath of Object.keys(config.nodeInfoMap)) {
|
|
||||||
const nodes: any[] = config.nodeInfoMap[scnenepath];
|
|
||||||
for (let index = 0; index < nodes.length; index++) {
|
|
||||||
const node:any = nodes[index];
|
|
||||||
const fullpath = node.parent + "/" + node.name;
|
|
||||||
if(fullpath == hoverText || fullpath.endsWith(hoverText)) {
|
|
||||||
let filepath = scnenepath;
|
|
||||||
if(workspace && workspace.rootPath)
|
|
||||||
filepath = path.join(workspace.rootPath, filepath);
|
|
||||||
let instance = "";
|
|
||||||
if(node.instance && node.instance.length > 1) {
|
|
||||||
let instancepath = node.instance;
|
|
||||||
if(workspace && workspace.rootPath)
|
|
||||||
instancepath = path.join(workspace.rootPath, instancepath);
|
|
||||||
instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
|
|
||||||
}
|
|
||||||
tips = [...tips,
|
|
||||||
makeMarkdown(`${genLink(node.type, node.type)} ${fullpath}`),
|
|
||||||
makeMarkdown(`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`)
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const format_documentation = (text, cls="") => {
|
|
||||||
let doc = text.replace(/\[code\]/g, "`").replace(/\[\/code\]/g, "`");
|
|
||||||
doc = doc.replace(/\[codeblock\]/g, "\n```gdscript\n").replace(/\[\/codeblock]/g, "\n```");
|
|
||||||
doc = doc.replace(/\[i\]/g, "*").replace(/\[\/i\]/g, "*");
|
|
||||||
doc = doc.replace(/\[b\]/g, "**").replace(/\[\/b\]/g, "**");
|
|
||||||
doc = doc.replace(/\[u\]/g, "__").replace(/\[\/u\]/g, "__");
|
|
||||||
doc = doc.replace(/\n\t\t\t\t/g, "\n");
|
|
||||||
return doc;
|
|
||||||
};
|
|
||||||
|
|
||||||
// check from builtin
|
|
||||||
const genBuiltinTips = ()=> {
|
|
||||||
const item2MarkdStrings = (name: string,item: CompletionItem, rowDoc: any):MarkdownString => {
|
|
||||||
let value = "";
|
|
||||||
let doc = format_documentation(item.documentation);
|
|
||||||
// get class name
|
|
||||||
let classname = name;
|
|
||||||
let matchs = name.match(/[@A-z][A-z0-9]*\./);
|
|
||||||
if(matchs) {
|
|
||||||
classname = matchs[0];
|
|
||||||
if(classname.endsWith("."))
|
|
||||||
classname = classname.substring(0, classname.length -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const genMethodMarkDown = ():string =>{
|
|
||||||
let content = `${genLink(rowDoc.return_type, rowDoc.return_type)} `;
|
|
||||||
if (rowDoc.name != classname) content += `${genLink(classname, classname)}.`;
|
|
||||||
let args = "";
|
|
||||||
for(let arg of rowDoc.arguments){
|
|
||||||
if(rowDoc.arguments.indexOf(arg)!=0)
|
|
||||||
args += ", ";
|
|
||||||
args += `${genLink(arg.type, arg.type)} ${arg.name}`
|
|
||||||
if(arg.default_value && arg.default_value.length > 0)
|
|
||||||
args += `=${arg.default_value}`;
|
|
||||||
}
|
|
||||||
content += `${genLink(rowDoc.name, classname+'.' + rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (item.kind) {
|
|
||||||
case CompletionItemKind.Class:
|
|
||||||
return makeMarkdown(`Native Class ${genLink(classname, classname)}\n${doc}`);
|
|
||||||
case CompletionItemKind.Method:
|
|
||||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
|
||||||
return makeMarkdown(`${genMethodMarkDown()}\n${doc}`);
|
|
||||||
case CompletionItemKind.Interface:
|
|
||||||
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
|
|
||||||
return makeMarkdown(`signal ${genMethodMarkDown()}\n${doc}`);
|
|
||||||
case CompletionItemKind.Variable:
|
|
||||||
case CompletionItemKind.Property:
|
|
||||||
return makeMarkdown(`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)}\n${doc}`);
|
|
||||||
case CompletionItemKind.Enum:
|
|
||||||
return makeMarkdown(`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname + "." + rowDoc.name)} = ${rowDoc.value}\n${doc}`);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return makeMarkdown(`${name} ${doc}`);
|
|
||||||
};
|
|
||||||
for (let name of Object.keys(config.builtinSymbolInfoMap)) {
|
|
||||||
const pattern = `[A-z@_]+[A-z0-9_]*\\.${hoverText}\\b`;
|
|
||||||
if(name == hoverText || name.match(new RegExp(pattern))) {
|
|
||||||
const item: {completionItem: CompletionItem, rowDoc: any} = config.builtinSymbolInfoMap[name];
|
|
||||||
tips = [...tips, item2MarkdStrings(name, item.completionItem, item.rowDoc)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
genBuiltinTips();
|
|
||||||
genWorkspaceTips();
|
|
||||||
genNodePathTips();
|
|
||||||
|
|
||||||
if (tips.length > 0)
|
|
||||||
return new Hover(tips);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptHoverProvider;
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import {
|
|
||||||
SignatureHelpProvider,
|
|
||||||
TextDocument,
|
|
||||||
Position,
|
|
||||||
CancellationToken,
|
|
||||||
SignatureInformation,
|
|
||||||
SignatureHelp,
|
|
||||||
CompletionItemKind,
|
|
||||||
ParameterInformation,
|
|
||||||
workspace
|
|
||||||
} from 'vscode';
|
|
||||||
import config from '../config';
|
|
||||||
import { countSubStr } from './utils';
|
|
||||||
class GDScriptSignatureHelpProvider implements SignatureHelpProvider {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
provideSignatureHelp(document : TextDocument, position : Position, token : CancellationToken) : SignatureHelp | Thenable < SignatureHelp > {
|
|
||||||
const self = this;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const res = self.do_provideSignatureHelp(document, position);
|
|
||||||
resolve(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Provide help for the signature at the given position and document.
|
|
||||||
*
|
|
||||||
* @param document The document in which the command was invoked.
|
|
||||||
* @param position The position at which the command was invoked.
|
|
||||||
* @param token A cancellation token.
|
|
||||||
* @return Signature help or a thenable that resolves to such. The lack of a result can be
|
|
||||||
* signaled by returning `undefined` or `null`.
|
|
||||||
*/
|
|
||||||
do_provideSignatureHelp(document : TextDocument, position : Position) : SignatureHelp | Thenable < SignatureHelp > {
|
|
||||||
const range = document.getWordRangeAtPosition(position);
|
|
||||||
let funcname = "";
|
|
||||||
let curparam = 0;
|
|
||||||
const checkPosition = () => {
|
|
||||||
const line = document.lineAt(position);
|
|
||||||
const startPos = line.firstNonWhitespaceCharacterIndex;
|
|
||||||
const endPos = position.character;
|
|
||||||
const queryStr = line.text.substring(startPos, endPos);
|
|
||||||
|
|
||||||
var reg = /([A-z_]+[A-z0-9_]*)\(/g;
|
|
||||||
let match = reg.exec(queryStr);
|
|
||||||
while (match != null) {
|
|
||||||
funcname = match[1];
|
|
||||||
match = reg.exec(queryStr);
|
|
||||||
}
|
|
||||||
if(funcname != "") {
|
|
||||||
const funcrangestr = line.text.substring(line.text.indexOf(queryStr)+queryStr.indexOf(funcname)+funcname.length, endPos);
|
|
||||||
curparam = countSubStr(funcrangestr, ",");
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
checkPosition();
|
|
||||||
|
|
||||||
let resultSignatures: SignatureInformation[] = [];
|
|
||||||
|
|
||||||
if (funcname.length > 0) {
|
|
||||||
// Builtin functions
|
|
||||||
for (let key of Object.keys(config.builtinSymbolInfoMap)) {
|
|
||||||
if (key.endsWith(`\.${funcname}`)) {
|
|
||||||
if (config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Method || config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Function) {
|
|
||||||
const rawDoc = config.builtinSymbolInfoMap[key].rowDoc;
|
|
||||||
const item = config.builtinSymbolInfoMap[key].completionItem;
|
|
||||||
let signatureInfor: SignatureInformation = new SignatureInformation(item.documentation.split('\n')[0], rawDoc.description);
|
|
||||||
for(let arg of rawDoc.arguments){
|
|
||||||
let param: ParameterInformation = new ParameterInformation(`${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}`, "");
|
|
||||||
signatureInfor.parameters.push(param);
|
|
||||||
}
|
|
||||||
resultSignatures.push(signatureInfor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// workspace functions
|
|
||||||
for (let path of Object.keys(config.getAllSymbols())) {
|
|
||||||
let script = config.getSymbols(path);
|
|
||||||
if(!script.signatures)
|
|
||||||
continue
|
|
||||||
let relaPath = path;
|
|
||||||
if(workspace && workspace.rootPath)
|
|
||||||
relaPath = workspace.asRelativePath(relaPath);
|
|
||||||
|
|
||||||
for(let f of Object.keys(script.signatures)) {
|
|
||||||
if(f == funcname) {
|
|
||||||
const signatureStr = script.signatures[f];
|
|
||||||
let signature: SignatureInformation = new SignatureInformation(`func ${f}${signatureStr}`, `Method defined in ${relaPath}`);
|
|
||||||
const params = (signatureStr.substring(signatureStr.indexOf("(")+1, signatureStr.indexOf(")"))).split(",");
|
|
||||||
for(let p of params)
|
|
||||||
signature.parameters.push(new ParameterInformation(p, ""));
|
|
||||||
resultSignatures.push(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(resultSignatures.length > 0) {
|
|
||||||
return ({
|
|
||||||
signatures: resultSignatures,
|
|
||||||
activeSignature: 0,
|
|
||||||
activeParameter: curparam
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptSignatureHelpProvider;
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
import {Range} from 'vscode';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
interface GDScript {
|
|
||||||
constants: {},
|
|
||||||
functions: {},
|
|
||||||
variables: {},
|
|
||||||
signals: {},
|
|
||||||
classes: {},
|
|
||||||
base: string,
|
|
||||||
native: string,
|
|
||||||
signatures: {},
|
|
||||||
// symbol: marked string
|
|
||||||
documents: {},
|
|
||||||
// name : value
|
|
||||||
constvalues: {},
|
|
||||||
enumerations: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GDScriptSymbolParser {
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
parseContent(content: string, ignoreIndentedVars:boolean = false): GDScript {
|
|
||||||
const script: GDScript = {
|
|
||||||
constants: {},
|
|
||||||
functions: {},
|
|
||||||
variables: {},
|
|
||||||
signals: {},
|
|
||||||
classes: {},
|
|
||||||
base: "",
|
|
||||||
native: "",
|
|
||||||
signatures: {},
|
|
||||||
documents: {},
|
|
||||||
constvalues: {},
|
|
||||||
enumerations: {}
|
|
||||||
}
|
|
||||||
const text = content;
|
|
||||||
const lines = text.split(/\r?\n/);
|
|
||||||
|
|
||||||
// Base class and native class
|
|
||||||
for (let line of lines) {
|
|
||||||
let match;
|
|
||||||
if (match = line.match(/extends\s+(\w+)/)) {
|
|
||||||
script.native = match[1];
|
|
||||||
break;
|
|
||||||
} else if (match = line.match(/extends\s+('|")(.*)('|")/)) {
|
|
||||||
script.base = match[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMatches = (regex:RegExp, index=1) => {
|
|
||||||
var matches = [];
|
|
||||||
for(let line of lines) {
|
|
||||||
let match;
|
|
||||||
if (match = regex.exec(line)) {
|
|
||||||
let commentReg = RegExp(/#.*?/.source+regex.source);
|
|
||||||
if(!commentReg.exec(line))
|
|
||||||
matches.push(match[index]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
// var matches = [];
|
|
||||||
// var match;
|
|
||||||
// while (match = regex.exec(string)) {
|
|
||||||
// matches.push(match[index]);
|
|
||||||
// }
|
|
||||||
// return matches;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findLineRanges = (symbols, reg)=>{
|
|
||||||
const sm = {};
|
|
||||||
symbols.map((name:string)=>{
|
|
||||||
let line = 0;
|
|
||||||
let curline = 0;
|
|
||||||
if(Object.keys(sm).indexOf(name) != -1) return;
|
|
||||||
lines.map(l=>{
|
|
||||||
const nreg = reg.replace("$X$", name);
|
|
||||||
if(l.match(nreg) != null) {
|
|
||||||
line = curline;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
curline += 1;
|
|
||||||
});
|
|
||||||
sm[name] = line;
|
|
||||||
});
|
|
||||||
return sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
const determRange = (key:string, array: any): Range =>{
|
|
||||||
let line = array[key];
|
|
||||||
let startAt = lines[line].indexOf(key);
|
|
||||||
if(line < 0) line = 0;
|
|
||||||
if(startAt < 0) startAt = 0;
|
|
||||||
return new Range(line, startAt, line, startAt + key.length);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseSignature = (range: Range):string => {
|
|
||||||
let res = "";
|
|
||||||
const line = lines[range.start.line];
|
|
||||||
if(line.indexOf("(")!= -1 && line.indexOf(")")!=-1) {
|
|
||||||
const signature = line.substring(line.indexOf("("), line.indexOf(")")+1);
|
|
||||||
if(signature && signature.length >0)
|
|
||||||
res = signature;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseDocument = (range: Range):string => {
|
|
||||||
let mdoc = ""
|
|
||||||
let line = range.start.line;
|
|
||||||
while( line > 0){
|
|
||||||
const linecontent = lines[line];
|
|
||||||
let match = linecontent.match(/\s*#\s*(.*)/);
|
|
||||||
let commentAtEnd = linecontent.match(/[\w'",\[\{\]\}\(\)]+\s*#\s*(.*)/) != null;
|
|
||||||
if(commentAtEnd && linecontent.match(/^#/))
|
|
||||||
commentAtEnd = false;
|
|
||||||
if(!match && line != range.start.line)
|
|
||||||
break;
|
|
||||||
if(commentAtEnd && line != range.start.line)
|
|
||||||
break;
|
|
||||||
if(match) {
|
|
||||||
let lmcontent = linecontent.substring(linecontent.indexOf("#")+1, linecontent.length);
|
|
||||||
if(lmcontent.startsWith(" ") && lmcontent != " ")
|
|
||||||
lmcontent = lmcontent.substring(1, lmcontent.length);
|
|
||||||
mdoc = lmcontent + "\r\n" + mdoc;
|
|
||||||
}
|
|
||||||
else if(line != range.start.line)
|
|
||||||
break
|
|
||||||
--line;
|
|
||||||
}
|
|
||||||
return mdoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
let funcsnames = getMatches(/func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
|
||||||
const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(");
|
|
||||||
for (let key of Object.keys(funcs)) {
|
|
||||||
let r: Range = determRange(key, funcs);
|
|
||||||
script.functions[key] = r;
|
|
||||||
script.signatures[key] = parseSignature(r);
|
|
||||||
script.documents[key] = parseDocument(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
let signalnames = getMatches(/signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/, 1);
|
|
||||||
const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(");
|
|
||||||
for (let key of Object.keys(signals)) {
|
|
||||||
let r: Range = determRange(key, signals);
|
|
||||||
script.signals[key] = r;
|
|
||||||
script.signatures[key] = parseSignature(r);
|
|
||||||
script.documents[key] = parseDocument(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
let varreg = /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/;
|
|
||||||
let varreg2 = "var\\s+$X$([^\\w]|$)";
|
|
||||||
let vargroup = 1;
|
|
||||||
if(ignoreIndentedVars) {
|
|
||||||
varreg = /^((export.*?var)|var)\s+([_A-Za-z]+[_A-Za-z0-9]*)\s?/;
|
|
||||||
varreg2 = "^((export.*?var)|var)\\s+$X$\\s?";
|
|
||||||
vargroup = 3;
|
|
||||||
}
|
|
||||||
let varnames = getMatches(varreg, vargroup);
|
|
||||||
const vars = findLineRanges(varnames, varreg2);
|
|
||||||
for (let key of Object.keys(vars)){
|
|
||||||
const r:Range = determRange(key, vars)
|
|
||||||
script.variables[key] = r;
|
|
||||||
let newdoc = parseDocument(r);
|
|
||||||
if(newdoc == "" && script.documents[key])
|
|
||||||
newdoc = script.documents[key];
|
|
||||||
script.documents[key] = newdoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
let constnames = getMatches(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/, 1);
|
|
||||||
const consts = findLineRanges(constnames, "const\\s+$X$\\s*");
|
|
||||||
for (let key of Object.keys(consts)){
|
|
||||||
const r:Range = determRange(key, consts)
|
|
||||||
script.constants[key] = r;
|
|
||||||
let newdoc = parseDocument(r);
|
|
||||||
if(newdoc == "" && script.documents[key])
|
|
||||||
newdoc = script.documents[key];
|
|
||||||
script.documents[key] = newdoc;
|
|
||||||
|
|
||||||
const linecontent = lines[r.start.line];
|
|
||||||
const match = linecontent.match(/const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*=\s*([\w+]+\(.*\)|"[^"]*"|\-?\d+\.?\d*|\[.*\]|\{.*\})/);
|
|
||||||
if(match && match.length && match.length >1)
|
|
||||||
script.constvalues[key] = match[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
let classnames = getMatches(/class\s+([_A-Za-z]+[_A-Za-z0-9]*)(\s|\:)/, 1);
|
|
||||||
const classes = findLineRanges(classnames, "class\\s+$X$(\\s|\\:)");
|
|
||||||
for (let key of Object.keys(classes)) {
|
|
||||||
const r:Range = determRange(key, classes)
|
|
||||||
script.classes[key] = r;
|
|
||||||
script.documents[key] = parseDocument(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
let enumnames = getMatches(/enum\s+([_A-Za-z]+[_A-Za-z0-9]*)\s+\{/, 1);
|
|
||||||
const enums = findLineRanges(enumnames, "enum\\s+$X$\\s+\{");
|
|
||||||
for (let key of Object.keys(enums)) {
|
|
||||||
const r:Range = determRange(key, enums)
|
|
||||||
script.constants[key] = r;
|
|
||||||
script.documents[key] = parseDocument(r);
|
|
||||||
|
|
||||||
let curindex = r.start.line
|
|
||||||
while (curindex < lines.length) {
|
|
||||||
const line = lines[curindex];
|
|
||||||
let matchs = line.match(/([_A-Za-z]+[_A-Za-z0-9]*)/g);
|
|
||||||
if(matchs && matchs.length >= 1 ){
|
|
||||||
for (var i = 0; i < matchs.length; i++)
|
|
||||||
if(line.indexOf(matchs[i]) > line.indexOf("{"))
|
|
||||||
script.enumerations[matchs[i]] = new Range(curindex, 0, curindex, line.length);
|
|
||||||
}
|
|
||||||
if(line.indexOf("}") == -1)
|
|
||||||
curindex += 1;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: enumerations without name
|
|
||||||
// const unnamedEnums = text.match(/enum\s+\{.*\}/gm)
|
|
||||||
|
|
||||||
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
parseFile(path:string, ignoreIndentedVars:boolean = false): GDScript {
|
|
||||||
const self = this;
|
|
||||||
if(fs.existsSync(path) && fs.statSync(path).isFile()){
|
|
||||||
const content = fs.readFileSync(path, 'utf-8');
|
|
||||||
return this.parseContent(content, ignoreIndentedVars);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptSymbolParser;
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import {
|
|
||||||
DocumentSymbolProvider,
|
|
||||||
TextDocument,
|
|
||||||
SymbolInformation,
|
|
||||||
CancellationToken,
|
|
||||||
SymbolKind,
|
|
||||||
Range,
|
|
||||||
workspace
|
|
||||||
} from 'vscode';
|
|
||||||
|
|
||||||
import GDScriptSymbolParser from '../gdscript/symbolparser';
|
|
||||||
import config from '../config';
|
|
||||||
|
|
||||||
class GDScriptSymbolProvider implements DocumentSymbolProvider {
|
|
||||||
private parser: GDScriptSymbolParser = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.parser = new GDScriptSymbolParser();
|
|
||||||
}
|
|
||||||
|
|
||||||
provideDocumentSymbols(document: TextDocument, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]> {
|
|
||||||
|
|
||||||
const symbols: SymbolInformation[] = [];
|
|
||||||
var ignoreIndentedVars = false;
|
|
||||||
if(workspace)
|
|
||||||
ignoreIndentedVars = workspace.getConfiguration("GodotTools").get("ignoreIndentedVars", false);
|
|
||||||
const script = this.parser.parseContent(document.getText(), ignoreIndentedVars);
|
|
||||||
const signatures = script.signatures;
|
|
||||||
config.setSymbols(document.fileName, script);
|
|
||||||
|
|
||||||
const funcs = script.functions;
|
|
||||||
for (let key of Object.keys(funcs))
|
|
||||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Function, funcs[key]));
|
|
||||||
|
|
||||||
const signals = script.signals;
|
|
||||||
for (let key of Object.keys(signals))
|
|
||||||
symbols.push(new SymbolInformation(key+signatures[key], SymbolKind.Interface, signals[key]));
|
|
||||||
|
|
||||||
const vars = script.variables;
|
|
||||||
for (let key of Object.keys(vars))
|
|
||||||
symbols.push(new SymbolInformation(key, SymbolKind.Variable, vars[key]));
|
|
||||||
|
|
||||||
const consts = script.constants;
|
|
||||||
for (let key of Object.keys(consts))
|
|
||||||
symbols.push(new SymbolInformation(key, SymbolKind.Constant, consts[key]));
|
|
||||||
|
|
||||||
const classes = script.classes;
|
|
||||||
for (let key of Object.keys(classes))
|
|
||||||
symbols.push(new SymbolInformation(key, SymbolKind.Class, classes[key]));
|
|
||||||
|
|
||||||
if(script.enumerations) {
|
|
||||||
const enumerations = script.enumerations;
|
|
||||||
for (let key of Object.keys(enumerations))
|
|
||||||
symbols.push(new SymbolInformation(key, SymbolKind.Enum, enumerations[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptSymbolProvider;
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import {TextDocument, Position} from 'vscode';
|
|
||||||
|
|
||||||
export function isStr(content:string) {
|
|
||||||
return (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSelectedContent(document: TextDocument, position: Position):string {
|
|
||||||
const line = document.lineAt(position);
|
|
||||||
const wordRange = document.getWordRangeAtPosition(position) ;
|
|
||||||
const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*?"|'.*?'|@".*?"/g)
|
|
||||||
let res = line.text.substring(wordRange.start.character, wordRange.end.character);
|
|
||||||
machs.map(m=>{
|
|
||||||
if(m) {
|
|
||||||
const startPos = line.text.indexOf(m);
|
|
||||||
const endPos = startPos + m.length;
|
|
||||||
if(isStr(m) && startPos != -1 && wordRange.start.character >= startPos && wordRange.end.character <= endPos){
|
|
||||||
res = m;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getStrContent(rawstr: string):string {
|
|
||||||
let ss = rawstr;
|
|
||||||
if(isStr(ss)) {
|
|
||||||
ss = ss.replace(/"|'|@"|"""/g,"")
|
|
||||||
}
|
|
||||||
return ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function countSubStr(str:string, sub:string): number {
|
|
||||||
let count = 0;
|
|
||||||
let pos = str.indexOf(sub);
|
|
||||||
while (pos !== -1) {
|
|
||||||
count++;
|
|
||||||
pos = str.indexOf(sub, pos + sub.length);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as vscode from 'vscode';
|
|
||||||
import config from '../config';
|
|
||||||
|
|
||||||
class GDScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
|
|
||||||
public provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): vscode.SymbolInformation[] {
|
|
||||||
const scripts = config.getAllSymbols();
|
|
||||||
const symbols: vscode.SymbolInformation[] = [];
|
|
||||||
for (let path of Object.keys(scripts)) {
|
|
||||||
const queryMembers = (query, members, kind: vscode.SymbolKind, path:string, extra=(text)=>text)=> {
|
|
||||||
for (let name of Object.keys(members)) {
|
|
||||||
const range: vscode.Range = members[name];
|
|
||||||
if(name.toLowerCase().indexOf(query.toLowerCase()) != -1) {
|
|
||||||
const symbol: vscode.SymbolInformation = {
|
|
||||||
name: extra(name),
|
|
||||||
kind,
|
|
||||||
containerName: "",
|
|
||||||
location: {
|
|
||||||
uri: vscode.Uri.file(path),
|
|
||||||
range
|
|
||||||
}
|
|
||||||
};
|
|
||||||
symbols.push(symbol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const scrip = scripts[path];
|
|
||||||
const signatures = scrip.signatures;
|
|
||||||
queryMembers(query, scrip.functions, vscode.SymbolKind.Function, path, (name)=>(name+signatures[name]));
|
|
||||||
queryMembers(query, scrip.signals, vscode.SymbolKind.Interface, path, (name)=>(name+signatures[name]));
|
|
||||||
queryMembers(query, scrip.variables, vscode.SymbolKind.Variable, path);
|
|
||||||
queryMembers(query, scrip.constants, vscode.SymbolKind.Constant, path);
|
|
||||||
queryMembers(query, scrip.classes, vscode.SymbolKind.Class, path);
|
|
||||||
if(scrip.enumerations)
|
|
||||||
queryMembers(query, scrip.enumerations, vscode.SymbolKind.Enum, path);
|
|
||||||
}
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GDScriptWorkspaceSymbolProvider;
|
|
||||||
164
src/godot-tools.ts
Normal file
164
src/godot-tools.ts
Normal 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
47
src/loggger.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export class Logger {
|
||||||
|
protected buffer: string = "";
|
||||||
|
protected tag: string = '';
|
||||||
|
protected time: boolean = false;
|
||||||
|
|
||||||
|
constructor(tag: string, time: boolean) {
|
||||||
|
this.tag = tag;
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.buffer = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
log(...messages) {
|
||||||
|
|
||||||
|
let line = '';
|
||||||
|
if (this.tag) {
|
||||||
|
line += `[${this.tag}]`;
|
||||||
|
}
|
||||||
|
if (this.time) {
|
||||||
|
line += `[${new Date().toISOString()}]`;
|
||||||
|
}
|
||||||
|
if (line) {
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < messages.length; index++) {
|
||||||
|
line += messages[index];
|
||||||
|
if (index < messages.length) {
|
||||||
|
line += " ";
|
||||||
|
} else {
|
||||||
|
line += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer += line;
|
||||||
|
console.log(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
get_buffer(): string {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = new Logger('godot-tools', true);
|
||||||
|
export default logger;
|
||||||
141
src/lsp/GDScriptLanguageClient.ts
Normal file
141
src/lsp/GDScriptLanguageClient.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { LanguageClient, LanguageClientOptions, ServerOptions, RequestMessage } from "vscode-languageclient";
|
||||||
|
import { is_debug_mode, get_configuration } from "../utils";
|
||||||
|
import { MessageIO, MessageIOReader, MessageIOWriter, Message } from "./MessageIO";
|
||||||
|
import logger from "../loggger";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import NativeDocumentManager from './NativeDocumentManager';
|
||||||
|
|
||||||
|
function getClientOptions(): LanguageClientOptions {
|
||||||
|
return {
|
||||||
|
// Register the server for plain text documents
|
||||||
|
documentSelector: [
|
||||||
|
{ scheme: "file", language: "gdscript" },
|
||||||
|
{ scheme: "untitled", language: "gdscript" },
|
||||||
|
],
|
||||||
|
synchronize: {
|
||||||
|
// Notify the server about file changes to '.gd files contain in the workspace
|
||||||
|
// fileEvents: workspace.createFileSystemWatcher("**/*.gd"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_server_uri() : string {
|
||||||
|
let port = get_configuration("gdscript_lsp_server_port", 6008);
|
||||||
|
return `ws://localhost:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const io = new MessageIO(get_server_uri());
|
||||||
|
const serverOptions: ServerOptions = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
resolve({reader: new MessageIOReader(io), writer: new MessageIOWriter(io)});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ClientStatus {
|
||||||
|
PENDING,
|
||||||
|
DISCONNECTED,
|
||||||
|
CONNECTED,
|
||||||
|
}
|
||||||
|
const CUSTOM_MESSAGE = "gdscrip_client/";
|
||||||
|
|
||||||
|
export default class GDScriptLanguageClient extends LanguageClient {
|
||||||
|
|
||||||
|
public io: MessageIO = io;
|
||||||
|
|
||||||
|
private context: vscode.ExtensionContext;
|
||||||
|
private _started : boolean = false;
|
||||||
|
private _status : ClientStatus;
|
||||||
|
private _status_changed_callbacks: ((v : ClientStatus)=>void)[] = [];
|
||||||
|
private _initialize_request: Message = null;
|
||||||
|
private message_handler: MessageHandler = null;
|
||||||
|
private native_doc_manager: NativeDocumentManager = null;
|
||||||
|
|
||||||
|
public get started() : boolean { return this._started; }
|
||||||
|
public get status() : ClientStatus { return this._status; }
|
||||||
|
public set status(v : ClientStatus) {
|
||||||
|
if (this._status != v) {
|
||||||
|
this._status = v;
|
||||||
|
for (const callback of this._status_changed_callbacks) {
|
||||||
|
callback(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public watch_status(callback: (v : ClientStatus)=>void) {
|
||||||
|
if (this._status_changed_callbacks.indexOf(callback) == -1) {
|
||||||
|
this._status_changed_callbacks.push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: vscode.ExtensionContext) {
|
||||||
|
super(`GDScriptLanguageClient`, serverOptions, getClientOptions());
|
||||||
|
this.context = context;
|
||||||
|
this.status = ClientStatus.PENDING;
|
||||||
|
this.message_handler = new MessageHandler();
|
||||||
|
this.io.on('disconnected', this.on_disconnected.bind(this));
|
||||||
|
this.io.on('connected', this.on_connected.bind(this));
|
||||||
|
this.io.on('message', this.on_message.bind(this));
|
||||||
|
this.io.on('send_message', this.on_send_message.bind(this));
|
||||||
|
this.native_doc_manager = new NativeDocumentManager(this.io);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_to_server() {
|
||||||
|
this.status = ClientStatus.PENDING;
|
||||||
|
io.connect_to_language_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): vscode.Disposable {
|
||||||
|
this._started = true;
|
||||||
|
return super.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_send_message(message: Message) {
|
||||||
|
if (is_debug_mode()) logger.log("[client]", JSON.stringify(message));
|
||||||
|
if ((message as RequestMessage).method == "initialize") {
|
||||||
|
this._initialize_request = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_message(message: Message) {
|
||||||
|
if (is_debug_mode()) logger.log("[server]", JSON.stringify(message));
|
||||||
|
this.message_handler.on_message(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_connected() {
|
||||||
|
if (this._initialize_request) {
|
||||||
|
this.io.writer.write(this._initialize_request);
|
||||||
|
}
|
||||||
|
this.status = ClientStatus.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_disconnected() {
|
||||||
|
this.status = ClientStatus.DISCONNECTED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MessageHandler extends EventEmitter {
|
||||||
|
|
||||||
|
changeWorkspace(params: {path: string}) {
|
||||||
|
vscode.window.showErrorMessage("The GDScript language server can't work properly!\nThe 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
87
src/lsp/MessageBuffer.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* --------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
const DefaultSize: number = 8192;
|
||||||
|
const CR: number = Buffer.from('\r', 'ascii')[0];
|
||||||
|
const LF: number = Buffer.from('\n', 'ascii')[0];
|
||||||
|
const CRLF: string = '\r\n';
|
||||||
|
|
||||||
|
export default class MessageBuffer {
|
||||||
|
|
||||||
|
private encoding: string;
|
||||||
|
private index: number;
|
||||||
|
private buffer: Buffer;
|
||||||
|
|
||||||
|
constructor(encoding: string = 'utf8') {
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.index = 0;
|
||||||
|
this.buffer = Buffer.allocUnsafe(DefaultSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public append(chunk: Buffer | String): void {
|
||||||
|
var toAppend: Buffer = <Buffer>chunk;
|
||||||
|
if (typeof (chunk) === 'string') {
|
||||||
|
var str = <string>chunk;
|
||||||
|
var bufferLen = Buffer.byteLength(str, this.encoding);
|
||||||
|
toAppend = Buffer.allocUnsafe(bufferLen);
|
||||||
|
toAppend.write(str, 0, bufferLen, this.encoding);
|
||||||
|
}
|
||||||
|
if (this.buffer.length - this.index >= toAppend.length) {
|
||||||
|
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
|
||||||
|
} else {
|
||||||
|
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
|
||||||
|
if (this.index === 0) {
|
||||||
|
this.buffer = Buffer.allocUnsafe(newSize);
|
||||||
|
toAppend.copy(this.buffer, 0, 0, toAppend.length);
|
||||||
|
} else {
|
||||||
|
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.index += toAppend.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryReadHeaders(): { [key: string]: string; } | undefined {
|
||||||
|
let result: { [key: string]: string; } | undefined = undefined;
|
||||||
|
let current = 0;
|
||||||
|
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
// No header / body separator found (e.g CRLFCRLF)
|
||||||
|
if (current + 3 >= this.index) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result = Object.create(null);
|
||||||
|
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
|
||||||
|
headers.forEach((header) => {
|
||||||
|
let index: number = header.indexOf(':');
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error('Message header must separate key and value using :');
|
||||||
|
}
|
||||||
|
let key = header.substr(0, index);
|
||||||
|
let value = header.substr(index + 1).trim();
|
||||||
|
result![key] = value;
|
||||||
|
})
|
||||||
|
|
||||||
|
let nextStart = current + 4;
|
||||||
|
this.buffer = this.buffer.slice(nextStart);
|
||||||
|
this.index = this.index - nextStart;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryReadContent(length: number): string | null {
|
||||||
|
if (this.index < length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let result = this.buffer.toString(this.encoding, 0, length);
|
||||||
|
let nextStart = length;
|
||||||
|
this.buffer.copy(this.buffer, 0, nextStart);
|
||||||
|
this.index = this.index - nextStart;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get numberOfBytes(): number {
|
||||||
|
return this.index;
|
||||||
|
}
|
||||||
|
};
|
||||||
200
src/lsp/MessageIO.ts
Normal file
200
src/lsp/MessageIO.ts
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { AbstractMessageReader, MessageReader, DataCallback } from "vscode-jsonrpc/lib/messageReader";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import * as WebSocket from 'ws';
|
||||||
|
import MessageBuffer from "./MessageBuffer";
|
||||||
|
import { AbstractMessageWriter, MessageWriter } from "vscode-jsonrpc/lib/messageWriter";
|
||||||
|
import { RequestMessage, ResponseMessage, NotificationMessage } from "vscode-jsonrpc/lib/messages";
|
||||||
|
export type Message = RequestMessage | ResponseMessage | NotificationMessage;
|
||||||
|
|
||||||
|
export class MessageIO extends EventEmitter {
|
||||||
|
|
||||||
|
reader: MessageIOReader = null;
|
||||||
|
writer: MessageIOWriter = null;
|
||||||
|
|
||||||
|
private socket: WebSocket = null;
|
||||||
|
private url: string = "";
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
super();
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public send_message(message: string) {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected on_message(chunk: WebSocket.Data) {
|
||||||
|
let message = chunk.toString();
|
||||||
|
this.emit('data', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_send_message(message: any) {
|
||||||
|
this.emit("send_message", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_message_callback(message: any) {
|
||||||
|
this.emit("message", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_to_language_server():Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.socket = null;
|
||||||
|
const ws = new WebSocket(this.url);
|
||||||
|
ws.on('open', ()=>{ this.on_connected(ws); resolve(); });
|
||||||
|
ws.on('message', this.on_message.bind(this));
|
||||||
|
ws.on('error', this.on_disconnected.bind(this));
|
||||||
|
ws.on('close', this.on_disconnected.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_connected(socket: WebSocket) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.emit("connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_disconnected() {
|
||||||
|
this.socket = null;
|
||||||
|
this.emit('disconnected');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export class MessageIOReader extends AbstractMessageReader implements MessageReader {
|
||||||
|
|
||||||
|
private io: MessageIO;
|
||||||
|
private callback: DataCallback;
|
||||||
|
private buffer: MessageBuffer;
|
||||||
|
private nextMessageLength: number;
|
||||||
|
private messageToken: number;
|
||||||
|
private partialMessageTimer: NodeJS.Timer | undefined;
|
||||||
|
private _partialMessageTimeout: number;
|
||||||
|
|
||||||
|
public constructor(io: MessageIO, encoding: string = 'utf8') {
|
||||||
|
super();
|
||||||
|
this.io = io;
|
||||||
|
this.io.reader = this;
|
||||||
|
this.buffer = new MessageBuffer(encoding);
|
||||||
|
this._partialMessageTimeout = 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set partialMessageTimeout(timeout: number) {
|
||||||
|
this._partialMessageTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get partialMessageTimeout(): number {
|
||||||
|
return this._partialMessageTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public listen(callback: DataCallback): void {
|
||||||
|
this.nextMessageLength = -1;
|
||||||
|
this.messageToken = 0;
|
||||||
|
this.partialMessageTimer = undefined;
|
||||||
|
this.callback = callback;
|
||||||
|
this.io.on('data', (data: Buffer) => {
|
||||||
|
this.onData(data);
|
||||||
|
});
|
||||||
|
this.io.on('error', (error: any) => this.fireError(error));
|
||||||
|
this.io.on('close', () => this.fireClose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onData(data: Buffer | String): void {
|
||||||
|
this.buffer.append(data);
|
||||||
|
while (true) {
|
||||||
|
if (this.nextMessageLength === -1) {
|
||||||
|
let headers = this.buffer.tryReadHeaders();
|
||||||
|
if (!headers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let contentLength = headers['Content-Length'];
|
||||||
|
if (!contentLength) {
|
||||||
|
throw new Error('Header must provide a Content-Length property.');
|
||||||
|
}
|
||||||
|
let length = parseInt(contentLength);
|
||||||
|
if (isNaN(length)) {
|
||||||
|
throw new Error('Content-Length value must be a number.');
|
||||||
|
}
|
||||||
|
this.nextMessageLength = length;
|
||||||
|
// Take the encoding form the header. For compatibility
|
||||||
|
// treat both utf-8 and utf8 as node utf8
|
||||||
|
}
|
||||||
|
var msg = this.buffer.tryReadContent(this.nextMessageLength);
|
||||||
|
if (msg === null) {
|
||||||
|
/** We haven't received the full message yet. */
|
||||||
|
this.setPartialMessageTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clearPartialMessageTimer();
|
||||||
|
this.nextMessageLength = -1;
|
||||||
|
this.messageToken++;
|
||||||
|
var json = JSON.parse(msg);
|
||||||
|
this.callback(json);
|
||||||
|
// callback
|
||||||
|
this.io.on_message_callback(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearPartialMessageTimer(): void {
|
||||||
|
if (this.partialMessageTimer) {
|
||||||
|
clearTimeout(this.partialMessageTimer);
|
||||||
|
this.partialMessageTimer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setPartialMessageTimer(): void {
|
||||||
|
this.clearPartialMessageTimer();
|
||||||
|
if (this._partialMessageTimeout <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.partialMessageTimer = setTimeout((token, timeout) => {
|
||||||
|
this.partialMessageTimer = undefined;
|
||||||
|
if (token === this.messageToken) {
|
||||||
|
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
|
||||||
|
this.setPartialMessageTimer();
|
||||||
|
}
|
||||||
|
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContentLength: string = 'Content-Length: ';
|
||||||
|
const CRLF = '\r\n';
|
||||||
|
export class MessageIOWriter extends AbstractMessageWriter implements MessageWriter {
|
||||||
|
|
||||||
|
private io: MessageIO;
|
||||||
|
private encoding: string;
|
||||||
|
private errorCount: number;
|
||||||
|
|
||||||
|
public constructor(io: MessageIO, encoding: string = 'utf8') {
|
||||||
|
super();
|
||||||
|
this.io = io;
|
||||||
|
this.io.writer = this;
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.errorCount = 0;
|
||||||
|
this.io.on('error', (error: any) => this.fireError(error));
|
||||||
|
this.io.on('close', () => this.fireClose());
|
||||||
|
}
|
||||||
|
|
||||||
|
public write(msg: Message): void {
|
||||||
|
let json = JSON.stringify(msg);
|
||||||
|
let contentLength = Buffer.byteLength(json, this.encoding);
|
||||||
|
|
||||||
|
let headers: string[] = [
|
||||||
|
ContentLength, contentLength.toString(), CRLF,
|
||||||
|
CRLF
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
// callback
|
||||||
|
this.io.on_send_message(msg);
|
||||||
|
// Header must be written in ASCII encoding
|
||||||
|
this.io.send_message(headers.join(''));
|
||||||
|
// Now write the content. This can be written in any encoding
|
||||||
|
this.io.send_message(json);
|
||||||
|
this.errorCount = 0;
|
||||||
|
} catch (error) {
|
||||||
|
this.errorCount++;
|
||||||
|
this.fireError(error, msg, this.errorCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
547
src/lsp/NativeDocumentManager.ts
Normal file
547
src/lsp/NativeDocumentManager.ts
Normal 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: //,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
27
src/lsp/gdscript.capabilities.ts
Normal file
27
src/lsp/gdscript.capabilities.ts
Normal 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[];
|
||||||
|
}
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as http from 'http';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
function requestGodot(body : Object) {
|
|
||||||
let postString = JSON.stringify(body);
|
|
||||||
const port = vscode.workspace.getConfiguration("GodotTools").get("editorServerPort", 6996);
|
|
||||||
const options = {
|
|
||||||
hostname: '127.0.0.1',
|
|
||||||
method: 'POST',
|
|
||||||
port,
|
|
||||||
body,
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Connection": "keep-alive",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Content-Length": Buffer.byteLength(postString)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
|
||||||
var req = http.request(options, (res) => {
|
|
||||||
let resultString = "";
|
|
||||||
res.setEncoding('utf8');
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
resultString += chunk;
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
resolve(JSON.parse(resultString));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', (e) => {
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
req.write(postString);
|
|
||||||
req.end();
|
|
||||||
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default requestGodot;
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
import * as vscode from 'vscode';
|
|
||||||
import godotRequest from './request';
|
|
||||||
import GDScriptSymbolProvider from './gdscript/symbolprovider';
|
|
||||||
import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider';
|
|
||||||
import GDScriptCompletionItemProvider from './gdscript/completion';
|
|
||||||
import GDScriptDefinitionProivder from './gdscript/definitionprovider';
|
|
||||||
import GDScriptHoverProvider from './gdscript/hoverprovider';
|
|
||||||
import GDScriptDocumentContentProvider from './gdscript/docprovider';
|
|
||||||
import GDScriptSignatureHelpProvider from './gdscript/signature_helper';
|
|
||||||
var glob = require("glob")
|
|
||||||
import config from './config';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
class ToolManager {
|
|
||||||
|
|
||||||
private workspaceDir: string = "";
|
|
||||||
private symbolprovider: GDScriptSymbolProvider = null;
|
|
||||||
private workspacesymbolprovider: GDScriptWorkspaceSymbolProvider = null;
|
|
||||||
private _disposable: vscode.Disposable;
|
|
||||||
private _context: vscode.ExtensionContext;
|
|
||||||
private _projectFile : string = "project.godot";
|
|
||||||
private _rootDir : string = "";
|
|
||||||
private _biuitinDocFile : string = "doc/classes-3.0.json";
|
|
||||||
|
|
||||||
constructor(context: vscode.ExtensionContext) {
|
|
||||||
this._context = context;
|
|
||||||
this.workspaceDir = vscode.workspace.rootPath;
|
|
||||||
let completionDollar = false;
|
|
||||||
|
|
||||||
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 3.0) < 3) {
|
|
||||||
this._projectFile = "engine.cfg";
|
|
||||||
this._biuitinDocFile = "doc/classes-2.1.json";
|
|
||||||
completionDollar = true;
|
|
||||||
}
|
|
||||||
this.loadClasses();
|
|
||||||
|
|
||||||
if (vscode.workspace && this.workspaceDir) {
|
|
||||||
vscode.workspace.registerTextDocumentContentProvider('godotdoc', new GDScriptDocumentContentProvider());
|
|
||||||
this.workspaceDir = this.workspaceDir.replace(/\\/g, "/");
|
|
||||||
this._rootDir = vscode.workspace.getConfiguration("GodotTools").get("godotProjectRoot", this.workspaceDir);
|
|
||||||
this._rootDir = this._rootDir.replace("${workspaceRoot}", this.workspaceDir);
|
|
||||||
this.loadWorkspaceSymbols();
|
|
||||||
}
|
|
||||||
|
|
||||||
// documentation symbol provider
|
|
||||||
this.symbolprovider = new GDScriptSymbolProvider();
|
|
||||||
vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider);
|
|
||||||
// workspace symbol provider
|
|
||||||
this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider();
|
|
||||||
vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
|
|
||||||
// definition provider
|
|
||||||
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder(this._rootDir));
|
|
||||||
// hover provider
|
|
||||||
vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
|
|
||||||
// code completion provider
|
|
||||||
if (completionDollar)
|
|
||||||
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'", "$");
|
|
||||||
else
|
|
||||||
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
|
|
||||||
// signature help provider
|
|
||||||
vscode.languages.registerSignatureHelpProvider('gdscript', new GDScriptSignatureHelpProvider(), '(', ',');
|
|
||||||
// Commands
|
|
||||||
this._disposable = vscode.Disposable.from(
|
|
||||||
vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)),
|
|
||||||
vscode.commands.registerCommand('godot.runWorkspace', () => { this.openWorkspaceWithEditor() }),
|
|
||||||
vscode.commands.registerCommand('godot.openWithEditor', () => { this.openWorkspaceWithEditor("-e") }),
|
|
||||||
vscode.commands.registerCommand('godot.runCurrentScene', this.runCurrentScene.bind(this)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
const self = this;
|
|
||||||
godotRequest({
|
|
||||||
action: "editor",
|
|
||||||
command: "projectdir"
|
|
||||||
}).then((res: any) => {
|
|
||||||
let path = res.path;
|
|
||||||
if (path && path.length > 0 && path.endsWith("/"))
|
|
||||||
path = path.substring(0, path.length - 1)
|
|
||||||
if (path.toLowerCase() == self.workspaceDir.toLowerCase())
|
|
||||||
vscode.window.showInformationMessage("Connected to the Godot editor server");
|
|
||||||
else {
|
|
||||||
vscode.window.showWarningMessage("The opened project is not the same within the Godot editor");
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
vscode.window.showErrorMessage("Failed connecting to the Godot editor server");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAllSymbols(): Promise < any > {
|
|
||||||
const self = this;
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
glob(self.workspaceDir + "/**/*.gd", (err, files) => {
|
|
||||||
if (!err) {
|
|
||||||
const symbols = {};
|
|
||||||
for (let i = 0; i < files.length; i++)
|
|
||||||
symbols[config.normalizePath(files[i])] = config.loadSymbolsFromFile(files[i]);
|
|
||||||
// load autoloads from engin.cfg
|
|
||||||
const engincfg = path.join(self.workspaceDir, this._projectFile);
|
|
||||||
if (fs.existsSync(engincfg) && fs.statSync(engincfg).isFile()) {
|
|
||||||
try {
|
|
||||||
const script = {
|
|
||||||
constants: {},
|
|
||||||
functions: {},
|
|
||||||
variables: {},
|
|
||||||
signals: {},
|
|
||||||
classes: {},
|
|
||||||
base: "Object",
|
|
||||||
native: "Object",
|
|
||||||
constpathes: {},
|
|
||||||
documents: {},
|
|
||||||
constvalues: {}
|
|
||||||
};
|
|
||||||
let content: string = fs.readFileSync(engincfg, 'utf-8');
|
|
||||||
if (content && content.indexOf("[autoload]") != -1) {
|
|
||||||
content = content.substring(content.indexOf("[autoload]") + "[autoload]".length, content.length);
|
|
||||||
content = content.substring(0, content.indexOf("["));
|
|
||||||
const lines = content.split(/\r?\n/);
|
|
||||||
lines.map((l) => {
|
|
||||||
if (l.indexOf("=") != 0) {
|
|
||||||
const name = l.substring(0, l.indexOf("="));
|
|
||||||
|
|
||||||
let gdpath = l.substring(l.indexOf("res://") + "res://".length, l.indexOf(".gd") + ".gd".length);
|
|
||||||
gdpath = path.join(this._rootDir, gdpath);
|
|
||||||
let showgdpath = vscode.workspace.asRelativePath(gdpath);
|
|
||||||
|
|
||||||
let doc = "Autoloaded instance of " + `[${showgdpath}](${vscode.Uri.file(gdpath).toString()})`;
|
|
||||||
doc = doc.replace(/"/g, " ");
|
|
||||||
|
|
||||||
script.constants[name] = new vscode.Range(0, 0, 0, 0);
|
|
||||||
script.constvalues[name] = "autoload";
|
|
||||||
script.documents[name] = doc;
|
|
||||||
script.constpathes[name] = gdpath;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
symbols["autoload"] = script;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(symbols);
|
|
||||||
} else
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadAllNodesInWorkspace() {
|
|
||||||
glob(this.workspaceDir + "/**/*.tscn", (err, files) => {
|
|
||||||
if (!err) {
|
|
||||||
const symbols = {};
|
|
||||||
for (let i = 0; i < files.length; i++)
|
|
||||||
config.loadScene(files[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadWorkspaceSymbols() {
|
|
||||||
let handle = this.showProgress("Loading symbols");
|
|
||||||
if (vscode.workspace.getConfiguration("GodotTools").get("parseTextScene", false)) {
|
|
||||||
this.loadAllNodesInWorkspace();
|
|
||||||
}
|
|
||||||
this.loadAllSymbols().then(symbols => {
|
|
||||||
handle();
|
|
||||||
vscode.window.setStatusBarMessage("$(check) Workspace symbols", 5000);
|
|
||||||
config.setAllSymbols(symbols);
|
|
||||||
}).catch(e => {
|
|
||||||
handle();
|
|
||||||
vscode.window.setStatusBarMessage("$(x) Workspace symbols", 5000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private openWorkspaceWithEditor(params = "") {
|
|
||||||
let workspaceValid = false
|
|
||||||
if (this.workspaceDir) {
|
|
||||||
let cfg = path.join(this._rootDir, this._projectFile);
|
|
||||||
console.log(cfg);
|
|
||||||
if (fs.existsSync(cfg) && fs.statSync(cfg).isFile())
|
|
||||||
workspaceValid = true;
|
|
||||||
}
|
|
||||||
if (workspaceValid) {
|
|
||||||
let pathFlag = "-path";
|
|
||||||
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 2.1) >= 3)
|
|
||||||
pathFlag = "--path";
|
|
||||||
this.runEditor(`${pathFlag} "${this._rootDir}" ${params}`);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
vscode.window.showErrorMessage("Current workspace is not a Godot project");
|
|
||||||
}
|
|
||||||
|
|
||||||
private runEditor(params = "") {
|
|
||||||
let editorPath = vscode.workspace.getConfiguration("GodotTools").get("editorPath", "")
|
|
||||||
editorPath = editorPath.replace("${workspaceRoot}", this.workspaceDir);
|
|
||||||
console.log(editorPath);
|
|
||||||
if (!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
|
|
||||||
vscode.window.showErrorMessage("Invalid editor path to run the project");
|
|
||||||
} else {
|
|
||||||
let existingTerminal = vscode.window.terminals.find(t => t._name === "GodotTools")
|
|
||||||
if (existingTerminal) {
|
|
||||||
existingTerminal.dispose()
|
|
||||||
}
|
|
||||||
let terminal = vscode.window.createTerminal("GodotTools");
|
|
||||||
editorPath = this.escapeCmd(editorPath);
|
|
||||||
let cmmand = `${editorPath} ${params}`;
|
|
||||||
terminal.sendText(cmmand, true);
|
|
||||||
terminal.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private runCurrentScene() {
|
|
||||||
const absFilePath = vscode.window.activeTextEditor.document.uri.fsPath;
|
|
||||||
let scenePath = null
|
|
||||||
if (vscode.window.activeTextEditor) {
|
|
||||||
scenePath = path.relative(this._rootDir, absFilePath);
|
|
||||||
scenePath = scenePath.replace(/\\/g, "/");
|
|
||||||
}
|
|
||||||
// Run scripts directly which is inhired from SceneTree or MainLoop
|
|
||||||
if (scenePath.endsWith(".gd")) {
|
|
||||||
const scriptPath = scenePath;
|
|
||||||
scenePath = config.scriptSceneMap[config.normalizePath(scenePath)];
|
|
||||||
if (!scenePath) {
|
|
||||||
const script = config.loadSymbolsFromFile(absFilePath);
|
|
||||||
if (script) {
|
|
||||||
if(script.native == "SceneTree" || script.native == "MainLoop") {
|
|
||||||
this.runEditor(`-s "${absFilePath}"`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (scenePath) {
|
|
||||||
if (scenePath.endsWith(".gd"))
|
|
||||||
scenePath = ` -s "res://${scenePath}" `;
|
|
||||||
else
|
|
||||||
scenePath = ` "res://${scenePath}" `;
|
|
||||||
this.openWorkspaceWithEditor(scenePath);
|
|
||||||
} else
|
|
||||||
vscode.window.showErrorMessage("Current document is not a scene file or MainLoop");
|
|
||||||
}
|
|
||||||
|
|
||||||
loadClasses() {
|
|
||||||
let done: boolean = false;
|
|
||||||
if (this.workspaceDir)
|
|
||||||
done = config.loadClasses(path.join(this.workspaceDir, ".vscode", "classes.json"));
|
|
||||||
if (!done)
|
|
||||||
done = config.loadClasses(path.join(this._context.extensionPath, this._biuitinDocFile));
|
|
||||||
if (!done)
|
|
||||||
vscode.window.showErrorMessage("Loading GDScript documentation failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this._disposable.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private showProgress(message: string) {
|
|
||||||
let r_resolve;
|
|
||||||
vscode.window.withProgress({ location: vscode.ProgressLocation.Window}, p => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
p.report({message});
|
|
||||||
r_resolve = resolve;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return r_resolve;
|
|
||||||
}
|
|
||||||
|
|
||||||
private escapeCmd(cmd: string) {
|
|
||||||
// Double quote command (should work in at least cmd.exe and bash)
|
|
||||||
let cmdEsc = `"${cmd}"`;
|
|
||||||
|
|
||||||
// Fetch Windows shell type
|
|
||||||
let shell = vscode.workspace.getConfiguration("terminal.integrated.shell").get("windows", "");
|
|
||||||
|
|
||||||
// For powershell we prepend an & to prevent the command being treated as a string
|
|
||||||
if (shell.endsWith("powershell.exe") && process.platform === "win32") {
|
|
||||||
cmdEsc = `&${cmdEsc}`;
|
|
||||||
}
|
|
||||||
return cmdEsc
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ToolManager;
|
|
||||||
15
src/utils.ts
Normal file
15
src/utils.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
|
const CONFIG_CONTAINER = "godot_tools";
|
||||||
|
|
||||||
|
export function get_configuration(name: string, default_value: any = null) {
|
||||||
|
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).get(name, default_value) || default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set_configuration(name: string, value: any) {
|
||||||
|
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).update(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function is_debug_mode(): boolean {
|
||||||
|
return process.env.VSCODE_DEBUG_MODE === "true";
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import * as vscode from 'vscode';
|
|
||||||
import {Disposable, window} from 'vscode';
|
|
||||||
import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
|
|
||||||
import GDScriptCompleter from './gdscript/completion';
|
|
||||||
import config from './config';
|
|
||||||
|
|
||||||
interface DocumentFlag {
|
|
||||||
path: string,
|
|
||||||
version: number
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowWatcher {
|
|
||||||
|
|
||||||
private _disposable: Disposable;
|
|
||||||
private _diagnosticSeverity: GDScriptDiagnosticSeverity;
|
|
||||||
private _lastText: DocumentFlag;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
let subscriptions: Disposable[] = [];
|
|
||||||
window.onDidChangeTextEditorSelection(this.onDidChangeTextEditorSelection.bind(this), this, subscriptions);
|
|
||||||
window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor.bind(this), this, subscriptions);
|
|
||||||
window.onDidChangeTextEditorOptions(this.onDidChangeTextEditorOptions.bind(this), this, subscriptions);
|
|
||||||
window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions);
|
|
||||||
|
|
||||||
this._diagnosticSeverity = new GDScriptDiagnosticSeverity();
|
|
||||||
this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity);
|
|
||||||
this._lastText = {path: "-1", version: -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this._disposable.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires when the [active editor](#window.activeTextEditor)
|
|
||||||
* has changed. *Note* that the event also fires when the active editor changes
|
|
||||||
* to `undefined`.
|
|
||||||
*/
|
|
||||||
private onDidChangeActiveTextEditor(event: any) {
|
|
||||||
// console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
|
|
||||||
if(window.activeTextEditor != undefined) {
|
|
||||||
const doc = window.activeTextEditor.document;
|
|
||||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
|
||||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
|
||||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
|
||||||
}
|
|
||||||
this._lastText = {path: doc.fileName, version: doc.version};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires when the selection in an editor has changed.
|
|
||||||
*/
|
|
||||||
private onDidChangeTextEditorSelection(event: any) {
|
|
||||||
// console.log("[GodotTools]:onDidChangeTextEditorSelection");
|
|
||||||
const doc = window.activeTextEditor.document;
|
|
||||||
const curText: DocumentFlag= {path: doc.fileName, version: doc.version};
|
|
||||||
// Check content changed
|
|
||||||
if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
|
|
||||||
const script = config.loadSymbolsFromFile(doc.fileName);
|
|
||||||
if (vscode.workspace.getConfiguration("GodotTools").get("enableSyntaxChecking", true)) {
|
|
||||||
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
|
|
||||||
}
|
|
||||||
this._lastText = curText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires when the options of an editor have changed.
|
|
||||||
*/
|
|
||||||
private onDidChangeTextEditorOptions(event: any) {
|
|
||||||
// console.log("[GodotTools]:onDidChangeTextEditorOptions", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fires when the view column of an editor has changed.
|
|
||||||
*/
|
|
||||||
private onDidChangeTextEditorViewColumn(event: any) {
|
|
||||||
// console.log("[GodotTools]:onDidChangeTextEditorViewColumn", event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WindowWatcher;
|
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"outDir": "out",
|
"outDir": "out",
|
||||||
"lib": [
|
"lib": [
|
||||||
"es6"
|
"es2020",
|
||||||
],
|
"dom"
|
||||||
"sourceMap": true,
|
],
|
||||||
"rootDir": "."
|
"sourceMap": true,
|
||||||
},
|
"rootDir": "src",
|
||||||
"exclude": [
|
"strict": false,
|
||||||
"node_modules",
|
"allowJs": true
|
||||||
".vscode-test",
|
},
|
||||||
"src/gdscript/server",
|
"exclude": [
|
||||||
"server"
|
"node_modules",
|
||||||
]
|
"out"
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
|||||||
14
tslint.json
Normal file
14
tslint.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"no-string-throw": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-duplicate-variable": false,
|
||||||
|
"curly": true,
|
||||||
|
"class-name": true,
|
||||||
|
"semicolon": [
|
||||||
|
true,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"defaultSeverity": "warning"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user