Compare commits

...

31 Commits
0.9.0 ... 1.0.1

Author SHA1 Message Date
Geequlim
2a467ed990 Release version 1.0.1 2020-01-30 00:12:21 +08:00
Geequlim
60937ad776 Fix run editor error on windows with default terminal configurations 2020-01-30 00:11:23 +08:00
Geequlim
0ac1299f47 Release 1.0.0 2020-01-29 20:44:33 +08:00
Geequlim
cf22aa3595 Merge pull request #138 from sevkin/137
check before calling platform-specific code
2020-01-26 16:27:00 +08:00
Vsevolod Balashov
c41bd642f0 check before calling platform-specific code
closes #137
2020-01-26 02:29:34 +03:00
geequlim
8d8f5c2d9b Support reconnect to language server when server port changed 2020-01-12 00:00:17 +08:00
Geequlim
119a7ebd23 Merge pull request #134 from Bromeon/bugfix/highlight-camel-case
Fix syntax highlighting for camelCase identifiers
2020-01-10 23:54:50 +08:00
Jan Haller
555cb1ce9a Fix syntax highlighting for camelCase identifiers
Currently, any occurrences of PascalCase identifiers (even as parts of other words) are recognized as classes.
This assumes snake_case convention for all methods and variables and makes it impossible to use camelCase. While this is the recommended GDScript style, the syntax highlighter should allow for different styles as long as it can do so unambiguously. This is already done for existing rules, but overridden by one rule with an overly general regex pattern.

This commit modifies the catch-all rule for the 'parscal_class' group to only capture whole words.
For clarity, it renames 'parscal_class' to 'pascal_case_class'.

Other groups to recognize classes remain unchanged: type_declear, function-return-type, class_def, class_new, class_is, class_enum, class_name, extends
2019-12-29 18:00:31 +01:00
geequlim
ca3a1e62c4 Fix status icon color bug 2019-12-07 14:03:59 +08:00
geequlim
61e05e7d6e Update dependencies 2019-12-07 14:03:38 +08:00
Geequlim
96b833851d Merge pull request #124 from Calinou/improve-messages
Improve messages for consistency and fix typos
2019-11-06 00:25:49 +08:00
Hugo Locurcio
602cc895c0 Improve messages for consistency and fix typos 2019-11-04 18:52:53 +01:00
geequlim
41a58d76b1 Fix indent in GDScript.tmLaguage.json 2019-10-27 12:04:42 +08:00
Geequlim
8d6992822e Merge pull request #123 from OrenjiAkira/master
Fix class_name statement bug
2019-10-27 12:00:41 +08:00
orenjiakira
f0914742e1 Fix class_name statement bug 2019-10-26 16:07:09 -03:00
Geequlim
a1e020560d Merge pull request #120 from Calinou/improve-readme
Improve the README and fix various typos
2019-10-10 23:31:51 +08:00
Hugo Locurcio
cf9e478b12 Improve the README and fix various typos 2019-10-10 17:29:43 +02:00
Geequlim
d155016b08 Merge pull request #121 from Calinou/optimize-images
Optimize images losslessly using `oxipng -o6 --strip --zopfli`
2019-10-10 23:28:36 +08:00
Hugo Locurcio
40b09491ac Optimize images losslessly using oxipng -o6 --strip --zopfli 2019-10-10 17:04:21 +02:00
Geequlim
24c29452c7 Fix typo in readme 2019-10-09 15:15:25 +08:00
Geequlim
ecffd631a8 Update version to 1.0.0 ! 2019-10-09 14:47:21 +08:00
Geequlim
3645e431d3 Add command to list godot native classes 2019-10-09 13:30:21 +08:00
Geequlim
e2febb81b1 Setup class inherit tree and render in native documentation page 2019-10-09 13:14:04 +08:00
Geequlim
f07e1154ef Improve native documentation webview renderer
Make prism as a custom lib instead of node module to reduce the binary size
2019-10-08 19:34:47 +08:00
geequlim
a563a3584a Bump to 0.9.1 2019-10-06 17:09:09 +08:00
geequlim
758aafc570 Render docs of native symbols in webview mostly works now 2019-10-06 17:06:49 +08:00
geequlim
eba90dbbf9 Jump between native documentations 2019-10-04 20:54:47 +08:00
geequlim
47647a05ae [WIP] Add NativeDocumentManager to show native symbol informations 2019-10-04 19:22:02 +08:00
geequlim
28e284f0ad Improve code hlighting 2019-10-04 14:15:42 +08:00
Geequlim
c26320ec03 Merge pull request #118 from pduzinki:fix-string-highlighting
fix string literal highlighting
2019-10-04 13:05:26 +08:00
pawel duzinkiewicz
7d20df3b35 fix string literal highlighting 2019-10-03 19:58:38 +02:00
14 changed files with 1342 additions and 134 deletions

