Compare commits

..

59 Commits
0.3.3 ... 1.0.0

Author SHA1 Message Date
Geequlim
ecffd631a8 Update version to 1.0.0 ! 2019-10-09 14:47:21 +08:00
Geequlim
3645e431d3 Add command to list godot native classes 2019-10-09 13:30:21 +08:00
Geequlim
e2febb81b1 Setup class inherit tree and render in native documentation page 2019-10-09 13:14:04 +08:00
Geequlim
f07e1154ef Improve native documentation webview renderer
Make prism as a custom lib instead of node module to reduce the binary size
2019-10-08 19:34:47 +08:00
geequlim
a563a3584a Bump to 0.9.1 2019-10-06 17:09:09 +08:00
geequlim
758aafc570 Render docs of native symbols in webview mostly works now 2019-10-06 17:06:49 +08:00
geequlim
eba90dbbf9 Jump between native documentations 2019-10-04 20:54:47 +08:00
geequlim
47647a05ae [WIP] Add NativeDocumentManager to show native symbol informations 2019-10-04 19:22:02 +08:00
geequlim
28e284f0ad Improve code hlighting 2019-10-04 14:15:42 +08:00
Geequlim
c26320ec03 Merge pull request #118 from pduzinki:fix-string-highlighting
fix string literal highlighting
2019-10-04 13:05:26 +08:00
pawel duzinkiewicz
7d20df3b35 fix string literal highlighting 2019-10-03 19:58:38 +02:00
Geequlim
a84548aeac Merge pull request #116 from anoadragon453/patch-2
typo fix
2019-09-27 11:12:04 +08:00
Andrew Morgan
0ae80d6bcd typo fix
small typo fix
2019-09-26 23:54:47 +01:00
geequlim
325b17a29c transfer to godotengine/godot-vscode-plugin 2019-08-28 22:36:45 +08:00
geequlim
0938b6384a Fix crash with detect shell configurations of VSCode settings 2019-08-28 22:15:42 +08:00
Geequlim
3064849452 Fix error with restart godot editor
Move more client related code to GDScriptLanguageClient
2019-06-26 17:29:06 +08:00
Geequlim
c416ea6789 Allow retry connect to editor after ignored retry 2019-06-26 12:08:10 +08:00
Geequlim
1e22ac0d9a Fix release build 2019-06-25 17:33:25 +08:00
Geequlim
61e1cafdfe The GDScript LSP Client is works correctly ! 2019-06-25 17:16:59 +08:00
Geequlim
1844dd570b Merge pull request #102 from ankitpriyarup/lsp-client
minor addition in configuration and grammar files
2019-05-31 19:17:31 +08:00
Ankit Priyarup
ff7f31776a minor addition in configuration and grammar files
comment folding, addition of new keywords in grammar, removing wrong comments from snippets which was causing problem
2019-05-31 15:13:40 +05:30
geequlim
fafabc3b34 improve syntax highlight of GDScript 2019-05-22 22:20:54 +08:00
Geequlim
d6331fee89 Implement MessageTransports for the websocket connection
Drop stream wrap fro the websocket connection
2019-05-21 10:47:34 +08:00
geequlim
41b36e6e95 Warp the websocket connection to a mock stream 2019-05-19 18:46:14 +08:00
geequlim
ece1f3118d Checkout new GDScriptLanguageClient branch 2019-05-04 19:48:36 +08:00
Geequlim
2ba776dc40 Merge pull request #94 from anoadragon453/patch-1
Fix small grammar issue
2019-03-13 20:40:28 +08:00
Andrew Morgan
abaa5d32d0 Fix some grammar issue
Lint configuration, not configurations.
2019-03-12 15:24:17 +00:00
Geequlim
06817de78e Merge pull request #84 from lleaff/feat-reuse-term
Close existing terminal when executing "run" commands
2018-12-03 13:06:03 +08:00
lleaff
6aab0be1d4 "run" commands close existing launched instances 2018-10-10 02:03:04 +02:00
Geequlim
307e29a7ec Merge pull request #79 from AlienGamesLLC/master
single line if-else statement
2018-09-08 12:04:47 +08:00
Geequlim
58e8626cac Merge pull request #77 from fractile81/deps-updates
Dependencies all updated to latest versions.
2018-09-08 11:55:57 +08:00
Geequlim
3ac359af86 Merge branch 'master' into deps-updates 2018-09-08 11:55:40 +08:00
Akira Kido
c20909fdb3 fixed regex to reflect my issue 2018-09-04 17:30:40 +09:00
Geequlim
ec9fe7fdc1 Merge pull request #80 from fractile81/update-readme
Further README refinements.  Updates to strings for consistency.
2018-09-04 09:20:58 +08:00
Craig A. Hancock
c84d7f9c12 Further README refinements. Updates to strings in package.json and tool_manager.ts to match. 2018-09-03 10:26:22 -06:00
Akira Kido
492fe5663c single if-else statement 2018-09-03 18:58:47 +09:00
Geequlim
8f5da41a41 Merge pull request #78 from fractile81/update-readme
README updated for consistency.
2018-09-03 00:12:39 +08:00
Craig A. Hancock
e4d86f35be README updated for consistency. 2018-09-02 07:37:37 -06:00
Craig A. Hancock
eed2d4f516 Dependencies all updated to latest versions. Only affects development packages. 2018-09-02 07:07:31 -06:00
geequlim
63b10b1d72 Sync docs with godot 3.0.4
Release 0.3.7
2018-06-29 14:03:42 +08:00
geequlim
fdc07e4743 fix syntax checing with inline condition expression fix #58 2018-06-29 13:19:14 +08:00
geequlim
80ce466d53 fix #54 2018-06-29 13:09:49 +08:00
geequlim
7e3e95086b Enable/Disable unused symbol checking 2018-06-29 13:04:25 +08:00
geequlim
7c8696abc1 Fix bracket checing in conditions fix #59 2018-06-29 12:53:32 +08:00
geequlim
8ddc7dd310 Indentifier checking for keywords bug fix
fix #65
2018-06-29 12:47:11 +08:00
geequlim
e76c06a31a Don't compains with assert checking fix #66 2018-06-29 12:35:10 +08:00
geequlim
4c0f864cf8 Add lint configuration to enable or disable some feature for syntax check 2018-06-29 12:20:09 +08:00
Geequlim
462a7bdbd7 Merge pull request #56 from RivenCode/master
Added handling of paths with spaces in them
2018-03-18 19:48:33 +08:00
RivenCode
e2dbf8146f Added handling of paths with spaces in them.
For the editor path, we use PowerShell syntax, when required.
2018-03-18 21:14:14 +11:00
geequlim
7b21267d07 Update to 0.3.6 2018-02-01 23:06:28 +08:00
geequlim
ef3a70f417 Set default project file to project.godot 2018-02-01 22:52:22 +08:00
geequlim
fc027ea9ad Update to 0.3.5 to works fine with Godot 3.0 2018-01-30 22:47:57 +08:00
geequlim
4c47fff9af Add option to disable syntax checking for GDScript 2018-01-01 15:22:29 +08:00
geequlim
d7ff45edac Add more text resource type support with syntax highlight. 2017-12-25 21:28:43 +08:00
Geequlim
38b0898649 Fix syntax checking for inline if else statement 2017-12-22 15:23:10 +08:00
geequlim
a3b062e242 Release 0.3.4 2017-10-28 11:53:27 +08:00
Geequlim
071e59364f Show window progress when parse workspace symbols 2017-10-16 15:33:25 +08:00
Geequlim
a47980981b Show relative script path in code completion and hover documentation 2017-10-16 15:00:47 +08:00
Geequlim
edcb8b96d1 Fix builtin documentation parsing for godot 2.1 fix #44 2017-10-16 14:17:43 +08:00
49 changed files with 2202 additions and 193236 deletions

7
.editorconfig Normal file
View File

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

1
.gitignore vendored
View File

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

36
.vscode/launch.json vendored
View File

@@ -1,28 +1,24 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.1.0",
"configurations": [
{
"name": "Launch Extension",
"version": "0.2.0",
"configurations": [{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/out/src/**/*.js"],
"preLaunchTask": "npm"
},
{
"name": "Launch Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/out/test"],
"preLaunchTask": "npm"
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: watch",
"env": {
"VSCODE_DEBUG_MODE": true
}
},
]
}

View File

@@ -6,5 +6,6 @@
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}

44
.vscode/tasks.json vendored
View File

@@ -1,30 +1,20 @@
// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
// A task runner that calls a custom npm script that compiles the extension.
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "0.1.0",
// we want to run npm
"command": "npm",
// the command is a shell script
"isShellCommand": true,
// show the output window only if unrecognized errors occur.
"showOutput": "silent",
// we run the custom script "compile" as defined in package.json
"args": ["run", "compile", "--loglevel", "silent"],
// The tsc compiler is started in watching mode
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@@ -1,9 +1,10 @@
.vscode/**
.vscode-test/**
out/test/**
test/**
src/**
**/*.map
.gitignore
tsconfig.json
vsc-extension-quickstart.md
**/tsconfig.json
**/tslint.json
**/*.map
**/*.ts

View File

