mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Implement mouse hover provider
This commit is contained in:
@@ -17,11 +17,14 @@ class Config {
|
||||
// scriptpath : scenepath
|
||||
public scriptSceneMap: Object;
|
||||
// scenepath : NodeInfo[]
|
||||
private nodeInfoMap: Object;
|
||||
public nodeInfoMap: Object;
|
||||
// symbolname: CompletionItem
|
||||
public builtinSymbolInfoMap: Object;
|
||||
|
||||
constructor() {
|
||||
this.symbols = {};
|
||||
this.bintinSybmolInfoList = [];
|
||||
this.builtinSymbolInfoMap = {};
|
||||
this.nodeInfoMap = {};
|
||||
this.scriptSceneMap = {};
|
||||
this.parser = new GDScriptSymbolParser();
|
||||
@@ -78,11 +81,13 @@ class Config {
|
||||
for (let key of Object.keys(this.classes)) {
|
||||
const classdoc = this.classes[key];
|
||||
const bintinSybmolInfoList = this.bintinSybmolInfoList;
|
||||
const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
|
||||
// class
|
||||
const item: CompletionItem = new CompletionItem(classdoc.name, CompletionItemKind.Class);
|
||||
item.detail = 'Native Class';
|
||||
item.documentation = classdoc.brief_description + " \n\n" +classdoc.description;
|
||||
bintinSybmolInfoList.push(item);
|
||||
builtinSymbolInfoMap[classdoc.name] = item;
|
||||
// methods
|
||||
const methods = classdoc.methods
|
||||
const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name+"()")=>{
|
||||
@@ -100,6 +105,7 @@ class Config {
|
||||
mdoc += m.description;
|
||||
mi.documentation = mdoc;
|
||||
bintinSybmolInfoList.push(mi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = mi;
|
||||
};
|
||||
methods.map(m=>parsMethod(m, CompletionItemKind.Method));
|
||||
// signals
|
||||
@@ -112,6 +118,7 @@ class Config {
|
||||
ci.detail = c.value;
|
||||
ci.documentation = `${classdoc.name}.${c.name} = ${c.value}`;
|
||||
bintinSybmolInfoList.push(ci);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = ci;
|
||||
});
|
||||
// properties
|
||||
const properties = classdoc.properties;
|
||||
@@ -120,6 +127,7 @@ class Config {
|
||||
pi.detail = `${p.type} of ${classdoc.name}`;
|
||||
pi.documentation = p.description;
|
||||
bintinSybmolInfoList.push(pi);
|
||||
builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = pi;
|
||||
};
|
||||
properties.map(p=>parseProp(p));
|
||||
// theme_properties
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import {isStr, getSelectedContent, getStrContent} from './utils';
|
||||
|
||||
class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
constructor() {
|
||||
@@ -19,22 +20,6 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
}
|
||||
|
||||
provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Definition | Thenable < Definition > {
|
||||
const isStr = (content:string) => (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
|
||||
const getSelectedContent = ():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) {
|
||||
if(isStr(m)){
|
||||
res = m;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
const getDefinitions = (content: string):Location[]| Location => {
|
||||
if(content.startsWith("res://")) {
|
||||
content = content.replace("res://", "");
|
||||
@@ -57,6 +42,7 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
for (let name of Object.keys(items)) {
|
||||
if(name == content) {
|
||||
_items.push(new Location(Uri.file(path), items[name]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
@@ -72,13 +58,12 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
|
||||
return locations;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let selStr = getSelectedContent();
|
||||
|
||||
let selStr = getSelectedContent(document, position);
|
||||
if(selStr) {
|
||||
// For strings
|
||||
if(isStr(selStr)) {
|
||||
selStr = selStr.replace(/"|'|@"/g,"");
|
||||
selStr = getStrContent(selStr);
|
||||
let fpath = path.join(path.dirname(document.uri.fsPath), selStr)
|
||||
console.log(fpath);
|
||||
if(fs.existsSync(fpath) && fs.statSync(fpath).isFile())
|
||||
|
||||
124
src/gdscript/hoverprovider.ts
Normal file
124
src/gdscript/hoverprovider.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import {
|
||||
HoverProvider,
|
||||
TextDocument,
|
||||
Position,
|
||||
CancellationToken,
|
||||
Hover,
|
||||
MarkedString,
|
||||
workspace,
|
||||
Uri,
|
||||
CompletionItem,
|
||||
CompletionItemKind
|
||||
} from 'vscode';
|
||||
import {
|
||||
isStr,
|
||||
getSelectedContent,
|
||||
getStrContent
|
||||
} from './utils';
|
||||
import config from '../config';
|
||||
import * as path from 'path';
|
||||
|
||||
class GDScriptHoverProvider implements HoverProvider {
|
||||
constructor() {}
|
||||
|
||||
provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover | Thenable < Hover > {
|
||||
let hoverText = getSelectedContent(document, position);
|
||||
if (isStr(hoverText))
|
||||
hoverText = getStrContent(hoverText);
|
||||
const workspaceSymbols = config.getAllSymbols();
|
||||
let tips: MarkedString[] = [];
|
||||
// check from workspace
|
||||
for (let path of Object.keys(workspaceSymbols)) {
|
||||
const script = workspaceSymbols[path];
|
||||
let scriptips: MarkedString[] = [];
|
||||
const getHoverText = (items, type, path): MarkedString[] => {
|
||||
const _items: MarkedString[] = [];
|
||||
for (let name of Object.keys(items)) {
|
||||
if (name == hoverText) {
|
||||
let dfile = path;
|
||||
if (workspace && workspace.asRelativePath(dfile))
|
||||
dfile = workspace.asRelativePath(dfile);
|
||||
_items.push({language:'gdscript', value:`${type} ${name}`});
|
||||
_items.push(`Defined in *[${dfile}](${Uri.file(path).toString()})*`)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
scriptips = [...scriptips, ...getHoverText(script.variables, 'var', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.constants, 'const', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.functions, 'func', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', path)];
|
||||
scriptips = [...scriptips, ...getHoverText(script.classes, 'class', path)];
|
||||
tips = [...tips, ...scriptips];
|
||||
}
|
||||
// check from scnes
|
||||
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,
|
||||
{language: 'gdscript', value: `${node.type} ${fullpath}`},
|
||||
`${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check from builtin
|
||||
const item2MarkdStrings = (name: string,item: CompletionItem):MarkedString[] => {
|
||||
let value = "";
|
||||
let doc = item.documentation;
|
||||
switch (item.kind) {
|
||||
case CompletionItemKind.Class:
|
||||
value += name;
|
||||
break;
|
||||
case CompletionItemKind.Method:
|
||||
value += item.documentation.substring(0, item.documentation.indexOf("\n"));
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
break;
|
||||
case CompletionItemKind.Interface:
|
||||
value += "signal " + item.documentation.substring(0, item.documentation.indexOf("\n"));
|
||||
doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
|
||||
break;
|
||||
case CompletionItemKind.Variable:
|
||||
case CompletionItemKind.Property:
|
||||
value += "var " + name;
|
||||
break;
|
||||
case CompletionItemKind.Enum:
|
||||
value += "const " + name;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return [{language: 'gdscript', value}, 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 = config.builtinSymbolInfoMap[name];
|
||||
tips = [...tips, ...(item2MarkdStrings(name, item))];
|
||||
}
|
||||
}
|
||||
|
||||
if (tips.length > 0)
|
||||
return new Hover(tips);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default GDScriptHoverProvider;
|
||||
31
src/gdscript/utils.ts
Normal file
31
src/gdscript/utils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
}
|
||||
@@ -4,6 +4,8 @@ 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';
|
||||
|
||||
var glob = require("glob")
|
||||
import config from './config';
|
||||
import * as path from 'path';
|
||||
@@ -37,6 +39,8 @@ class ToolManager {
|
||||
vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
|
||||
// definition provider
|
||||
vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder());
|
||||
// hover provider
|
||||
vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
|
||||
// code completion provider
|
||||
vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
|
||||
// Commands
|
||||
|
||||
Reference in New Issue
Block a user