View File

@@ -1,5 +1,13 @@
# Change Log
### 1.0.1
* Fix run editor error on windows with default terminal configurations
### 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
@@ -16,7 +24,7 @@
```
### 0.3.6
* Fix project configuartion file path
* Fix project configuration file path
### 0.3.5
* Add option to disable syntax checking for GDScript
@@ -56,7 +64,7 @@
* Limited code completions
### 0.2.9
* Add configuration `GodotTools.completeNodePath` to switch is complete node pathes
* Add configuration `GodotTools.completeNodePath` to switch is complete node paths
* Enhanced syntax highlight with GDScript
* Enhanced code completion with GDScript
@@ -69,7 +77,7 @@
* Fix some error with syntax checking
* Add symbol support for enumerations
* Remove key bindings for `F5`~`F8` as it might be confict with other functionalities of VSCode
* Remove key bindings for `F5`~`F8` as it might be conflict with other functionalities of VSCode
* You can bind the key bindings back by add following configurations
```json
{
@@ -105,14 +113,14 @@
* Run games within VSCode terminals
* Add key bindings for `F5 to run the workspace` and `F6 to run the edting scene`
* Fix a lot of bugs with unused vaiable cheching
* Fix a lot of bugs with unused variable checking
* Move workspace symbols state notice to status bar
### 0.2.4
* Add code cheching for asignments and comparations
* Impoved builtin documentation preview page
* Fix bugs with unused vaiable cheching
* Add code checking for asignments and comparisons
* Improved builtin documentation preview page
* Fix bugs with unused variable checking
### 0.2.3
* Fix known errors with code syntax checking
@@ -122,7 +130,7 @@
### 0.2.2
* Better Syntax validating for code blocks
* More waring for non-python liked expression
* More warning for non-python liked expression
### 0.2.1
* Support markdown render in hover tips for documentations in workspace symbols
@@ -130,14 +138,14 @@
### 0.2.0
* Show autoloads informations in hover tips and go to autoloads' definitions are supported now
* Show autoloads information in hover tips and go to autoloads' definitions are supported now
* Fix the bug that workspace symbols resoved twice on Windows
### 0.1.9
* Show workspace constant value in hover tips and completion items
* More readable style for links in documentation preview page
* Improve code completion sort order and auto insert `()` for functions without paramaters
* Improve code completion sort order and auto insert `()` for functions without parameters
* Fix bugs with workspace documentation parsing
### 0.1.8
@@ -154,7 +162,7 @@
* Reorder mouse hover tips, builtin methods are at top of workspace methods
* Show callabel signatures with documente symbols and workspace symbols
* Syntax highlight support for signal paramaters
* Syntax highlight support for signal parameters
### 0.1.5

View File

@@ -1,64 +1,79 @@
A complete set of tools to code games with the [Godot game engine](http://www.godotengine.org/) in Visual Studio Code.
# Godot Tools
A complete set of tools to code games with
[Godot Engine](http://www.godotengine.org/) in Visual Studio Code.
**IMPORTANT NOTE:** Versions 1.0.0 and later of this plugin only support
Godot 3.2 or later.
## Features
The extension comes with a wealth of features to make your Godot programming experience as comfortable as possible:
The extension comes with a wealth of features to make your Godot programming
experience as comfortable as possible:
- Syntax highlighting for the GDscript (`.gd`) language
- Syntax highlighting for the GDScript (`.gd`) language
- Syntax highlighting for the `.tscn` and `.tres` scene formats
- Full typed GDScript support
- Optional "Smart Mode" to improve productivity with dynamically typed scripts
- Function definitions and documentation display on hover (see image below)
- Rich auto-completion
- Static code validation
- Open projects and scenes in Godot from VS Code
- Ctrl-click on a variable or method call to jump to its definition
- Full documentation of the Godot engine's API supported
- Rich autocompletion
- Display script warnings and errors
- Ctrl + click on a variable or method call to jump to its definition
- Full documentation of the Godot Engine's API supported
- Run a Godot project from VS Code
![Showing the documentation on hover feature](img/godot-tools.jpg)
![Showing the documentation on hover feature](img/godot-tools.png)
## Available Commands
## Available commands
The extension adds a few entries to the VS Code Command Palette under "GodotTools":
The extension adds a few entries to the VS Code Command Palette under "Godot Tools":
- Update workspace symbols
- Run workspace as Godot project
- Open workspace with Godot editor
- Run current scene
- Run the workspace as a Godot project
- List Godot's native classes
## Settings
### 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. Make sure the `Use External Editor` box is checked
4. Fill `Exec Path` with the path to your VS Code executable
5. Fill `Exec Flags` with `{project} --goto {file}:{line}:{col}`
If you like this extension, you can set VS Code as your default script editor
for Godot by following these steps:
1. Open the **Editor Settings**
2. Select **Text Editor > External**
3. Make sure the **Use External Editor** box is checked
4. Fill **Exec Path** with the path to your VS Code executable
5. Fill **Exec Flags** with `{project} --goto {file}:{line}:{col}`
### VS Code
You can use the following settings to configure Godot Tools:
- **GodotTools.godotVersion** - The Godot version of your project.
- **GodotTools.editorPath** - The absolute path to the Godot executable. Required to run the project and test scenes directly from VS Code.
- **GodotTools.workspaceDocumentWithMarkdown** - Control how the documentation of workspace symbols should be rendered: as plain text or as HTML from Markdown.
- **GodotTools.ignoreIndentedVars** - Only parse variables defined on lines without an indentation.
- **GodotTools.parseTextScene** - Parse a file as a Godot scene when the file name ends with `.tscn`.
- **GodotTools.completeNodePath** - Show node paths within a workspace as part of code completion.
- **GodotTools.godotProjectRoot** - Your Godot project's directory, which contains `project.godot` or `engine.cfg`.
## Issues and Contributions
- `editor_path` - The absolute path to the Godot editor executable.
- `gdscript_lsp_server_port` - The WebSocket server port of the GDScript language server.
- `check_status` - Check the GDScript language server connection status.
The [Godot Tools](https://github.com/GodotExplorer/godot-tools) extension and [engine modules](https://github.com/GodotExplorer/editor-server) are both hosted on GitHub. Feel free to open issues there and create pull requests anytime.
## Issues and contributions
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md) for the latest changes.
The [Godot Tools](https://github.com/godotengine/godot-vscode-plugin) extension
is an open source project from the Godot orgnization. Feel free to open issues
and create pull requests anytime.
See the [full changelog](https://github.com/GodotExplorer/godot-tools/blob/master/CHANGELOG.md)
for the latest changes.
## FAQ
### Why isn't Intellisense showing up for me?
Make sure you save your `.gd` file, then run "GodotTools: Update Workspace Symbols" from the Command Palette.
### Why does it fail to connect to the language server?
## TODO:
* Convert official BBCode documentation into Markdown and render it into HTML with documentation previewer pages
* Add mermaid support with documentation
* Undefined variable checking
- Godot 3.2 or later is required.
- Make sure to open the project in the Godot editor first. If you opened
the editor after opening VS Code, you can click the **Retry** button
in the bottom-right corner in VS Code.
### Why isn't IntelliSense displaying script members?
- GDScript is a dynamically typed script language. The language server can't
infer all variable types.
- To increase the number of results displayed, open the **Editor Settings**,
go to the **Language Server** section then check **Enable Smart Resolve**.

View File

@@ -15,22 +15,19 @@
{ "include": "#const_def" },
{ "include": "#type_declear"},
{ "include": "#class_def" },
{ "include": "#builtinFuncs" },
{ "include": "#builtinClasses" },
{ "include": "#builtinProps" },
{ "include": "#builtinConsts" },
{ "include": "#class_name"},
{ "include": "#builtin_func" },
{ "include": "#builtin_classes" },
{ "include": "#const_vars" },
{ "include": "#classname"},
{ "include": "#class_new"},
{ "include": "#class_is"},
{ "include": "#class_enum"},
{ "include": "#function-declaration" },
{ "include": "#function-return-type" },
{ "include": "#any-method" },
{ "include": "#any-property" },
{
"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": "#extends" },
{ "include": "#pascal_case_class" }
],
"repository": {
"comment": {
@@ -45,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"
}
]
@@ -91,7 +103,7 @@
},
"keywords": {
"match": "\\b(?i:elif|else|for|if|while|break|continue|pass|in|is|return|onready|setget|enum|match|breakpoint|tool|extends|signal|class|static|export|var|const|func|new|void|float|int|bool|as|assert|class_name|preload|yield|remote|sync|master|puppet|slave|remotesync|mastersync|puppetsync)\\b",
"match": "\\b(?i:if|elif|else|for|while|break|continue|pass|return|match|func|class|class_name|extends|is|onready|tool|static|export|setget|const|var|as|void|enum|preload|assert|yield|signal|breakpoint|rpc|sync|master|puppet|slave|remotesync|mastersync|puppetsync)\\b",
"name": "keyword.language.gdscript"
},
"letter": {
@@ -154,17 +166,6 @@
},
"match": "(?<=^class)\\s+([a-zA-Z_]\\w*)\\s*(?=:)"
},
"classname": {
"captures": {
"1": {
"name": "keyword.language.gdscript"
},
"2": {
"name": "entity.other.inherited-class.gdscript"
}
},
"match": "^(class_name)\\s+([a-zA-Z_]\\w*)"
},
"class_new": {
"captures": {
"1": { "name": "entity.name.type.class.gdscript" },
@@ -172,18 +173,36 @@
},
"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]+)"
"match": "\\b([A-Z][a-zA-Z_0-9]*)\\.([A-Z_0-9]+)"
},
"class_name": {
"captures": {
"1": { "name": "entity.name.type.class.gdscript" },
"2": { "name": "class.other.gdscript" }
},
"match": "(?<=class_name)\\s+([a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?)"
},
"extends": {
"match": "(?<=extends)\\s+[a-zA-Z_][a-zA-Z_0-9]*(\\.([a-zA-Z_][a-zA-Z_0-9]*))?",
"name": "entity.other.inherited-class.gdscript"
},
"builtin_func": {
"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": {
"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"
},
@@ -308,6 +327,12 @@
]
}
]
},
"pascal_case_class": {
"captures": {
"1": { "name": "entity.name.type.class.gdscript" }
},
"match": "\\b([A-Z][a-zA-Z_0-9]*)\\b"
}
}
}

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