@@ -1,5 +1,40 @@
# Change Log
### 1.0.0
* Refactor the whole plugin with gdscript language server support
* Add webview renderer to show documentations of native symbols.
* Only support godot 3.2 and above
### 0.3.7
* Add `lint` configuration to control the behaviors of syntax checking
* Fix error with run godot editor when the editor contains spaces
* Disable semicolons and brackets checks as default can be enabled with project settings
* Fix bugs in syntax valiadating
* Sync documentations with godot 3.0.4
```json
{
"lint": {
"semicolon": true,
"conditionBrackets": true
}
}
```
### 0.3.6
* Fix project configuartion file path
### 0.3.5
* Add option to disable syntax checking for GDScript
* Improved inline if else statement syntax checking
* More resource type supported for syntax highglight
* Bump default godot version to 3.0
* Sync the documentations from godot 3.0
### 0.3.4
* Fix bug with builtin symbols parsing for godot 2.1
* Improved hover documentation
* Show window progress when parsing workspace symbols
### 0.3.3
* Fix some syntax checking errors.
* Fix problems with hover documentation with latest VSCode.

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016-2017 Geequlim
Copyright (c) 2016-2019 The Godot Engine community
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,78 +1,63 @@
A complete set of tools to code games with the [Godot game engine](http://www.godotengine.org/) in Visual Studio Code.
**IMPORTANT NOTE**
This version of plugin only support godot 3.2 and above.
## Features
The plug-in comes with a wealth of features to make your programming experience as comfortable as possible
The extension comes with a wealth of features to make your Godot programming experience as comfortable as possible:
- Syntax highlighting for the GDscript language
- Syntax highlighting for the tscn and tres scene formats
- Function definitions and documentation on hover
- Rich auto completion
- Static code validation
- Open projects and scenes in Godot from VScode
- Ctrl click on a variable or method call to jump to its definition
- Full documentation supported with API of godot engine
- Syntax highlighting for the GDScript (`.gd`) language
- Syntax highlighting for the `.tscn` and `.tres` scene formats
- Full Typed GDScript support
- Optional `Smart Mode` to speed up dynamic typed script coding
- Function definitions and documentation display on hover (see image below)
- Rich auto-completion
- Display script warnings and errors
- Ctrl-click on a variable or method call to jump to its definition
- Full documentation of the Godot engine's API supported
- Run godot project from VS Code
![Showing the documentation on hover feature](img/godot-tools.jpg)
![Showing the documentation on hover feature](img/godot-tools.png)
## Available commands
## Available Commands
The plug-ins adds a few entries to the command palette
The extension adds a few entries to the VS Code Command Palette under "GodotTools":
- Update Workspace Symbols
- Run workspace as godot project
- Open workspace with godot editor
- Run current scene
- Open workspace with Godot editor
- Run workspace as Godot project
- List native classes of godot
## Settings
If you like this plugin you can set VSCode as your default script editor with following steps:
### Godot
If you like this extension, you can set VS Code as your default script editor for Godot by following these steps:
1. Open editor settings
2. Select `Text Editor / External`
3. Check the `Use External Editor` box with mouse click
4. Fill `Exec Path` to the path of your Visual Studio Code
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}`
You can use the following settings to setup the Godot Tools:
- GodotTools.godotVersion: The godot version of your project
- GodotTools.editorPath: An absolute path pointing at the Godot Editor executable file. Required to run the project and test scenes from VScode
- GodotTools.workspaceDocumentWithMarkdown: Control the documentations of workspace symbols should be rendered as plain text or html from markdown
- GodotTools.ignoreIndentedVars: Parse variables defined after indent of not
- GodotTools.parseTextScene: Parse scene files with extension ends with tscn
- GodotTools.completeNodePath: Show node paths of of workspace in the code completion
- GodotTools.godotProjectRoot: The godot project directory wich contains project.godot or engine.cfg
## Issues and contributions
### VS Code
The [Godot Tools](https://github.com/GodotExplorer/godot-tools) and the go to [engine modules](https://github.com/GodotExplorer/editor-server) are all hosted on GitHub. Feel free to open issues there and create pull requests anytime.
You can use the following settings to configure Godot Tools:
- **editor_path** - The absolute path to the Godot editor executable
- **gdscript_lsp_server_port** - The websocket server port of the GDScript language server
- **check_status** - Check the GDScript language server connection status
## Issues and Contributions
The [Godot Tools](https://github.com/godotengine/godot-vscode-plugin) extension is an open source project of 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
### Intelisense isn't showing up for me
### Why failed to connect to language server?
- You may not open your project with godot editor.
- Godot 3.2 and above is required.
Make sure you save your .gd file, then run "GodotTools: Update Workspace Symbols" from the command palate
## Release Notes
### 0.3.3
* Fix some syntax checking errors.
* Fix problems with hover documentation with latest VSCode.
* Improved builtin class documentation page.
* Update the documentation data with latest godot version.
### 0.3.2
* Fix syntax checking error with match statement.
* Improved documentation for builtin code blocks.
* Start using MarkdonwString to keep links valid.
### 0.3.1
* Update documentations with latest godot.
* Fix errors with run script and run project.
* Improve code completion with opening script file and constants.
* Some improvements for documentations.
[Full change log](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
## TODOS:
* Convert official BBCode documentation into Markdown and render it to HTML with documentation previewer pages
* Add mermaid support with documentation
* Undefined variable checking
### Why isn't intellisense showing up my script members for me?
- The GDScript is a dynamic typed script language the tool may can infer all the variable types as you want.
- You can turn on the `Smart Mode` in godot editor `Editor Settings > Language Server` check the `Enable Smart Resolve`.

File diff suppressed because one or more lines are too long

View File

@@ -6,36 +6,28 @@
"name": "GDScript",
"patterns": [
{ "include": "#base_expression" },
{ "include": "#self" },
{ "include": "#logic_op" },
{ "include": "#compare_op" },
{ "include": "#arithmetic_op" },
{ "include": "#assignment_op" },
{ "include": "#keywords" },
{ "include": "#self" },
{ "include": "#const_def" },
{ "include": "#var_def" },
{ "include": "#type_declear"},
{ "include": "#class_def" },
{ "match": "\\b(?i:export|tool)\\b", "name": "storage.modifier.static.gdscript" },
{ "include": "#builtinFuncs" },
{
"match": "\\b([A-Za-z_]\\w*)\\b(?=\\s*(?:[(]))",
"name": "support.function.any-method.gdscript"
},
{
"match": "(?<=[^.]\\.)\\b([A-Za-z_]\\w*)\\b(?![(])",
"name": "variable.other.property.gdscript"
},
{ "include": "#classname"},
{ "include": "#builtin_func" },
{ "include": "#builtin_classes" },
{ "include": "#const_vars" },
{ "include": "#class_new"},
{ "include": "#class_is"},
{ "include": "#class_enum"},
{ "include": "#function-declaration" },
{
"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"
},
{ "include": "#builtinClasses" },
{ "include": "#builtinProps" },
{ "include": "#builtinConsts" },
{ "include": "#const_vars" }
{ "include": "#function-return-type" },
{ "include": "#any-method" },
{ "include": "#any-property" },
{ "include": "#extends" },
{ "include": "#parscal_class" }
],
"repository": {
"comment": {
@@ -50,17 +42,32 @@
"strings": {
"patterns": [{
"begin": "\"",
"end": "(?<!\\\\)\"",
"end": "\"",
"patterns": [
{ "name": "constant.character.escape.untitled",
"match": "\\."
}
],
"name": "string.quoted.double.gdscript"
},
{
"begin": "'",
"end": "(?<!\\\\)'",
"end": "'",
"patterns": [
{ "name": "constant.character.escape.untitled",
"match": "\\."
}
],
"name": "string.quoted.single.gdscript"
},
{
"begin": "@\"",
"end": "(?<!\\\\)\"",
"end": "\"",
"patterns": [
{ "name": "constant.character.escape.untitled",
"match": "\\."
}
],
"name": "string.nodepath.gdscript"
}
]
@@ -96,8 +103,8 @@
},
"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",
"name": "keyword.control.gdscript"
"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.language.gdscript"
},
"letter": {
"match": "\\b(?i:true|false|null)\\b",
@@ -130,14 +137,26 @@
"match": "\\b(?i:(const))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
"captures": {
"1": { "name": "storage.type.const.gdscript" },
"2": { "name": "constant.other.gdscript" }
"2": { "name": "constant.language.gdscript" }
}
},
"var_def": {
"match": "\\b(?i:(var))\\s+([a-zA-Z_][a-zA-Z_0-9]*)",
"captures": {
"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": {
@@ -147,19 +166,47 @@
},
"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]+)"
},
"classname": {
"match": "(?<=class_name)\\s+([a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?)",
"name": "entity.name.type.class.gdscript"
},
"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": {
"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"
},
"builtinClasses": {
"match": "(?<![^.]\\.|:)\\b(Vector2|Vector3|Color|Rect2|Array|Basis|Dictionary|Plane|Quat|RID|Rect3|Transform|Transform2D)\\b",
"builtin_classes": {
"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"
},
"const_vars": {
"match": "\\b([A-Z_0-9]+)\\b",
"name": "constant.other.gdscript"
"name": "constant.language.gdscript"
},
"function-declaration": {
"name": "meta.function.gdscript",
"begin": "(?x)\n \\s*\n (?:\\b(static) \\s+)? \\b(func|signal)\\s+\n (?=\n [[:alpha:]_][[:word:]]* \\s* \\(\n )\n",
@@ -213,6 +260,14 @@
{ "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": {
"match": "(?x)\n \\b ((self)|(cls)) \\b \\s*(?:(,)|(?=\\)))\n",
"captures": {
@@ -269,6 +324,12 @@
]
}
]
},
"parscal_class": {
"captures": {
"1": { "name": "entity.name.type.class.gdscript" }
},
"match": "([A-Z][a-zA-Z_0-9]*)"
}
}
}

View File

@@ -25,5 +25,12 @@
"indentationRules": {
"increaseIndentPattern": "^\\s*((class|func|else|elif|for|if|match|while|enum)|(.*\\sdo\\b))\\b[^\\{;]*$",
"decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(else|elif)\\b)"
},
"folding": {
"offSide": true,
"markers": {
"start": "^\\s*#\\s*region\\b",
"end": "^\\s*#\\s*endregion\\b"
}
}
}

View File

@@ -1,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": {
"prefix": "class",
"body": [

2
doc/.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,70 +0,0 @@
#!/usr/bin/python
import sys
import xml.etree.ElementTree as ET
import json
import os
def parseClass(data):
dictCls = dict(data.attrib)
dictCls['brief_description'] = data.find("brief_description").text.strip()
dictCls['description'] = data.find("description").text.strip()
dictCls['methods'] = []
for m in data.find("methods"):
dictCls['methods'].append(parseMethod(m))
dictCls['signals'] = []
for s in (data.find("signals") if data.find("signals") is not None else []):
dictCls['signals'].append(parseMethod(s))
dictCls['constants'] = []
for c in (data.find("constants") if data.find("constants") is not None else []):
dictCls['constants'].append(parseConstant(c))
dictCls['properties'] = []
for m in (data.find("members") if data.find("members") is not None else []):
dictCls['properties'].append(parseProperty(m))
dictCls['theme_properties'] = []
for thi in (data.find("theme_items") if data.find("theme_items") is not None else []):
dictCls['theme_properties'].append(parseProperty(thi))
return dictCls
def parseMethod(data):
dictMethod = dict(data.attrib)
dictMethod['description'] = data.find("description").text.strip()
dictMethod['return_type'] = data.find("return").attrib["type"] if data.find("return") is not None else ""
if "qualifiers" not in dictMethod: dictMethod["qualifiers"] = ""
dictMethod["arguments"] = []
for arg in data.iter('argument'):
dictMethod["arguments"].append(parseArgument(arg))
return dictMethod
def parseArgument(data):
dictArg = dict(data.attrib)
if "dictArg" in dictArg: dictArg.pop("index")
dictArg["default_value"] = dictArg["default"] if "default" in dictArg else ""
if "default" in dictArg: dictArg.pop("default")
return dictArg
def parseConstant(data):
dictConst = dict(data.attrib)
dictConst["description"] = data.text.strip()
return dictConst
def parseProperty(data):
dictProp = dict(data.attrib)
dictProp["description"] = data.text.strip()
return dictProp
def main():
if len(sys.argv) >=2 :
if os.path.isdir(sys.argv[1]):
classes = {}
for fname in os.listdir(sys.argv[1]):
f = os.path.join(sys.argv[1], fname)
tree = ET.parse(open(f, 'r'))
cls = tree.getroot()
dictCls = parseClass(cls)
classes[dictCls['name']] = dictCls
jsonContent = json.dumps({"classes": classes, "version": "3.0.alpha"}, ensure_ascii=False, indent=2)
print(jsonContent)
if __name__ == '__main__':
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

BIN
img/godot-tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

2613
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +1,63 @@
{
"name": "godot-tools",
"displayName": "Godot Tools",
"displayName": "godot-tools",
"icon": "icon.png",
"version": "1.0.0",
"description": "Tools for game development with godot game engine",
"version": "0.3.3",
"repository": "https://github.com/godotengine/godot-vscode-plugin",
"author": "The Godot Engine community",
"publisher": "geequlim",
"repository": "https://github.com/GodotExplorer/godot-tools",
"license": "MIT",
"engines": {
"vscode": "^1.16.1"
"vscode": "^1.33.0"
},
"categories": [
"Other"
],
"activationEvents": [
"workspaceContains:engine.cfg",
"workspaceContains:project.godot",
"onLanguage:gdscript"
],
"main": "./out/src/extension",
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "yarn run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "yarn run compile && node ./node_modules/vscode/bin/test"
},
"contributes": {
"commands": [
{
"command": "godot.updateWorkspaceSymbols",
"title": "GodotTools: Update Workspace Symbols"
"command": "godot-tool.open_editor",
"title": "Godot Tools: Open workspace with Godot editor"
},
{
"command": "godot.runWorkspace",
"title": "GodotTools: Run workspace as godot project"
"command": "godot-tool.run_project",
"title": "Godot Tools: Run workspace as Godot project"
},
{
"command": "godot.openWithEditor",
"title": "GodotTools: Open workspace with godot editor"
},
{
"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",
"title": "Godot tools configuration",
"title": "Godot Tools configuration",
"properties": {
"GodotTools.maxNumberOfProblems": {
"godot_tools.gdscript_lsp_server_port": {
"type": "number",
"default": 100,
"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": {
"godot_tools.editor_path": {
"type": "string",
"default": "",
"description": "The absolute path of your godot editor"
"description": "The absolute path to the Godot editor executable"
},
"GodotTools.workspaceDocumentWithMarkdown": {
"type": "boolean",
"default": false,
"description": "Render workspace documentations as markdown content"
},
"GodotTools.ignoreIndentedVars": {
"type": "boolean",
"default": false,
"description": "Only parse variables without indents in GDScript"
},
"GodotTools.godotVersion": {
"type": "number",
"default": 2.1,
"description": "The godot version of your project"
},
"GodotTools.parseTextScene": {
"type": "boolean",
"default": true,
"description": "Parse scene files with extention ends with tscn"
},
"GodotTools.completeNodePath": {
"type": "boolean",
"default": false,
"description": "Show node pathes of of workspace in the code completion"
},
"GodotTools.godotProjectRoot": {
"godot-tool.check_status": {
"type": "string",
"default": "${workspaceRoot}",
"description": "Relate path to the godot project"
"default": "",
"description": "Check the gdscript language server connection status"
}
}
},
@@ -99,9 +76,12 @@
{
"id": "properties",
"extensions": [
".cfg",
"cfg",
"tres",
"tscn"
"tscn",
"godot",
"gdns",
"gdnlib"
]
}
],
@@ -119,22 +99,20 @@
}
]
},
"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": {
"typescript": "^2.0.3",
"vscode": "^1.1.5",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
"@types/marked": "^0.6.5",
"@types/mocha": "^2.2.42",
"@types/node": "^10.12.21",
"@types/prismjs": "^1.16.0",
"@types/ws": "^6.0.1",
"tslint": "^5.16.0",
"typescript": "^3.4.5",
"vscode": "^1.1.33"
},
"dependencies": {
"glob": "^7.1.1",
"vscode-debugprotocol": "^1.17.0",
"vscode-debugadapter": "^1.17.0"
"global": "^4.4.0",
"marked": "^0.7.0",
"vscode-languageclient": "^5.2.1",
"ws": "^7.0.0"
}
}

View File

@@ -1,326 +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\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];
const addScriptItems = (items, kind: CompletionItemKind, kindName:string = "Symbol", insertText = (n)=>n)=>{
const _items: CompletionItem[] = [];
for (let name of Object.keys(items)) {
const signature = (script.signatures && script.signatures[name])?script.signatures[name]:"";
const cvalue = (script.constvalues && script.constvalues[name])?script.constvalues[name]:"";
const item = new CompletionItem(name+signature, kind);
item.sortText = name;
item.filterText = name;
item.detail = cvalue;
item.insertText = insertText(name) + (signature=="()"?"()":"");
item.documentation = (script.documents && script.documents[name])?script.documents[name]+"\r\n":"";
item.documentation += `${kindName} defined in ${workspace.asRelativePath(path)}`;
_items.push(item);
}
return _items;
}
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();

View File

@@ -1,162 +0,0 @@
// import {
// DebugSession,
// InitializedEvent, TerminatedEvent, StoppedEvent, BreakpointEvent, OutputEvent, Event,
// Thread, StackFrame, Scope, Source, Handles, Breakpoint
// } from 'vscode-debugadapter';
// import {DebugProtocol} from 'vscode-debugprotocol';
// import * as fs from 'fs';
// import * as path from 'path';
// const cmd = require('node-cmd');
// export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
// godot: string;
// projectDir: string;
// runWithEditor: boolean;
// params: string[];
// }
// class GodotDebugSession extends DebugSession {
// // we don't support multiple threads, so we can use a hardcoded ID for the default thread
// private static THREAD_ID = 1;
// /**
// * Creates a new debug adapter that is used for one debug session.
// * We configure the default implementation of a debug adapter here.
// */
// public constructor() {
// super();
// }
// /**
// * The 'initialize' request is the first request called by the frontend
// * to interrogate the features the debug adapter provides.
// */
// protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
// // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
// // we request them early by sending an 'initializeRequest' to the frontend.
// // The frontend will end the configuration sequence by calling 'configurationDone' request.
// this.sendEvent(new InitializedEvent());
// // This debug adapter implements the configurationDoneRequest.
// response.body.supportsConfigurationDoneRequest = true;
// // make VS Code to use 'evaluate' when hovering over source
// response.body.supportsEvaluateForHovers = true;
// // make VS Code to show a 'step back' button
// response.body.supportsStepBack = true;
// this.log("initializeRequest");
// this.log_err("initializeRequest");
// this.log_console("initializeRequest");
// this.sendResponse(response);
// }
// protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
// for(let key of Object.keys(args))
// this.log(`${key} : ${args[key]}`);
// let workspaceValid = false
// if(args.godot && fs.existsSync(args.godot) && fs.statSync(args.godot).isFile() ) {
// if(args.projectDir && fs.existsSync(args.projectDir) && fs.statSync(args.projectDir).isDirectory() ) {
// let cfg = path.join(args.projectDir, "engine.cfg");
// if( fs.existsSync(cfg) && fs.statSync(cfg).isFile())
// workspaceValid = true;
// }
// }
// if(workspaceValid) {
// let params = `-path ${args.projectDir} `;
// if(args.runWithEditor)
// params += "-e";
// if(args.params) {
// for(let p of args.params)
// params += " " + p;
// }
// let cmdcontent = `${args.godot} ${params}`;
// this.log(cmdcontent)
// // TODO: print outputs in terminal console
// cmd.run(cmdcontent);
// this.sendEvent(new TerminatedEvent());
// }
// else {
// this.log_err("Invalidate path of projectDir or godot:");
// this.log_err(JSON.stringify(args, null, '\t'));
// this.sendEvent(new TerminatedEvent());
// }
// }
// protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
// this.sendResponse(response);
// }
// protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
// // return the default thread
// response.body = {
// threads: [
// new Thread(GodotDebugSession.THREAD_ID, "thread 1")
// ]
// };
// this.sendResponse(response);
// }
// /**
// * Returns a fake 'stacktrace' where every 'stackframe' is a word from the current line.
// */
// protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
// this.sendResponse(response);
// }
// protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
// this.sendResponse(response);
// }
// protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
// this.sendResponse(response);
// }
// protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
// this.sendEvent(new TerminatedEvent());
// }
// protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) : void {
// this.sendResponse(response);
// }
// protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
// this.sendResponse(response);
// }
// protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments): void {
// this.sendResponse(response);
// }
// protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
// this.sendResponse(response);
// }
// /**
// * Fire StoppedEvent if line is not empty.
// */
// private fireStepEvent(response: DebugProtocol.Response, ln: number): boolean {
// return false;
// }
// private log(msg: string) {
// const e = new OutputEvent(msg, "stdout");
// this.sendEvent(e);
// }
// private log_err(msg: string) {
// const e = new OutputEvent(msg, "stderr");
// this.sendEvent(e);
// }
// private log_console(msg: string) {
// const e = new OutputEvent(msg, "console");
// this.sendEvent(e);
// }
// }
// DebugSession.run(GodotDebugSession);

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

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

View File

@@ -1,13 +1,17 @@
'use strict';
import { workspace, Disposable, ExtensionContext } from 'vscode';
import WindowWatch from "./window_watcher";
import ToolManager from './tool_manager';
import { ExtensionContext } from "vscode";
import { GodotTools } from "./godot-tools";
let tool: ToolManager = null;
let tools: GodotTools = null;
export function activate(context: ExtensionContext) {
tool = new ToolManager(context);
context.subscriptions.push(tool);
context.subscriptions.push(new WindowWatch());
console.log("[GodotTools]: Extension Activated");
tools = new GodotTools(context);
tools.activate();
}
export function deactivate(): Thenable<void> {
return new Promise((resolve, reject) => {
tools.deactivate();
resolve();
});
}

View File

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

View File

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

View File

@@ -1,189 +0,0 @@
import requestGodot from "../request";
import * as vscode from 'vscode';
import {DiagnosticCollection, DiagnosticSeverity} from 'vscode';
import config from '../config';
interface GDParseError {
message : string,
column : number,
row : number
}
interface GDScript {
members : {
constants: {},
functions: {},
variables: {},
signals: {}
},
base : string,
errors : GDParseError[],
valid : boolean,
is_tool : boolean,
native : string
}
interface ParseRequest {
text : string,
path : string
}
class GDScriptDiagnosticSeverity {
private _subscription : DiagnosticCollection;
constructor() {
this._subscription = vscode.languages.createDiagnosticCollection("gdscript")
}
dispose() {
this._subscription.dispose()
}
async validateScript(doc : vscode.TextDocument, script : any) {
if (doc.languageId == 'gdscript') {
if (script) {
let diagnostics = [ ...(this.validateExpression(doc)), ...(this.validateUnusedSymbols(doc, script)) ];
this._subscription.set(doc.uri, diagnostics);
return true;
}
}
return false;
}
private validateUnusedSymbols(doc : vscode.TextDocument, script) {
let diagnostics = [];
const text = doc.getText();
const check = (name : string, range : vscode.Range) => {
var matchs = text.match(new RegExp(`([^\\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 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 (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+\w.*/))
return
if (line.match(/.*?\\/))
expectEndOfLine = true;
else if (line.match(/.*?\:[\s+]+[^#\s]+/))
return
else if (!line.match(/.*?(\\|\:)/))
diagnostics.push(new vscode.Diagnostic(range, "': or \\' expected at end of the line.", DiagnosticSeverity.Error));
else if (line.match(/(if|elif|while|func|class|match)\s*\:/))
diagnostics.push(new vscode.Diagnostic(range, "Indentifier expected before ':'", DiagnosticSeverity.Error));
else if (line.match(/[^\w]for[^\w]/) && !line.match(/\s+for\s\w+\s+in\s+|[\w+]|\{.*?\}|\[.*?\]|\(.*?\)/)){
if(!(line.match(/".*?for.*?"/) || line.match(/'.*?for.*?'/)))
diagnostics.push(new vscode.Diagnostic(range, "Invalid for expression", DiagnosticSeverity.Error));
}
else if (line.match(/(if|elif|while|match)\s*\(.*\)/))
diagnostics.push(new vscode.Diagnostic(range, "Extra brackets in condition expression.", DiagnosticSeverity.Warning));
const blockIndetCheck = function() {
const err = new vscode.Diagnostic(range, "Expected indented block after expression", DiagnosticSeverity.Error);
if (i < lines.length - 1) {
let next = i + 1;
let nextline = lines[next];
// changes nextline until finds a line containg text or comes to the last line
while (((!nextline || !nextline.trim().length) || nextline.match(/^\s*#/)) && next < lines.length - 1) {
++next;
nextline = lines[next];
}
let nextLineStartAt = -1;
let match = /[^\s]+.*/.exec(nextline);
if (match)
nextLineStartAt = match.index;
if (nextLineStartAt <= curLineStartAt)
diagnostics.push(err);
}
else if(line.match(/\:\s*$/))
diagnostics.push(err);
};
if(!expectEndOfLine)
blockIndetCheck();
}
// Do not check : for end of statement as it breaks match statment
let endOfStateMentWithComma = false;
if(endOfStateMentWithComma && !line.match(colonKeywords) && line.match(/\:\s*$/)) {
let showErr = true;
if( i >= 1 ) {
let previous = i - 1;
let previousline = lines[previous];
while(previousline.match(/\\\s*$/) && previous>=1) {
--previous;
const ppreviousline = lines[previous];
if(ppreviousline.match(/\\\s*$/))
previousline = ppreviousline;
}
const keywords = previousline.match(colonKeywords);
if(keywords && !(previousline.match(new RegExp(`".*?\\s${keywords[1]}\\s.*?"`)) || previousline.match(new RegExp(`'.*?\\s${keywords[1]}\\s.*?'`)) ))
showErr = false
}
if(showErr)
diagnostics.push(new vscode.Diagnostic(range, "Expected end of statement after expression", DiagnosticSeverity.Error));
}
if (line.match(/(if|elif|while|return)\s+\w+\s*=\s*\w+/))
diagnostics.push(new vscode.Diagnostic(range, "Assignment in condition or return expressions", DiagnosticSeverity.Warning));
else if (line.indexOf("==") > 0 && !line.match(/\:\s*/)) {
const endAt = line.indexOf("==");
const precontent = line.substring(0, endAt);
if (!precontent.match(/\s(if|elif|while|return)\s/) && !precontent.match(/=[^=]/) && !expectEndOfLine) {
diagnostics.push(new vscode.Diagnostic(range, "Unhandled comparation expression contains", DiagnosticSeverity.Warning));
}
}
let match = /var\s+(\w+)\s*=\s*(\w+)/.exec(line);
if (match && match.length > 2 && match[1].length > 0 && match[1] == match[2]) {
diagnostics.push(new vscode.Diagnostic(range, "Self Assignment may cause error.", DiagnosticSeverity.Warning));
}
});
return diagnostics;
}
}
export default GDScriptDiagnosticSeverity;

View File

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

View File

@@ -1,192 +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 path of Object.keys(workspaceSymbols)) {
const script = workspaceSymbols[path];
let scriptips: MarkdownString[] = [];
const getHoverText = (items, type, path): MarkdownString[] => {
const _items: MarkdownString[] = [];
for (let name of Object.keys(items)) {
if (name == hoverText) {
let dfile = path;
if (workspace && workspace.asRelativePath(dfile))
dfile = workspace.asRelativePath(dfile);
let signature = "";
if(type == "func"|| type == "signal" && script.signatures[name])
signature = script.signatures[name];
if(type == "const" && script.constvalues[name])
signature = ` = ${script.constvalues[name]}`;
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(path != "autoload")
doc += `*Defined in [${dfile}](${Uri.file(path).toString()})*`;
_items.push(makeMarkdown(doc));
break;
}
}
return _items;
}
scriptips = [...scriptips, ...getHoverText(script.variables, 'var', path)];
scriptips = [...scriptips, ...getHoverText(script.constants, 'const', path)];
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', path)];
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', path)];
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', path)];
if(script.enumerations)
scriptips = [...scriptips, ...getHoverText(script.enumerations, 'const', path)];
tips = [...tips, ...scriptips];
}
};
// check from scnes
const genNodePathTips = ()=> {
for (let scnenepath of Object.keys(config.nodeInfoMap)) {
const nodes: any[] = config.nodeInfoMap[scnenepath];
for (let index = 0; index < nodes.length; index++) {
const node:any = nodes[index];
const fullpath = node.parent + "/" + node.name;
if(fullpath == hoverText || fullpath.endsWith(hoverText)) {
let filepath = scnenepath;
if(workspace && workspace.rootPath)
filepath = path.join(workspace.rootPath, filepath);
let instance = "";
if(node.instance && node.instance.length > 1) {
let instancepath = node.instance;
if(workspace && workspace.rootPath)
instancepath = path.join(workspace.rootPath, instancepath);
instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
}
tips = [...tips,
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)} ${doc}`);
case CompletionItemKind.Method:
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
return makeMarkdown(`${genMethodMarkDown()} ${doc}`);
case CompletionItemKind.Interface:
doc = doc.substring(doc.indexOf("\n")+1, doc.length);
return makeMarkdown(`signal + ${genMethodMarkDown()} ${doc}`);
case CompletionItemKind.Variable:
case CompletionItemKind.Property:
return makeMarkdown(`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)} ${doc}`);
case CompletionItemKind.Enum:
return makeMarkdown(`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)} = ${rowDoc.value} ${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;

View File

@@ -1,109 +0,0 @@
import {
SignatureHelpProvider,
TextDocument,
Position,
CancellationToken,
SignatureInformation,
SignatureHelp,
CompletionItemKind,
ParameterInformation,
workspace
} from 'vscode';
import config from '../config';
import { countSubStr } from './utils';
class GDScriptSignatureHelpProvider implements SignatureHelpProvider {
constructor() {}
provideSignatureHelp(document : TextDocument, position : Position, token : CancellationToken) : SignatureHelp | Thenable < SignatureHelp > {
const self = this;
return new Promise((resolve, reject) => {
const res = self.do_provideSignatureHelp(document, position);
resolve(res);
});
}
/**
* Provide help for the signature at the given position and document.
*
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @param token A cancellation token.
* @return Signature help or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
do_provideSignatureHelp(document : TextDocument, position : Position) : SignatureHelp | Thenable < SignatureHelp > {
const range = document.getWordRangeAtPosition(position);
let funcname = "";
let curparam = 0;
const checkPosition = () => {
const line = document.lineAt(position);
const startPos = line.firstNonWhitespaceCharacterIndex;
const endPos = position.character;
const queryStr = line.text.substring(startPos, endPos);
var reg = /([A-z_]+[A-z0-9_]*)\(/g;
let match = reg.exec(queryStr);
while (match != null) {
funcname = match[1];
match = reg.exec(queryStr);
}
if(funcname != "") {
const funcrangestr = line.text.substring(line.text.indexOf(queryStr)+queryStr.indexOf(funcname)+funcname.length, endPos);
curparam = countSubStr(funcrangestr, ",");
}
};
checkPosition();
let resultSignatures: SignatureInformation[] = [];
if (funcname.length > 0) {
// Builtin functions
for (let key of Object.keys(config.builtinSymbolInfoMap)) {
if (key.endsWith(`\.${funcname}`)) {
if (config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Method || config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Function) {
const rawDoc = config.builtinSymbolInfoMap[key].rowDoc;
const item = config.builtinSymbolInfoMap[key].completionItem;
let signatureInfor: SignatureInformation = new SignatureInformation(item.documentation.split('\n')[0], rawDoc.description);
for(let arg of rawDoc.arguments){
let param: ParameterInformation = new ParameterInformation(`${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}`, "");
signatureInfor.parameters.push(param);
}
resultSignatures.push(signatureInfor);
}
}
}
// workspace functions
for (let path of Object.keys(config.getAllSymbols())) {
let script = config.getSymbols(path);
if(!script.signatures)
continue
let relaPath = path;
if(workspace && workspace.rootPath)
relaPath = workspace.asRelativePath(relaPath);
for(let f of Object.keys(script.signatures)) {
if(f == funcname) {
const signatureStr = script.signatures[f];
let signature: SignatureInformation = new SignatureInformation(`func ${f}${signatureStr}`, `Method defined in ${relaPath}`);
const params = (signatureStr.substring(signatureStr.indexOf("(")+1, signatureStr.indexOf(")"))).split(",");
for(let p of params)
signature.parameters.push(new ParameterInformation(p, ""));
resultSignatures.push(signature);
}
}
}
}
if(resultSignatures.length > 0) {
return ({
signatures: resultSignatures,
activeSignature: 0,
activeParameter: curparam
});
}
return null
}
}
export default GDScriptSignatureHelpProvider;

View File

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

View File

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

View File

@@ -1,41 +0,0 @@
import {TextDocument, Position} from 'vscode';
export function isStr(content:string) {
return (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
}
export function getSelectedContent(document: TextDocument, position: Position):string {
const line = document.lineAt(position);
const wordRange = document.getWordRangeAtPosition(position) ;
const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*?"|'.*?'|@".*?"/g)
let res = line.text.substring(wordRange.start.character, wordRange.end.character);
machs.map(m=>{
if(m) {
const startPos = line.text.indexOf(m);
const endPos = startPos + m.length;
if(isStr(m) && startPos != -1 && wordRange.start.character >= startPos && wordRange.end.character <= endPos){
res = m;
return;
}
}
});
return res;
};
export function getStrContent(rawstr: string):string {
let ss = rawstr;
if(isStr(ss)) {
ss = ss.replace(/"|'|@"|"""/g,"")
}
return ss;
}
export function countSubStr(str:string, sub:string): number {
let count = 0;
let pos = str.indexOf(sub);
while (pos !== -1) {
count++;
pos = str.indexOf(sub, pos + sub.length);
}
return count;
}

View File

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

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

@@ -0,0 +1,165 @@
import * as vscode from "vscode";
import * as path from 'path';
import * as fs from 'fs';
import GDScriptLanguageClient, { ClientStatus } from "./lsp/GDScriptLanguageClient";
import { get_configuration, set_configuration } from "./utils";
const CONFIG_CONTAINER = "godot_tools";
const TOOL_NAME = "GodotTools";
export class GodotTools {
private context: vscode.ExtensionContext;
private client: GDScriptLanguageClient = null;
private workspace_dir = vscode.workspace.rootPath;
private project_file = "project.godot";
private connection_status: vscode.StatusBarItem = null;
constructor(p_context: vscode.ExtensionContext) {
this.context = p_context;
this.client = new GDScriptLanguageClient(p_context);
this.client.watch_status(this.on_client_status_changed.bind(this));
this.connection_status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
}
public activate() {
vscode.commands.registerCommand("godot-tool.open_editor", ()=>{
this.open_workspace_with_editor("-e").catch(err=>vscode.window.showErrorMessage(err));
});
vscode.commands.registerCommand("godot-tool.run_project", ()=>{
this.open_workspace_with_editor().catch(err=>vscode.window.showErrorMessage(err));
});
vscode.commands.registerCommand("godot-tool.check_status", this.check_client_status.bind(this));
this.connection_status.text = "$(sync) Initializing";
this.connection_status.command = "godot-tool.check_status";
this.connection_status.show();
this.client.connect_to_server();
}
public deactivate() {
this.client.stop();
}
private open_workspace_with_editor(params = "") {
return new Promise((resolve, reject) => {
let valid = false
if (this.workspace_dir) {
let cfg = path.join(this.workspace_dir, this.project_file);
valid = (fs.existsSync(cfg) && fs.statSync(cfg).isFile());
}
if (valid) {
this.run_editor(`--path "${this.workspace_dir}" ${params}`).then(()=>resolve()).catch(err=>{
reject(err);
});
} else {
reject("Current workspace is not a Godot project");
}
});
}
private run_editor(params = "") {
return new Promise((resolve, reject) => {
const run_godot = (path: string, params: string) => {
const escape_command = (cmd: string) => {
let cmdEsc = `"${cmd}"`;
const shell_plugin = vscode.workspace.getConfiguration("terminal.integrated.shell");
let shell = shell_plugin ? shell_plugin.get("windows", "") || "" : "";
if (shell.endsWith("powershell.exe") && process.platform === "win32") {
cmdEsc = `&${cmdEsc}`;
}
return cmdEsc;
};
let existingTerminal = vscode.window.terminals.find(t => t.name === TOOL_NAME)
if (existingTerminal) {
existingTerminal.dispose()
}
let terminal = vscode.window.createTerminal(TOOL_NAME);
let editorPath = escape_command(path);
let cmmand = `${editorPath} ${params}`;
terminal.sendText(cmmand, true);
terminal.show();
resolve();
};
let editorPath = get_configuration("editor_path", "")
editorPath = editorPath.replace("${workspaceRoot}", this.workspace_dir);
if (!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
vscode.window.showOpenDialog({
openLabel: "Run",
filters: process.platform === "win32" ? {"Godot Editor Binary": ["exe", "EXE"]} : undefined
}).then((uris: vscode.Uri[])=> {
if (!uris) return;
let path = uris[0].fsPath;
if (!fs.existsSync(path) || !fs.statSync(path).isFile()) {
reject("Invalid editor path to run the project");
} else {
run_godot(path, params);
set_configuration("editor_path", path);
}
});
} else {
run_godot(editorPath, params);
}
});
}
private check_client_status() {
switch (this.client.status) {
case ClientStatus.PENDING:
vscode.window.showInformationMessage("Connecting to GDScript language server");
break;
case ClientStatus.CONNECTED:
vscode.window.showInformationMessage("Connected to GDScript language server");
break;
case ClientStatus.DISCONNECTED:
this.retry_connect_client();
break;
}
}
private on_client_status_changed(status: ClientStatus) {
this.connection_status.color = vscode.ThemeColor;
switch (status) {
case ClientStatus.PENDING:
this.connection_status.text = `$(sync) Connecting`;
this.connection_status.tooltip = `Connecting to GDScript Language Server`;
break;
case ClientStatus.CONNECTED:
this.connection_status.text = `$(check) Connected`;
this.connection_status.tooltip = `Connected to GDScript Language Server`;
if (!this.client.started) {
this.context.subscriptions.push(this.client.start());
}
break;
case ClientStatus.DISCONNECTED:
this.connection_status.text = `$(x) Disconnected`;
this.connection_status.tooltip = `Disconnect to GDScript Language Server`;
// retry
this.retry_connect_client();
break;
default:
break;
}
}
private retry_connect_client() {
vscode.window.showErrorMessage(`Failed connect to GDScript Language Server`, 'Open Godot Editor', 'Retry', 'Ignore').then(item=>{
if (item == 'Retry') {
this.client.connect_to_server();
} else if (item == 'Open Godot Editor') {
this.client.status = ClientStatus.PENDING;
this.open_workspace_with_editor("-e").then(()=>{
setTimeout(()=>{
this.client.connect_to_server();
}, 10 * 1000);
});
}
});
}
};

47
src/loggger.ts Normal file
View File

@@ -0,0 +1,47 @@
export class Logger {
protected buffer: string = "";
protected tag: string = '';
protected time: boolean = false;
constructor(tag: string, time: boolean) {
this.tag = tag;
this.time = time;
}
clear() {
this.buffer = "";
}
log(...messages) {
let line = '';
if (this.tag) {
line += `[${this.tag}]`;
}
if (this.time) {
line += `[${new Date().toISOString()}]`;
}
if (line) {
line += ' ';
}
for (let index = 0; index < messages.length; index++) {
line += messages[index];
if (index < messages.length) {
line += " ";
} else {
line += "\n";
}
}
this.buffer += line;
console.log(line);
}
get_buffer(): string {
return this.buffer;
}
}
const logger = new Logger('godot-tools', true);
export default logger;

View File

@@ -0,0 +1,141 @@
import * as vscode from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, RequestMessage } from "vscode-languageclient";
import { is_debug_mode, get_configuration } from "../utils";
import { MessageIO, MessageIOReader, MessageIOWriter, Message } from "./MessageIO";
import logger from "../loggger";
import { EventEmitter } from "events";
import NativeDocumentManager from './NativeDocumentManager';
function getClientOptions(): LanguageClientOptions {
return {
// Register the server for plain text documents
documentSelector: [
{ scheme: "file", language: "gdscript" },
{ scheme: "untitled", language: "gdscript" },
],
synchronize: {
// Notify the server about file changes to '.gd files contain in the workspace
// fileEvents: workspace.createFileSystemWatcher("**/*.gd"),
},
};
}
function get_server_uri() : string {
let port = get_configuration("gdscript_lsp_server_port", 6008);
return `ws://localhost:${port}`;
}
const io = new MessageIO(get_server_uri());
const serverOptions: ServerOptions = () => {
return new Promise((resolve, reject) => {
resolve({reader: new MessageIOReader(io), writer: new MessageIOWriter(io)});
});
};
export enum ClientStatus {
PENDING,
DISCONNECTED,
CONNECTED,
}
const CUSTOM_MESSAGE = "gdscrip_client/";
export default class GDScriptLanguageClient extends LanguageClient {
public io: MessageIO = io;
private context: vscode.ExtensionContext;
private _started : boolean = false;
private _status : ClientStatus;
private _status_changed_callbacks: ((v : ClientStatus)=>void)[] = [];
private _initialize_request: Message = null;
private message_handler: MessageHandler = null;
private native_doc_manager: NativeDocumentManager = null;
public get started() : boolean { return this._started; }
public get status() : ClientStatus { return this._status; }
public set status(v : ClientStatus) {
if (this._status != v) {
this._status = v;
for (const callback of this._status_changed_callbacks) {
callback(v);
}
}
}
public watch_status(callback: (v : ClientStatus)=>void) {
if (this._status_changed_callbacks.indexOf(callback) == -1) {
this._status_changed_callbacks.push(callback);
}
}
constructor(context: vscode.ExtensionContext) {
super(`GDScriptLanguageClient`, serverOptions, getClientOptions());
this.context = context;
this.status = ClientStatus.PENDING;
this.message_handler = new MessageHandler();
this.io.on('disconnected', this.on_disconnected.bind(this));
this.io.on('connected', this.on_connected.bind(this));
this.io.on('message', this.on_message.bind(this));
this.io.on('send_message', this.on_send_message.bind(this));
this.native_doc_manager = new NativeDocumentManager(this.io);
}
connect_to_server() {
this.status = ClientStatus.PENDING;
io.connect_to_language_server();
}
start(): vscode.Disposable {
this._started = true;
return super.start();
}
private on_send_message(message: Message) {
if (is_debug_mode()) logger.log("[client]", JSON.stringify(message));
if ((message as RequestMessage).method == "initialize") {
this._initialize_request = message;
}
}
private on_message(message: Message) {
if (is_debug_mode()) logger.log("[server]", JSON.stringify(message));
this.message_handler.on_message(message);
}
private on_connected() {
if (this._initialize_request) {
this.io.writer.write(this._initialize_request);
}
this.status = ClientStatus.CONNECTED;
}
private on_disconnected() {
this.status = ClientStatus.DISCONNECTED;
}
};
class MessageHandler extends EventEmitter {
changeWorkspace(params: {path: string}) {
vscode.window.showErrorMessage("The GDScript Language Server can't work properly!\nThe opening workspace is diffrent with the editor's.", 'Reload', 'Ignore').then(item=>{
if (item == "Reload") {
let folderUrl = vscode.Uri.file(params.path);
vscode.commands.executeCommand('vscode.openFolder', folderUrl, false);
}
});
}
on_message(message: any) {
if (message && message.method && (message.method as string).startsWith(CUSTOM_MESSAGE)) {
const method = (message.method as string).substring(CUSTOM_MESSAGE.length, message.method.length);
if (this[method]) {
let ret = this[method](message.params);
if (ret) {
io.writer.write(ret);
}
}
}
}
}

87
src/lsp/MessageBuffer.ts Normal file
View File

@@ -0,0 +1,87 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
const DefaultSize: number = 8192;
const CR: number = Buffer.from('\r', 'ascii')[0];
const LF: number = Buffer.from('\n', 'ascii')[0];
const CRLF: string = '\r\n';
export default class MessageBuffer {
private encoding: string;
private index: number;
private buffer: Buffer;
constructor(encoding: string = 'utf8') {
this.encoding = encoding;
this.index = 0;
this.buffer = Buffer.allocUnsafe(DefaultSize);
}
public append(chunk: Buffer | String): void {
var toAppend: Buffer = <Buffer>chunk;
if (typeof (chunk) === 'string') {
var str = <string>chunk;
var bufferLen = Buffer.byteLength(str, this.encoding);
toAppend = Buffer.allocUnsafe(bufferLen);
toAppend.write(str, 0, bufferLen, this.encoding);
}
if (this.buffer.length - this.index >= toAppend.length) {
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
} else {
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
if (this.index === 0) {
this.buffer = Buffer.allocUnsafe(newSize);
toAppend.copy(this.buffer, 0, 0, toAppend.length);
} else {
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
}
}
this.index += toAppend.length;
}
public tryReadHeaders(): { [key: string]: string; } | undefined {
let result: { [key: string]: string; } | undefined = undefined;
let current = 0;
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
current++;
}
// No header / body separator found (e.g CRLFCRLF)
if (current + 3 >= this.index) {
return result;
}
result = Object.create(null);
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
headers.forEach((header) => {
let index: number = header.indexOf(':');
if (index === -1) {
throw new Error('Message header must separate key and value using :');
}
let key = header.substr(0, index);
let value = header.substr(index + 1).trim();
result![key] = value;
})
let nextStart = current + 4;
this.buffer = this.buffer.slice(nextStart);
this.index = this.index - nextStart;
return result;
}
public tryReadContent(length: number): string | null {
if (this.index < length) {
return null;
}
let result = this.buffer.toString(this.encoding, 0, length);
let nextStart = length;
this.buffer.copy(this.buffer, 0, nextStart);
this.index = this.index - nextStart;
return result;
}
public get numberOfBytes(): number {
return this.index;
}
};