BIN
img/godot-tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -2,7 +2,7 @@
"name": "godot-tools",
"displayName": "godot-tools",
"icon": "icon.png",
"version": "0.9.0",
"version": "1.0.1",
"description": "Tools for game development with godot game engine",
"repository": "https://github.com/godotengine/godot-vscode-plugin",
"author": "The Godot Engine community",
@@ -19,11 +19,10 @@
],
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "yarn run compile",
"vscode:prepublish": "npm 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"
"lint": "tslint -p ./",
"watch": "tsc -watch -p ./"
},
"contributes": {
"commands": [
@@ -34,6 +33,10 @@
{
"command": "godot-tool.run_project",
"title": "Godot Tools: Run workspace as Godot project"
},
{
"command": "godot-tool.list_native_classes",
"title": "Godot Tools: List native classes of godot"
}
],
"configuration": {
@@ -53,7 +56,7 @@
"godot-tool.check_status": {
"type": "string",
"default": "",
"description": "The absolute path to the Godot editor executable"
"description": "Check the gdscript language server connection status"
}
}
},
@@ -96,16 +99,19 @@
]
},
"devDependencies": {
"@types/marked": "^0.6.5",
"@types/mocha": "^2.2.42",
"@types/node": "^10.12.21",
"@types/prismjs": "^1.16.0",
"@types/ws": "^6.0.1",
"tslint": "^5.16.0",
"vscode": "^1.1.33",
"typescript": "^3.4.5"
"typescript": "^3.5.1",
"@types/vscode": "^1.33.0"
},
"dependencies": {
"vscode-languageclient": "^5.2.1",
"global": "^4.4.0",
"marked": "^0.7.0",
"vscode-languageclient": "^5.2.1",
"ws": "^7.0.0"
}
}

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

@@ -17,11 +17,11 @@ export class GodotTools {
constructor(p_context: vscode.ExtensionContext) {
this.context = p_context;
this.client = new GDScriptLanguageClient();
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));
@@ -30,22 +30,22 @@ export class GodotTools {
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) {
@@ -63,15 +63,18 @@ export class GodotTools {
}
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}`;
if (process.platform === "win32") {
const POWERSHELL = "powershell.exe";
const shell_plugin = vscode.workspace.getConfiguration("terminal.integrated.shell");
let shell = (shell_plugin ? shell_plugin.get("windows", POWERSHELL) : POWERSHELL) || POWERSHELL;
if (shell.endsWith(POWERSHELL)) {
cmdEsc = `&${cmdEsc}`;
}
}
return cmdEsc;
};
@@ -86,7 +89,7 @@ export class GodotTools {
terminal.show();
resolve();
};
let editorPath = get_configuration("editor_path", "")
editorPath = editorPath.replace("${workspaceRoot}", this.workspace_dir);
if (!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) {
@@ -108,38 +111,37 @@ export class GodotTools {
}
});
}
private check_client_status() {
switch (this.client.status) {
case ClientStatus.PENDING:
vscode.window.showInformationMessage("Connecting to GDScript language server");
vscode.window.showInformationMessage("Connecting to the GDScript language server...");
break;
case ClientStatus.CONNECTED:
vscode.window.showInformationMessage("Connected to GDScript language server");
vscode.window.showInformationMessage("Connected to the GDScript language server.");
break;
case ClientStatus.DISCONNECTED:
this.retry_connect_client();
break;
}
}
private on_client_status_changed(status: ClientStatus) {
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`;
this.connection_status.tooltip = `Connecting to the GDScript language server...`;
break;
case ClientStatus.CONNECTED:
this.connection_status.text = `$(check) Connected`;
this.connection_status.tooltip = `Connected to GDScript Language Server`;
this.connection_status.tooltip = `Connected to the GDScript language server.`;
if (!this.client.started) {
this.context.subscriptions.push(this.client.start());
}
break;
case ClientStatus.DISCONNECTED:
this.connection_status.text = `$(x) Disconnected`;
this.connection_status.tooltip = `Disconnect to GDScript Language Server`;
this.connection_status.tooltip = `Disconnected from the GDScript language server.`;
// retry
this.retry_connect_client();
break;
@@ -147,9 +149,9 @@ export class GodotTools {
break;
}
}
private retry_connect_client() {
vscode.window.showErrorMessage(`Failed connect to GDScript Language Server`, 'Open Godot Editor', 'Retry', 'Ignore').then(item=>{
vscode.window.showErrorMessage(`Couldn't connect to the GDScript language server.`, 'Open Godot Editor', 'Retry', 'Ignore').then(item=>{
if (item == 'Retry') {
this.client.connect_to_server();
} else if (item == 'Open Godot Editor') {

View File

@@ -1,11 +1,10 @@
import * as vscode from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, RequestMessage, NotificationMessage } from "vscode-languageclient";
import { LanguageClient, LanguageClientOptions, ServerOptions, RequestMessage } from "vscode-languageclient";
import { is_debug_mode, get_configuration } from "../utils";
import { MessageIO, MessageIOReader, MessageIOWriter } from "./MessageIO";
import { ResponseMessage } from "vscode-jsonrpc/lib/messages";
import { MessageIO, MessageIOReader, MessageIOWriter, Message } from "./MessageIO";
import logger from "../loggger";
import { EventEmitter } from "events";
type Message = RequestMessage | ResponseMessage | NotificationMessage;
import NativeDocumentManager from './NativeDocumentManager';
function getClientOptions(): LanguageClientOptions {
return {
@@ -41,15 +40,17 @@ export enum ClientStatus {
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) {
@@ -60,52 +61,54 @@ export default class GDScriptLanguageClient extends LanguageClient {
}
}
}
public watch_status(callback: (v : ClientStatus)=>void) {
if (this._status_changed_callbacks.indexOf(callback) == -1) {
this._status_changed_callbacks.push(callback);
}
}
constructor() {
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();
io.connect_to_language_server(get_server_uri());
}
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;
}
@@ -114,16 +117,16 @@ export default class GDScriptLanguageClient extends LanguageClient {
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=>{
vscode.window.showErrorMessage("The GDScript language server can't work properly!\nThe open workspace is different from the editor's.", 'Reload', 'Ignore').then(item=>{
if (item == "Reload") {
let folderUrl = vscode.Uri.file(params.path);
vscode.commands.executeCommand('vscode.openFolder', folderUrl, false);
}
});
}
on_message(message: any) {
if (message && message.method && (message.method as string).startsWith(CUSTOM_MESSAGE)) {
const method = (message.method as string).substring(CUSTOM_MESSAGE.length, message.method.length);

View File

@@ -3,7 +3,8 @@ import { EventEmitter } from "events";
import * as WebSocket from 'ws';
import MessageBuffer from "./MessageBuffer";
import { AbstractMessageWriter, MessageWriter } from "vscode-jsonrpc/lib/messageWriter";
import { Message } from "vscode-jsonrpc";
import { RequestMessage, ResponseMessage, NotificationMessage } from "vscode-jsonrpc/lib/messages";
export type Message = RequestMessage | ResponseMessage | NotificationMessage;
export class MessageIO extends EventEmitter {
@@ -11,11 +12,9 @@ export class MessageIO extends EventEmitter {
writer: MessageIOWriter = null;
private socket: WebSocket = null;
private url: string = "";
constructor(url: string) {
super();
this.url = url;
}
public send_message(message: string) {
@@ -38,10 +37,10 @@ export class MessageIO extends EventEmitter {
this.emit("message", message);
}
connect_to_language_server():Promise<void> {
connect_to_language_server(url: string):Promise<void> {
return new Promise((resolve, reject) => {
this.socket = null;
const ws = new WebSocket(this.url);
const ws = new WebSocket(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));

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

@@ -4,13 +4,16 @@
"target": "es6",
"outDir": "out",
"lib": [
"es6"
"es2020",
"dom"
],
"sourceMap": true,
"rootDir": "src",
"strict": false
"strict": false,
"allowJs": true
},
"exclude": [
"node_modules",
"out"
]
}