200
src/lsp/MessageIO.ts Normal file
View File

@@ -0,0 +1,200 @@
import { AbstractMessageReader, MessageReader, DataCallback } from "vscode-jsonrpc/lib/messageReader";
import { EventEmitter } from "events";
import * as WebSocket from 'ws';
import MessageBuffer from "./MessageBuffer";
import { AbstractMessageWriter, MessageWriter } from "vscode-jsonrpc/lib/messageWriter";
import { RequestMessage, ResponseMessage, NotificationMessage } from "vscode-jsonrpc/lib/messages";
export type Message = RequestMessage | ResponseMessage | NotificationMessage;
export class MessageIO extends EventEmitter {
reader: MessageIOReader = null;
writer: MessageIOWriter = null;
private socket: WebSocket = null;
private url: string = "";
constructor(url: string) {
super();
this.url = url;
}
public send_message(message: string) {
if (this.socket) {
this.socket.send(message);
}
}
protected on_message(chunk: WebSocket.Data) {
let message = chunk.toString();
this.emit('data', message);
}
on_send_message(message: any) {
this.emit("send_message", message);
}
on_message_callback(message: any) {
this.emit("message", message);
}
connect_to_language_server():Promise<void> {
return new Promise((resolve, reject) => {
this.socket = null;
const ws = new WebSocket(this.url);
ws.on('open', ()=>{ this.on_connected(ws); resolve(); });
ws.on('message', this.on_message.bind(this));
ws.on('error', this.on_disconnected.bind(this));
ws.on('close', this.on_disconnected.bind(this));
});
}
private on_connected(socket: WebSocket) {
this.socket = socket;
this.emit("connected");
}
private on_disconnected() {
this.socket = null;
this.emit('disconnected');
}
};
export class MessageIOReader extends AbstractMessageReader implements MessageReader {
private io: MessageIO;
private callback: DataCallback;
private buffer: MessageBuffer;
private nextMessageLength: number;
private messageToken: number;
private partialMessageTimer: NodeJS.Timer | undefined;
private _partialMessageTimeout: number;
public constructor(io: MessageIO, encoding: string = 'utf8') {
super();
this.io = io;
this.io.reader = this;
this.buffer = new MessageBuffer(encoding);
this._partialMessageTimeout = 10000;
}
public set partialMessageTimeout(timeout: number) {
this._partialMessageTimeout = timeout;
}
public get partialMessageTimeout(): number {
return this._partialMessageTimeout;
}
public listen(callback: DataCallback): void {
this.nextMessageLength = -1;
this.messageToken = 0;
this.partialMessageTimer = undefined;
this.callback = callback;
this.io.on('data', (data: Buffer) => {
this.onData(data);
});
this.io.on('error', (error: any) => this.fireError(error));
this.io.on('close', () => this.fireClose());
}
private onData(data: Buffer | String): void {
this.buffer.append(data);
while (true) {
if (this.nextMessageLength === -1) {
let headers = this.buffer.tryReadHeaders();
if (!headers) {
return;
}
let contentLength = headers['Content-Length'];
if (!contentLength) {
throw new Error('Header must provide a Content-Length property.');
}
let length = parseInt(contentLength);
if (isNaN(length)) {
throw new Error('Content-Length value must be a number.');
}
this.nextMessageLength = length;
// Take the encoding form the header. For compatibility
// treat both utf-8 and utf8 as node utf8
}
var msg = this.buffer.tryReadContent(this.nextMessageLength);
if (msg === null) {
/** We haven't received the full message yet. */
this.setPartialMessageTimer();
return;
}
this.clearPartialMessageTimer();
this.nextMessageLength = -1;
this.messageToken++;
var json = JSON.parse(msg);
this.callback(json);
// callback
this.io.on_message_callback(json);
}
}
private clearPartialMessageTimer(): void {
if (this.partialMessageTimer) {
clearTimeout(this.partialMessageTimer);
this.partialMessageTimer = undefined;
}
}
private setPartialMessageTimer(): void {
this.clearPartialMessageTimer();
if (this._partialMessageTimeout <= 0) {
return;
}
this.partialMessageTimer = setTimeout((token, timeout) => {
this.partialMessageTimer = undefined;
if (token === this.messageToken) {
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
this.setPartialMessageTimer();
}
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
}
}
const ContentLength: string = 'Content-Length: ';
const CRLF = '\r\n';
export class MessageIOWriter extends AbstractMessageWriter implements MessageWriter {
private io: MessageIO;
private encoding: string;
private errorCount: number;
public constructor(io: MessageIO, encoding: string = 'utf8') {
super();
this.io = io;
this.io.writer = this;
this.encoding = encoding;
this.errorCount = 0;
this.io.on('error', (error: any) => this.fireError(error));
this.io.on('close', () => this.fireClose());
}
public write(msg: Message): void {
let json = JSON.stringify(msg);
let contentLength = Buffer.byteLength(json, this.encoding);
let headers: string[] = [
ContentLength, contentLength.toString(), CRLF,
CRLF
];
try {
// callback
this.io.on_send_message(msg);
// Header must be written in ASCII encoding
this.io.send_message(headers.join(''));
// Now write the content. This can be written in any encoding
this.io.send_message(json);
this.errorCount = 0;
} catch (error) {
this.errorCount++;
this.fireError(error, msg, this.errorCount);
}
}
}

View File

@@ -0,0 +1,547 @@
import * as vscode from 'vscode';
import { EventEmitter } from "events";
import { MessageIO } from "./MessageIO";
import { NotificationMessage } from "vscode-jsonrpc";
import * as Prism from "../deps/prism/prism";
import * as marked from "marked";
import { Methods, NativeSymbolInspectParams, GodotNativeSymbol, GodotNativeClassInfo, GodotCapabilities } from './gdscript.capabilities';
marked.setOptions({
highlight: function (code, lang) {
return Prism.highlight(code, GDScriptGrammar, lang);
}
});
const enum WebViewMessageType {
INSPECT_NATIVE_SYMBOL = 'INSPECT_NATIVE_SYMBOL',
};
const LIST_NATIVE_CLASS_COMMAND = 'godot-tool.list_native_classes';
export default class NativeDocumentManager extends EventEmitter {
private io: MessageIO = null;
private native_classes: {[key: string]: GodotNativeClassInfo } = {};
constructor(io: MessageIO) {
super();
this.io = io;
io.on("message", (message: NotificationMessage)=>{
if (message.method == Methods.SHOW_NATIVE_SYMBOL) {
this.show_native_symbol(message.params);
} else if (message.method == Methods.GDSCRIPT_CAPABILITIES) {
for (const gdclass of (message.params as GodotCapabilities).native_classes) {
this.native_classes[gdclass.name] = gdclass;
}
for (const gdclass of (message.params as GodotCapabilities).native_classes) {
if (gdclass.inherits) {
const extended_classes = this.native_classes[gdclass.inherits].extended_classes || [];
extended_classes.push(gdclass.name);
this.native_classes[gdclass.inherits].extended_classes = extended_classes;
}
}
}
});
vscode.commands.registerCommand(LIST_NATIVE_CLASS_COMMAND, this.list_native_classes.bind(this));
}
private async list_native_classes() {
let classname = await vscode.window.showQuickPick(
Object.keys(this.native_classes).sort(),
{
placeHolder: 'Type godot class name here',
canPickMany: false
}
);
if (classname) {
this.inspect_native_symbol({native_class: classname, symbol_name: classname});
}
}
private inspect_native_symbol(params: NativeSymbolInspectParams) {
this.io.send_message(JSON.stringify({
id: -1,
jsonrpc: "2.0",
method: Methods.INSPECT_NATIVE_SYMBOL,
params
}));
}
private show_native_symbol(symbol: GodotNativeSymbol) {
// 创建webview
const panel = vscode.window.createWebviewPanel(
'doc',
symbol.name,
vscode.ViewColumn.Nine,
{
enableScripts: true, // 启用JS默认禁用
retainContextWhenHidden: false, // webview被隐藏时保持状态避免被重置
}
);
panel.title = symbol.name;
panel.webview.html = this.make_html_content(symbol);
panel.webview.onDidReceiveMessage(this.on_webview_message.bind(this));
}
private on_webview_message(msg: any) {
switch (msg.type) {
case WebViewMessageType.INSPECT_NATIVE_SYMBOL:
this.inspect_native_symbol(msg.data);
break;
default:
break;
}
}
private make_html_content(symbol: GodotNativeSymbol): string {
return `
<html>
<head>
<style type="text/css">
${PrismStyleSheet}
.codeblock {
padding: 0.5em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
!background-color: #fdf6e3;
}
a {
text-decoration: none;
}
</style>
</head>
<body style="line-height: 16pt;">${this.make_symbol_document(symbol)}</body>
<script>
var vscode = acquireVsCodeApi();
function inspect(native_class, symbol_name) {
if (typeof(godot_class) != 'undefined' && godot_class == native_class) {
document.getElementById(symbol_name).scrollIntoView();
} else {
vscode.postMessage({
type: '${WebViewMessageType.INSPECT_NATIVE_SYMBOL}',
data: {
native_class: native_class,
symbol_name: symbol_name
}
});
}
};
</script>
</html>`;
}
private make_symbol_document(symbol: GodotNativeSymbol): string {
const classlink = make_link(symbol.native_class, undefined);
const classinfo = this.native_classes[symbol.native_class];
function make_function_signature(s: GodotNativeSymbol, with_class = false) {
let parts = /\((.*)?\)\s*\-\>\s*(([A-z0-9]+)?)$/.exec(s.detail);
if (!parts) return "";
const ret_type = make_link(parts[2] || "void", undefined);
let args = (parts[1] || "").replace(/\:\s([A-z0-9_]+)(\,\s*)?/g, `: <a href="" onclick="inspect('$1', '$1')">$1</a>$2`);
args = args.replace(/\s=\s(.*?)[\,\)]/g, "")
return `${ret_type} ${with_class?`${classlink}.`:''}${element("a", s.name, {href: `#${s.name}`})}( ${args} )`;
};
function make_symbol_elements(s: GodotNativeSymbol, with_class = false): {index?: string, body: string} {
switch (s.kind) {
case vscode.SymbolKind.Property:
case vscode.SymbolKind.Variable: {
// var Control.anchor_left: float
const parts = /\.([A-z_0-9]+)\:\s(.*)$/.exec(s.detail);
if (!parts) return;
let type = make_link(parts[2], undefined);
let name = element("a", s.name, {href: `#${s.name}`});
const title = element('h4', `${type} ${with_class?`${classlink}.`:''}${s.name}`);
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
const div = element("div", title + doc);
return {
index: type + " " + name,
body: div,
};
} break;
case vscode.SymbolKind.Constant: {
// const Control.FOCUS_ALL: FocusMode = 2
// const Control.NOTIFICATION_RESIZED = 40
const parts = /\.([A-Za-z_0-9]+)(\:\s*)?([A-z0-9_\.]+)?\s*=\s*(.*)$/.exec(s.detail);
if (!parts) return;
let type = make_link(parts[3] || 'int', undefined);
let name = parts[1];
let value = element('code', parts[4]);
const title = element('p', `${type} ${with_class?`${classlink}.`:''}${name} = ${value}`);
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
const div = element("div", title + doc);
return {
body: div
};
} break;
case vscode.SymbolKind.Event: {
const parts = /\.([A-z0-9]+)\((.*)?\)/.exec(s.detail);
if (!parts) return;
const args = (parts[2] || "").replace(/\:\s([A-z0-9_]+)(\,\s*)?/g, `: <a href="" onclick="inspect('$1', '$1')">$1</a>$2`);
const title = element('p', `${with_class?`signal ${with_class?`${classlink}.`:''}`:''}${s.name}( ${args} )`);
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
const div = element("div", title + doc);
return {
body: div
};
} break;
case vscode.SymbolKind.Method:
case vscode.SymbolKind.Function: {
const signature = make_function_signature(s, with_class);
const title = element("h4", signature);
const doc = element("p", format_documentation(s.documentation, symbol.native_class));
const div = element("div", title + doc);
return {
index: signature,
body: div
};
} break;
default:
break;
}
};
if (symbol.kind == vscode.SymbolKind.Class) {
let doc = element("h2", `Native class ${symbol.name}`);
const parts = /extends\s+([A-z0-9]+)/.exec(symbol.detail);
let inherits = parts && parts.length > 1 ? parts[1] : '';
if (inherits) {
let inherits_chian = '';
let base_class = this.native_classes[inherits];
while(base_class) {
inherits_chian += `${inherits_chian?' >':''} ${make_link(base_class.name, undefined)}`;
base_class = this.native_classes[base_class.inherits];
}
inherits = `Inherits: ${inherits_chian}`;
doc += element("p", inherits);
}
if (classinfo && classinfo.extended_classes) {
let inherited = "";
for (const c of classinfo.extended_classes) {
inherited += (inherited ? ', ' : ' ') + make_link(c, c);
}
doc += element("p", `Inherited by:${inherited}`);
}
let constants = "";
let signals = "";
let methods_index = "";
let methods = "";
let properties_index = "";
let propertyies = "";
let others = "";
for (let s of symbol.children as GodotNativeSymbol[]) {
const elements = make_symbol_elements(s);
switch (s.kind) {
case vscode.SymbolKind.Property:
case vscode.SymbolKind.Variable:
properties_index += element("li", elements.index);
propertyies += element("li", elements.body, {id: s.name});
break;
case vscode.SymbolKind.Constant:
constants += element("li", elements.body, {id: s.name});
break;
case vscode.SymbolKind.Event:
signals += element("li", elements.body, {id: s.name});
break;
case vscode.SymbolKind.Method:
case vscode.SymbolKind.Function:
methods_index += element("li", elements.index);
methods += element("li", elements.body, {id: s.name});
break;
default:
others += element("li", elements.body, {id: s.name});
break;
}
}
function add_group(title: string, block: string) {
if (block) {
doc += element('h3', title);
doc += element('ul', block);
}
};
doc += element("p", format_documentation(symbol.documentation, symbol.native_class));
add_group("Properties", properties_index);
add_group("Constants", constants);
add_group("Signals", signals);
add_group("Methods", methods_index);
add_group("Property Descriptions", propertyies);
add_group("Method Descriptions", methods);
add_group("Other Members", others);
doc += element("script", `var godot_class = "${symbol.native_class}";`);
return doc;
} else {
let doc = "";
const elements = make_symbol_elements(symbol, true);
if (elements.index) {
if ([vscode.SymbolKind.Function, vscode.SymbolKind.Method].indexOf(symbol.kind) == -1) {
doc += element("h2", elements.index);
}
}
doc += element("div", elements.body);
return doc;
}
}
}
function element<K extends keyof HTMLElementTagNameMap>(tag: K, content: string, props = {}, new_line?: boolean, indent?:string) {
let props_str = "";
for (const key in props) {
if (props.hasOwnProperty(key)) {
props_str += ` ${key}="${props[key]}"`;
}
}
return `${indent || ''}<${tag} ${props_str}>${content}</${tag}>${new_line ? '\n' : ''}`;
}
function make_link(classname: string, symbol: string) {
if (!symbol || symbol == classname) {
return element('a', classname, {onclick: `inspect('${classname}', '${classname}')`, href: ''});
} else {
return element('a', `${classname}.${symbol}`, {onclick: `inspect('${classname}', '${symbol}')`, href: ''});
}
}
function make_codeblock(code: string) {
const md = marked('```gdscript\n' + code + '\n```');
return `<div class="codeblock">${md}</div>`;
}
function format_documentation(p_bbcode: string, classname: string) {
let html = p_bbcode.trim();
let lines = html.split("\n");
let in_code_block = false;
let code_block_indent = -1;
let cur_code_block = "";
html = "";
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
let block_start = line.indexOf("[codeblock]");
if (block_start != -1) {
code_block_indent = block_start;
in_code_block = true;
line = line.replace("[codeblock]", "");
} else if (in_code_block) {
line = line.substr(code_block_indent, line.length);
}
if (in_code_block && line.indexOf("[/codeblock]") != -1) {
line = line.replace("[/codeblock]", "");
in_code_block = false;
html += make_codeblock(cur_code_block);
cur_code_block = "";
}
if (!in_code_block) {
line = line.trim();
// [i] [/u] [code] --> <i> </u> <code>
line = line.replace(/(\[(\/?)([a-z]+)\])/g, `<$2$3>`);
// [Reference] --> <a>Reference</a>
line = line.replace(/(\[([A-Z]+[A-Z_a-z0-9]*)\])/g, `<a href="" onclick="inspect('$2', '$2')">$2</a>`);
// [method _set] --> <a>_set</a>
line = line.replace(/(\[([a-z]+)\s+([A-Z_a-z][A-Z_a-z0-9]*)\])/g, `<a href="" onclick="inspect('${classname}', '$3')">$3</a>`);
line += "<br/>";
html += line;
} else {
line += "\n";
if (cur_code_block || line.trim()) {
cur_code_block += line;
}
}
}
return html;
}
const GDScriptGrammar = {
'comment': {
pattern: /(^|[^\\])#.*/,
lookbehind: true
},
'string-interpolation': {
pattern: /(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,
greedy: true,
inside: {
'interpolation': {
// "{" <expression> <optional "!s", "!r", or "!a"> <optional ":" format specifier> "}"
pattern: /((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,
lookbehind: true,
inside: {
'format-spec': {
pattern: /(:)[^:(){}]+(?=}$)/,
lookbehind: true
},
'conversion-option': {
pattern: /![sra](?=[:}]$)/,
alias: 'punctuation'
},
rest: null
}
},
'string': /[\s\S]+/
}
},
'triple-quoted-string': {
pattern: /(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,
greedy: true,
alias: 'string'
},
'string': {
pattern: /(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,
greedy: true
},
'function': {
pattern: /((?:^|\s)func[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,
lookbehind: true
},
'class-name': {
pattern: /(\bclass\s+)\w+/i,
lookbehind: true
},
'decorator': {
pattern: /(^\s*)@\w+(?:\.\w+)*/im,
lookbehind: true,
alias: ['annotation', 'punctuation'],
inside: {
'punctuation': /\./
}
},
'keyword': /\b(?:if|elif|else|for|while|break|continue|pass|return|match|func|class|class_name|extends|is|onready|tool|static|export|setget|const|var|as|void|enum|preload|assert|yield|signal|breakpoint|rpc|sync|master|puppet|slave|remotesync|mastersync|puppetsync)\b/,
'builtin': /\b(?:PI|TAU|NAN|INF|_|sin|cos|tan|sinh|cosh|tanh|asin|acos|atan|atan2|sqrt|fmod|fposmod|floor|ceil|round|abs|sign|pow|log|exp|is_nan|is_inf|ease|decimals|stepify|lerp|dectime|randomize|randi|randf|rand_range|seed|rand_seed|deg2rad|rad2deg|linear2db|db2linear|max|min|clamp|nearest_po2|weakref|funcref|convert|typeof|type_exists|char|str|print|printt|prints|printerr|printraw|var2str|str2var|var2bytes|bytes2var|range|load|inst2dict|dict2inst|hash|Color8|print_stack|instance_from_id|preload|yield|assert|Vector2|Vector3|Color|Rect2|Array|Basis|Dictionary|Plane|Quat|RID|Rect3|Transform|Transform2D|AABB|String|Color|NodePath|RID|Object|Dictionary|Array|PoolByteArray|PoolIntArray|PoolRealArray|PoolStringArray|PoolVector2Array|PoolVector3Array|PoolColorArray)\b/,
'boolean': /\b(?:true|false)\b/,
'number': /(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,
'operator': /[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,
'punctuation': /[{}[\];(),.:]/
};
const PrismStyleSheet = `
code[class*="language-"],
pre[class*="language-"] {
color: #657b83; /* base00 */
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
background: #073642; /* base02 */
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
background: #073642; /* base02 */
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background-color: #fdf6e3; /* base3 */
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #93a1a1; /* base1 */
}
.token.punctuation {
color: #586e75; /* base01 */
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #268bd2; /* blue */
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.url,
.token.inserted {
color: #2aa198; /* cyan */
}
.token.entity {
color: #657b83; /* base00 */
background: #eee8d5; /* base2 */
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #859900; /* green */
}
.token.function,
.token.class-name {
color: #b58900; /* yellow */
}
.token.regex,
.token.important,
.token.variable {
color: #cb4b16; /* orange */
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
`;

View File

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

View File

@@ -1,40 +0,0 @@
import * as http from 'http';
import * as vscode from 'vscode';
function requestGodot(body : Object) {
let postString = JSON.stringify(body);
const port = vscode.workspace.getConfiguration("GodotTools").get("editorServerPort", 6996);
const options = {
hostname: '127.0.0.1',
method: 'POST',
port,
body,
headers: {
"Accept": "application/json",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(postString)
}
};
let promise = new Promise((resolve, reject) => {
var req = http.request(options, (res) => {
let resultString = "";
res.setEncoding('utf8');
res.on('data', (chunk) => {
resultString += chunk;
});
res.on('end', () => {
resolve(JSON.parse(resultString));
});
});
req.on('error', (e) => {
reject(e);
});
req.write(postString);
req.end();
});
return promise;
}
export default requestGodot;

View File

@@ -1,248 +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 = "engine.cfg";
private _rootDir : string = "";
private _biuitinDocFile : string = "doc/classes-2.1.json";
constructor(context: vscode.ExtensionContext) {
this._context = context;
this.workspaceDir = vscode.workspace.rootPath;
let completionDollar = false;
if (vscode.workspace.getConfiguration("GodotTools").get("godotVersion", 2.1) >= 3) {
this._projectFile = "project.godot";
this._biuitinDocFile = "doc/classes-3.0.json";
completionDollar = true;
this.loadClasses();
}
if (vscode.workspace && this.workspaceDir) {
vscode.workspace.registerTextDocumentContentProvider('godotdoc', new GDScriptDocumentContentProvider());
this.workspaceDir = this.workspaceDir.replace(/\\/g, "/");
this._rootDir = vscode.workspace.getConfiguration("GodotTools").get("godotProjectRoot", this.workspaceDir);
this._rootDir = this._rootDir.replace("${workspaceRoot}", this.workspaceDir);
this.loadWorkspaceSymbols();
}
// 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 godot editor server");
else {
vscode.window.showWarningMessage("The opened project is not same with godot editor");
}
}).catch(e => {
vscode.window.showErrorMessage("Failed connect to godot editor server");
});
}
loadAllSymbols(): Promise < any > {
const self = this;
return new Promise((resolve, reject) => {
glob(self.workspaceDir + "/**/*.gd", (err, files) => {
if (!err) {
const symbols = {};
for (let i = 0; i < files.length; i++)
symbols[config.normalizePath(files[i])] = config.loadSymbolsFromFile(files[i]);
// load autoloads from engin.cfg
const engincfg = path.join(self.workspaceDir, 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 = "Auto loaded instance of " + `[${showgdpath}](${vscode.Uri.file(gdpath).toString()})`;
doc = doc.replace(/"/g, " ");
script.constants[name] = new vscode.Range(0, 0, 0, 0);
script.constvalues[name] = "autoload";
script.documents[name] = doc;
script.constpathes[name] = gdpath;
}
});
}
symbols["autoload"] = script;
} catch (error) {
console.error(error);
}
}
resolve(symbols);
} else
reject(err);
});
});
}
private loadAllNodesInWorkspace() {
glob(this.workspaceDir + "/**/*.tscn", (err, files) => {
if (!err) {
const symbols = {};
for (let i = 0; i < files.length; i++)
config.loadScene(files[i]);
}
});
}
private loadWorkspaceSymbols() {
if (vscode.workspace.getConfiguration("GodotTools").get("parseTextScene", false)) {
this.loadAllNodesInWorkspace();
}
this.loadAllSymbols().then(symbols => {
vscode.window.setStatusBarMessage("$(check) Workspace symbols", 5000);
config.setAllSymbols(symbols);
}).catch(e => {
vscode.window.setStatusBarMessage("$(x) Workspace symbols", 5000);
});
}
private openWorkspaceWithEditor(params = "") {
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 terminal = vscode.window.createTerminal("Godot");
let cmmand = `${editorPath.replace(" ", "\\ ")} ${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("Load GDScript documentations failed");
}
dispose() {
this._disposable.dispose();
}
};
export default ToolManager;

15
src/utils.ts Normal file
View File

@@ -0,0 +1,15 @@
import * as vscode from "vscode";
const CONFIG_CONTAINER = "godot_tools";
export function get_configuration(name: string, default_value: any = null) {
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).get(name, default_value) || default_value;
}
export function set_configuration(name: string, value: any) {
return vscode.workspace.getConfiguration(CONFIG_CONTAINER).update(name, value);
}
export function is_debug_mode(): boolean {
return process.env.VSCODE_DEBUG_MODE === "true";
}

View File

@@ -1,78 +0,0 @@
import {Disposable, window} from 'vscode';
import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
import GDScriptCompleter from './gdscript/completion';
import config from './config';
interface DocumentFlag {
path: string,
version: number
}
class WindowWatcher {
private _disposable: Disposable;
private _diagnosticSeverity: GDScriptDiagnosticSeverity;
private _lastText: DocumentFlag;
constructor() {
let subscriptions: Disposable[] = [];
window.onDidChangeTextEditorSelection(this.onDidChangeTextEditorSelection.bind(this), this, subscriptions);
window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor.bind(this), this, subscriptions);
window.onDidChangeTextEditorOptions(this.onDidChangeTextEditorOptions.bind(this), this, subscriptions);
window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions);
this._diagnosticSeverity = new GDScriptDiagnosticSeverity();
this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity);
this._lastText = {path: "-1", version: -1};
}
dispose() {
this._disposable.dispose();
}
/**
* Fires when the [active editor](#window.activeTextEditor)
* has changed. *Note* that the event also fires when the active editor changes
* to `undefined`.
*/
private onDidChangeActiveTextEditor(event: any) {
// console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
if(window.activeTextEditor != undefined) {
const doc = window.activeTextEditor.document;
const script = config.loadSymbolsFromFile(doc.fileName);
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
this._lastText = {path: doc.fileName, version: doc.version};
}
}
/**
* Fires when the selection in an editor has changed.
*/
private onDidChangeTextEditorSelection(event: any) {
// console.log("[GodotTools]:onDidChangeTextEditorSelection");
const doc = window.activeTextEditor.document;
const curText: DocumentFlag= {path: doc.fileName, version: doc.version};
// Check content changed
if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
const script = config.loadSymbolsFromFile(doc.fileName);
this._diagnosticSeverity.validateScript(doc, script).then(()=>{});
this._lastText = curText;
}
}
/**
* Fires when the options of an editor have changed.
*/
private onDidChangeTextEditorOptions(event: any) {
// console.log("[GodotTools]:onDidChangeTextEditorOptions", event);
}
/**
* Fires when the view column of an editor has changed.
*/
private onDidChangeTextEditorViewColumn(event: any) {
// console.log("[GodotTools]:onDidChangeTextEditorViewColumn", event);
}
}
export default WindowWatcher;

View File

@@ -4,15 +4,16 @@
"target": "es6",
"outDir": "out",
"lib": [
"es6"
"es2020",
"dom"
],
"sourceMap": true,
"rootDir": "."
"rootDir": "src",
"strict": false,
"allowJs": true
},
"exclude": [
"node_modules",
".vscode-test",
"src/gdscript/server",
"server"
"out"
]
}

14
tslint.json Normal file
View File

@@ -0,0 +1,14 @@
{
"rules": {
"no-string-throw": true,
"no-unused-expression": true,
"no-duplicate-variable": false,
"curly": true,
"class-name": true,
"semicolon": [
true,
"always"
],
},
"defaultSeverity": "warning"
}