1580 lines
50 KiB
JavaScript
1580 lines
50 KiB
JavaScript
/*
|
||
* @Author: Zh_Strong
|
||
* @Date: 2022-11-07 16:55:18
|
||
* @LastEditTime: 2025-07-15 15:45:12
|
||
* @LastEditors: Qiang 1841747216@qq.com
|
||
* @Description: Zh_Strong
|
||
* @FilePath: \view\lib\ace\src-noconflict\ext-tern.js
|
||
*/
|
||
ace.define("ace/ext/tern", ["require", "exports", "module", "ace/range", "ace/lib/dom", "ace/lib/lang", "ace/config"], function (require, exports, module) {
|
||
"use strict";
|
||
var dom = require("ace/lib/dom");
|
||
var lang = require("../lib/lang");
|
||
var config = require("../config");
|
||
|
||
dom.importCssString(".Ace-Tern-tooltip { border: 1px solid silver; border-radius: 3px; padding: 5px 10px; padding-right:15px; color:#444; font-weight: bold; background-color: white; white-space: pre-wrap; max-width: 50em; max-height:30em; overflow-y:auto; position: absolute; z-index: 10; -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); box-shadow: 2px 3px 5px rgba(0, 0, 0, .2); transition: opacity 1s; -moz-transition: opacity 1s; -webkit-transition: opacity 1s; -o-transition: opacity 1s; -ms-transition: opacity 1s; } .Ace-Tern-tooltip-boxclose { position:absolute; top:0; right:3px; color:red; } .Ace-Tern-tooltip-boxclose:hover { background-color:yellow; } .Ace-Tern-tooltip-boxclose:before { content:'×'; cursor:pointer; font-weight:bold; font-size:larger; } .Ace-Tern-completion { padding-left: 12px; position: relative; } .Ace-Tern-completion:before { position: absolute; left: 0; bottom: 0; border-radius: 50%; font-weight: bold; height: 13px; width: 13px; font-size:11px; line-height: 14px; text-align: center; color: white; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } .Ace-Tern-completion-unknown:before { content:'?'; background: #4bb; } .Ace-Tern-completion-object:before { content:'O'; background: #77c; } .Ace-Tern-completion-fn:before { content:'F'; background: #7c7; } .Ace-Tern-completion-array:before { content:'A'; background: #c66; } .Ace-Tern-completion-number:before { content:'1'; background: #999; } .Ace-Tern-completion-string:before { content:'S'; background: #999; } .Ace-Tern-completion-bool:before { content:'B'; background: #999; } .Ace-Tern-completion-guess { color: #999; } .Ace-Tern-hint-doc { max-width: 35em; } .Ace-Tern-fhint-guess { opacity: .7; } .Ace-Tern-fname { color: black; } .Ace-Tern-farg { color: #70a; font-size: smaller; } .Ace-Tern-farg-current { color: #70a; font-weight:bold; font-size:larger; text-decoration:underline; } .Ace-Tern-farg-current-description { margin-top:5px; font-weight: normal; } .Ace-Tern-farg-current-name { font-weight:bold; } .Ace-Tern-type { color: #07c; font-size:smaller; } .Ace-Tern-jsdoc-tag { color: #B93A38; text-transform: lowercase; font-size:smaller; font-weight:600; } .Ace-Tern-jsdoc-param-wrapper{ /*background-color: #FFFFE3; padding:3px;*/ } .ace-tip-info{ white-space: pre-line; line-height: 1.8; } .Ace-Tern-jsdoc-tag-param-child{ display:inline-block; } .Ace-Tern-jsdoc-param-optionalWrapper { } .Ace-Tern-jsdoc-param-optionalBracket { color:grey; } .Ace-Tern-jsdoc-param-name { color: #70a; font-weight:bold; } .Ace-Tern-jsdoc-param-defaultValue { font-size: smaller;} .Ace-Tern-jsdoc-param-description { color:black; } .Ace-Tern-typeHeader-simple{ font-size:smaller; font-weight:bold; display:block; margin-bottom:3px; color:grey; } .Ace-Tern-typeHeader{ display:block; margin-bottom:3px; } .Ace-Tern-tooltip-link{font-size:smaller; color:blue;}", "ace_tern");
|
||
|
||
|
||
// 主线程
|
||
const worker = new Worker('/lib/ace/src-noconflict/worker-tern.js');
|
||
// 同步变量
|
||
let CODE_TYPE_WORKER, EDIT_DOC_WORKER, LAY_ID_WORKER;
|
||
worker.postMessage({
|
||
action: 'syncvar',
|
||
config: {
|
||
name: "host",
|
||
data: window.location.host
|
||
}
|
||
});
|
||
// 错误提示
|
||
function showError(editor, error) {
|
||
// console.error(editor, error);
|
||
}
|
||
// 拆解键值 JSON类|JSON
|
||
function splitLang(data) {
|
||
if (Object.prototype.toString.call(data) === '[object Object]') {
|
||
for (const key in data) {
|
||
data[key] = splitLang(data[key]);
|
||
if (/\|/.test(key)) {
|
||
let arr = key.split("|");
|
||
arr.forEach((name, index) => {
|
||
data[name] = {
|
||
...data[key],
|
||
"!translated": index ? arr[0] : arr[1]
|
||
};
|
||
})
|
||
delete data[key]
|
||
}
|
||
}
|
||
};
|
||
return data;
|
||
}
|
||
// 分析器
|
||
let requestId = 0;
|
||
let callbackArr = {};
|
||
var TernServer = function (options) {
|
||
let self = this;
|
||
this.options = options || {};
|
||
let plugins = this.options.plugins || (this.options.plugins = {});
|
||
|
||
if (!plugins.hasOwnProperty('doc_comment')) {
|
||
plugins.doc_comment = {}
|
||
};
|
||
if (!plugins.doc_comment.hasOwnProperty('fullDocs')) {
|
||
plugins.doc_comment.fullDocs = true
|
||
};
|
||
if (!this.options.hasOwnProperty('defs')) {
|
||
this.options.defs = ['browser', "ecmascript"];
|
||
}
|
||
|
||
// 绑定解析器服务
|
||
worker.postMessage({
|
||
action: 'init',
|
||
config: {
|
||
async: true,
|
||
defs: this.options.defs,
|
||
plugins: this.options.plugins
|
||
}
|
||
});
|
||
// js的标准文件
|
||
if (this.options.defs && this.options.defs.length > 0) {
|
||
this.options.defs.forEach(async v => {
|
||
this.addDefs("lib/tern/defs/" + v + ".json")
|
||
});
|
||
}
|
||
worker.onmessage = function (event) {
|
||
const {
|
||
id,
|
||
error,
|
||
data
|
||
} = event.data;
|
||
// 利用分析结果更新编辑器状态
|
||
if (id && callbackArr["callback" + id]) {
|
||
callbackArr["callback" + id](error, data);
|
||
delete callbackArr["callback" + id];
|
||
};
|
||
if (id && id == "addDefsError") {
|
||
// layer.open({
|
||
// title: "分析器加载错误",
|
||
// area: "500px",
|
||
// content: "加载支持库分析模块出现错误,错误原因如下:<br>" + error
|
||
// })
|
||
}
|
||
};
|
||
worker.onerror = function (error) {
|
||
console.error('海燕分析器错误:', error.message);
|
||
};
|
||
|
||
|
||
// 设置代码文本为空对象(绝对空对象---没有原型)
|
||
this.docs = Object.create(null);
|
||
// 文本改变
|
||
this.trackChange = function (change, doc) {
|
||
trackChange(self, doc, change);
|
||
};
|
||
// 分析器的提示代码
|
||
this.aceTextCompletor = null;
|
||
// 分析器的防抖节流变量
|
||
this.lastAutoCompleteFireTime = null;
|
||
// 防抖时间
|
||
this.queryTimeout = 3000;
|
||
// 配置参数的防抖时间,进行转换数值型
|
||
if (this.options.queryTimeout && !isNaN(parseInt(this.options.queryTimeout))) {
|
||
this.queryTimeout = parseInt(this.options.queryTimeout)
|
||
};
|
||
};
|
||
var Pos = function (line, ch) {
|
||
return {
|
||
"line": line,
|
||
"ch": ch
|
||
};
|
||
};
|
||
var cls = "Ace-Tern-";
|
||
var bigDoc = 250;
|
||
var debugCompletions = false;
|
||
var requireIds = [];
|
||
|
||
TernServer.prototype = {
|
||
// 添加文档,上下文关联提示器
|
||
addDoc: async function (name, doc) {
|
||
var data = {
|
||
doc: doc.session || doc,
|
||
name: name,
|
||
changed: null
|
||
};
|
||
var value = '';
|
||
if (doc.constructor.name === 'String') {
|
||
value = doc;
|
||
} else {
|
||
value = docValue(this, data);
|
||
doc.on("change", this.trackChange);
|
||
}
|
||
// 提示关联视图添加文件
|
||
worker.postMessage({
|
||
action: 'addFile',
|
||
config: {
|
||
name: name,
|
||
value: value
|
||
}
|
||
});
|
||
return this.docs[name] = data;
|
||
},
|
||
// 删除文档
|
||
delDoc: async function (name) {
|
||
var found = this.docs[name];
|
||
if (!found) return;
|
||
try {
|
||
found.doc.off("change", this.trackChange);
|
||
} catch (ex) { }
|
||
delete this.docs[name];
|
||
worker.postMessage({
|
||
action: 'delFile',
|
||
config: {
|
||
name: name
|
||
}
|
||
});
|
||
},
|
||
// 添加def文件
|
||
addDefs: async function (def, after = false) {
|
||
worker.postMessage({
|
||
action: 'addDefs',
|
||
config: {
|
||
def,
|
||
after
|
||
}
|
||
});
|
||
},
|
||
// 删除def文件
|
||
deleteDefs: async function (name) {
|
||
worker.postMessage({
|
||
action: 'deleteDefs',
|
||
config: {
|
||
name
|
||
}
|
||
});
|
||
},
|
||
/**
|
||
* 请求分析器 目前类型:
|
||
* completions 提示器代码
|
||
* type 当前值类型
|
||
* definition 当前定义类型
|
||
* documentation 当前文档
|
||
* refs 当前变量的引用
|
||
* rename 变量重命名
|
||
* properties 所有属性
|
||
* files 分析服务器的文件
|
||
*
|
||
*/
|
||
request: function (editor, query, callback, pos, forcePushChangedfile) {
|
||
var self = this;
|
||
var id;
|
||
if (editor.file && editor.file.id) {
|
||
id = editor.file.id
|
||
}
|
||
var doc = findDoc(this, editor, id);
|
||
var request = buildRequest(this, doc, query, pos, forcePushChangedfile);
|
||
|
||
requestId++;
|
||
callbackArr["callback" + requestId] = callback;
|
||
worker.postMessage({
|
||
action: 'request',
|
||
id: requestId,
|
||
config: {
|
||
request
|
||
}
|
||
});
|
||
},
|
||
// 获取代码提示
|
||
getCompletions: function (editor, session, pos, prefix, callback) {
|
||
getCompletions(this, editor, session, pos, prefix, callback);
|
||
},
|
||
// 获取当前值类型
|
||
getTypeAt: function (editor = EDITOR, prefix = '', callback = null) {
|
||
this.request(editor, {
|
||
type: "type",
|
||
types: true,
|
||
origins: true,
|
||
docs: true,
|
||
urls: true,
|
||
prefix: prefix
|
||
}, function (error, data) {
|
||
callback && callback(data);
|
||
if (error) return showError(editor, error);
|
||
});
|
||
},
|
||
// 更新参数值介绍
|
||
updateArgHints: function (editor = EDITOR) {
|
||
updateArgHints(this, editor);
|
||
},
|
||
// 获取当前页面变量、函数
|
||
getoutline: function (editor, fileid, callback) {
|
||
this.request(editor, {
|
||
type: "outline",
|
||
file: fileid,
|
||
lineCharPositions: true,
|
||
needfile: [fileid]
|
||
}, function (error, data) {
|
||
// 赋值
|
||
callback(data ? data.outline : []);
|
||
});
|
||
},
|
||
// 生成提示代码
|
||
createInfoDataTip: function (data = '') {
|
||
return createInfoDataTip(data);
|
||
},
|
||
// 获取项目变量
|
||
// 获取项目变量
|
||
getAllFileVar: function () {
|
||
return new Promise((resolve, reject) => {
|
||
let files = [];
|
||
let num = 0;
|
||
// 获取项目缓存
|
||
if (!window.files_snippets) {
|
||
window.files_snippets = JSON.parse(GetFilesFunctionCache("filesFunction") || "{}");
|
||
}
|
||
const cacheFlieIds = Object.keys(files_snippets); // 获取所有页面代码数据(每次都要重新运行,项目文件可能会更改)
|
||
function getAllFileFunction(fileJson = []) {
|
||
(fileJson.length > 0) && fileJson.forEach(v => {
|
||
let titleArr = v.title.split(".");
|
||
let type = titleArr[titleArr.length - 1];
|
||
|
||
if (['lhtml', 'lcss', 'ljs'].includes(type)) {
|
||
if (ALL_FILE_SEARCH.includes(v.id) || !cacheFlieIds.includes(v.id)) {
|
||
files.push({
|
||
"id": v.id,
|
||
"title": v.title,
|
||
"mode": v.type,
|
||
"href": v.href,
|
||
"content": FILE.getFile(v.href)
|
||
})
|
||
ALL_FILE_SEARCH.push(v.id)
|
||
};
|
||
}
|
||
if (v.children) {
|
||
getAllFileFunction(v.children);
|
||
}
|
||
})
|
||
}
|
||
getAllFileFunction(DirectoryTreeJson);
|
||
function getSnippet(data = [], file, p_name = '', p_type = '') {
|
||
let snippetData = [];
|
||
data.forEach(v => {
|
||
let type = stringToType(v.type);
|
||
snippetData.push({
|
||
title: v.title,
|
||
snippet: v.title,
|
||
p_snippet: p_name,
|
||
page_id: file.id,
|
||
type: type,
|
||
// start: v.start,
|
||
// end: v.end,
|
||
// id: v.id,
|
||
tabTrigger: file.id,
|
||
is_user: v.isUser
|
||
});
|
||
if (v.children) {
|
||
snippetData = [...snippetData, ...getSnippet(v.children, file, p_name ? p_name + "." + v.title : v.title, type)]
|
||
};
|
||
})
|
||
if (snippetData.length > 0) {
|
||
let uniqueArr = snippetData.reduce((map, item) => {
|
||
const key = `${item.title}-${item.type}`; // 创建一个组合键
|
||
if (!map.has(key)) {
|
||
map.set(key, item);
|
||
}
|
||
return map;
|
||
}, new Map()).values();
|
||
snippetData = Array.from(uniqueArr);
|
||
}
|
||
return snippetData;
|
||
}
|
||
// 获取全部提示
|
||
if (files.length === 0) {
|
||
resolve(ALL_FILE_SEARCH_VAR);
|
||
return;
|
||
}
|
||
files.forEach(file => {
|
||
if (!this.docs[file.id] || this.docs[file.id].doc) {
|
||
if (!TAB["tab-" + file.id]) {
|
||
TAB['tab-' + file.id] = new EditSession(file.content, "ace/mode/" + file.mode);
|
||
TAB['tab-' + file.id]["file"] = {
|
||
id: file["id"],
|
||
title: file["title"],
|
||
href: file["href"],
|
||
content: file.content,
|
||
mode: file["mode"],
|
||
};
|
||
}
|
||
this.addDoc(file.id, TAB["tab-" + file.id])
|
||
}
|
||
num++;
|
||
this.getoutline(this.docs[file.id].doc, this.docs[file.id].name, function (data) {
|
||
window.files_snippets[file.id] = Array.from(new Set(getSnippet(data, file)));
|
||
num--;
|
||
if (num == 0) {
|
||
ALL_FILE_SEARCH_VAR = [];
|
||
for (const id in files_snippets) {
|
||
ALL_FILE_SEARCH_VAR = ALL_FILE_SEARCH_VAR.concat(files_snippets[id])
|
||
}
|
||
resolve(ALL_FILE_SEARCH_VAR);
|
||
ALL_FILE_SEARCH = [];
|
||
SetFilesFunctionCache("filesFunction", JSON.stringify(files_snippets))
|
||
}
|
||
})
|
||
})
|
||
});
|
||
},
|
||
// 重置海鸥服务
|
||
reServer: function () {
|
||
var plugins = {
|
||
...this.options.plugins || (this.options.plugins = {})
|
||
};
|
||
// 桌面端需要导入导出
|
||
if (!plugins.hasOwnProperty('es_modules')) plugins.es_modules = {};
|
||
if (!plugins.hasOwnProperty('modules')) plugins.modules = {};
|
||
worker.postMessage({
|
||
action: 'init',
|
||
config: {
|
||
async: true,
|
||
defs: this.options.defs,
|
||
plugins: this.options.plugins
|
||
}
|
||
});
|
||
}
|
||
};
|
||
// 获取文档
|
||
function docValue(ts, doc, query) {
|
||
let val
|
||
if (doc?.getValue) {
|
||
val = JSON.parse(JSON.stringify(doc.doc.getValue()));
|
||
} else if (doc?.doc?.getValue) {
|
||
val = JSON.parse(JSON.stringify(doc.doc.getValue()));
|
||
} else {
|
||
return "";
|
||
}
|
||
requireIds = [];
|
||
if (ts.options.fileFilter) {
|
||
val = ts.options.fileFilter(val, doc.name, doc.doc)
|
||
};
|
||
// 对应光标位置设置标识(行数不会变)
|
||
try {
|
||
if (query && query.type == "completions") {
|
||
let end = query.end;
|
||
let lines = val.split('\n');
|
||
let line = lines[end["line"]];
|
||
lines[end["line"]] = line.slice(0, end["ch"]) + '${fof#光标位置#fof}' + line.slice(end["ch"]);
|
||
val = lines.join('\n');
|
||
}
|
||
} catch (error) {
|
||
|
||
}
|
||
// TODO @类型转换
|
||
val = val.replace(/[\u4E00-\u9FA5A-Za-z0-9_]+@\(([\S\s]+?)\)/g, ($1, $2) => {
|
||
return createType($2);
|
||
});
|
||
// 更改set局域
|
||
val = val.replace(/set\s+/g, "");
|
||
// 引用,导出
|
||
val = val.replace(/(exports|导出模块)\./g, "导出命令 常量 ");
|
||
val = val.replace(/(const|常量)\s+([\S\s]+?)=\s*(require|导入模块)\(([\S\s]+?)\)/g, ($1, $2, $3, $4, $5) => {
|
||
let id = getFileID($5);
|
||
(requireIds.findIndex(rid => rid == id) == -1) && requireIds.push(id);
|
||
if (id === $5.replace(/"/g, "")) {
|
||
return `import ${$3} from "${id}"`;
|
||
} else {
|
||
return `import ${$3} from "./${id}"`;
|
||
}
|
||
});
|
||
// 兼容异步函数无法提示-2024-2-29更新
|
||
val = val.replace(/(?<=^|\s|;)(异步)(?=\s|$)/g, ";");
|
||
// 获取标识重新设置光标位置
|
||
try {
|
||
if (query && query.type == "completions") {
|
||
let end = query.end;
|
||
let lines = val.split('\n');
|
||
let line = lines[end["line"]];
|
||
query.end.ch = line.indexOf("${fof#光标位置#fof}");
|
||
val = val.replace("${fof#光标位置#fof}", "")
|
||
}
|
||
} catch (error) {
|
||
|
||
}
|
||
return val;
|
||
}
|
||
// 新建类型
|
||
function createType(type) {
|
||
return `${type}.原型`;
|
||
}
|
||
// 改变坐标格式
|
||
function toTernLoc(pos) {
|
||
if (typeof (pos.row) !== 'undefined') {
|
||
return {
|
||
line: pos.row,
|
||
ch: pos.column
|
||
};
|
||
}
|
||
return pos;
|
||
}
|
||
// 改变坐标格式
|
||
function toAceLoc(pos) {
|
||
if (pos.line) {
|
||
return {
|
||
row: pos.line,
|
||
column: pos.ch
|
||
};
|
||
}
|
||
return pos;
|
||
}
|
||
// 查找文档
|
||
function findDoc(ts, doc, name) {
|
||
for (var n in ts.docs) {
|
||
var cur = ts.docs[n];
|
||
// if (cur.doc == doc) return cur;
|
||
if (cur.doc?.id == doc?.session?.id) {
|
||
return cur;
|
||
}
|
||
}
|
||
// 没有名称则默认生成名称 doc,doc1,doc2...
|
||
if (!name) {
|
||
for (var i = 0; ; ++i) {
|
||
n = "[页面" + (i || "") + "]";
|
||
if (!ts.docs[n]) {
|
||
name = n;
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
return ts.addDoc(name, doc);
|
||
}
|
||
// 文件修改
|
||
function trackChange(ts, doc, change) {
|
||
var _change = {};
|
||
_change.from = toTernLoc(change.start);
|
||
_change.to = toTernLoc(change.end);
|
||
_change.text = change.lines;
|
||
|
||
var data = findDoc(ts, doc);
|
||
var argHints = ts.cachedArgHints;
|
||
|
||
if (argHints && argHints.doc == doc && cmpPos(argHints.start, _change.to) <= 0) {
|
||
ts.cachedArgHints = null;
|
||
}
|
||
|
||
var changed = data.changed; //data is the tern server doc, which keeps a changed property, which is null here
|
||
if (changed === null) {
|
||
data.changed = changed = {
|
||
from: _change.from.line,
|
||
to: _change.from.line
|
||
};
|
||
}
|
||
|
||
var end = _change.from.line + (_change.text.length - 1);
|
||
if (changed.to && _change.from.line < changed.to) {
|
||
changed.to = changed.to - (_change.to.line - end);
|
||
}
|
||
if (changed?.to && end >= changed.to) {
|
||
changed.to = end + 1;
|
||
}
|
||
if (changed?.from && changed.from > _change.from.line) {
|
||
changed.from = changed.from.line;
|
||
}
|
||
}
|
||
// 生成请求
|
||
function buildRequest(ts, doc, query, pos, forcePushChangedfile) {
|
||
var files = [],
|
||
offsetLines = 0,
|
||
allowFragments = !query.fullDocs;
|
||
if (!allowFragments) delete query.fullDocs;
|
||
if (typeof query == "string") query = {
|
||
type: query
|
||
};
|
||
query.lineCharPositions = true;
|
||
if (query.end == null) {
|
||
try {
|
||
query.end = toTernLoc(pos || (doc.doc.selection.getCursor ? doc.doc.selection.getCursor() : doc.selection.getCursor()));
|
||
} catch (error) {
|
||
query.end = toTernLoc(0);
|
||
}
|
||
}
|
||
var startPos = query.start || query.end;
|
||
|
||
if (!forcePushChangedfile && doc.doc && doc.doc.session && doc.doc.session.getLength() > bigDoc && allowFragments !== false &&
|
||
doc.changed.to - doc.changed.from < 100 &&
|
||
doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
|
||
files.push(getFragmentAround(doc, startPos, query.end));
|
||
query.file = "#0";
|
||
var offsetLines = files[0].offsetLines;
|
||
if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
|
||
query.end = Pos(query.end.line - offsetLines, query.end.ch);
|
||
} else {
|
||
if(!doc.name){
|
||
doc.name = "[页面1]"
|
||
}
|
||
files.push({
|
||
type: "full",
|
||
name: doc.name,
|
||
text: docValue(ts, doc, query)
|
||
});
|
||
query.file = doc.name;
|
||
doc.changed = null;
|
||
}
|
||
for (var name in ts.docs) {
|
||
var cur = ts.docs[name];
|
||
if (cur.changed && cur != doc) {
|
||
files.push({
|
||
type: "full",
|
||
name: cur.name,
|
||
text: docValue(ts, cur, query)
|
||
});
|
||
cur.changed = null;
|
||
}
|
||
};
|
||
if (query.prefix && query.type == "completions" && /\./g.test(query.prefix)) {
|
||
if ((SnippetManager.getActiveScopes(EDITOR) || ["html"])[0] == "html") {
|
||
files[0].text += `\n${query.prefix}`;
|
||
query.end = {
|
||
line: files[0].text.split('\n').length - 1,
|
||
ch: query.prefix.length
|
||
}
|
||
}
|
||
|
||
};
|
||
return {
|
||
query: query,
|
||
files: files
|
||
};
|
||
}
|
||
|
||
function getFragmentAround(data, start, end) {
|
||
var editor = data.doc;
|
||
var minIndent = null,
|
||
minLine = null,
|
||
endLine,
|
||
tabSize = editor.session.$tabSize;
|
||
for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
|
||
var line = editor.session.getLine(p),
|
||
fn = line.search(/\bfunction\b/);
|
||
if (fn < 0) continue;
|
||
var indent = countColumn(line, null, tabSize);
|
||
if (minIndent != null && minIndent <= indent) continue;
|
||
minIndent = indent;
|
||
minLine = p;
|
||
}
|
||
if (minLine == null) minLine = min;
|
||
var max = Math.min(editor.session.getLength() - 1, end.line + 20);
|
||
if (minIndent == null || minIndent == countColumn(editor.session.getLine(start.line), null, tabSize)) endLine = max;
|
||
else
|
||
for (endLine = end.line + 1; endLine < max; ++endLine) {
|
||
var indent = countColumn(editor.session.getLine(endLine), null, tabSize);
|
||
if (indent <= minIndent) break;
|
||
}
|
||
var from = Pos(minLine, 0);
|
||
|
||
return {
|
||
type: "part",
|
||
name: data.name,
|
||
offsetLines: from.line,
|
||
text: editor.session.getTextRange({
|
||
start: toAceLoc(from),
|
||
end: toAceLoc(Pos(endLine, 0))
|
||
})
|
||
};
|
||
}
|
||
|
||
function countColumn(string, end, tabSize, startIndex, startValue) {
|
||
if (end == null) {
|
||
end = string.search(/[^\s\u00a0]/);
|
||
if (end == -1) end = string.length;
|
||
}
|
||
for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
|
||
if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
|
||
else ++n;
|
||
}
|
||
return n;
|
||
}
|
||
// 获取提示器
|
||
function getCompletions(ts, editor, session, pos, prefix, callback) {
|
||
var autoCompleteFiredTwiceInThreshold = function () {
|
||
try {
|
||
var t = ts.lastAutoCompleteFireTime;
|
||
if (!t) {
|
||
return false;
|
||
}
|
||
var msPassed = new Date().getTime() - t;
|
||
if (msPassed < 1000) { //less than 1 second
|
||
return true;
|
||
}
|
||
} catch (ex) {
|
||
showError({
|
||
msg: 'autoCompleteFiredTwiceInThreshold',
|
||
err: ex
|
||
});
|
||
}
|
||
return false;
|
||
};
|
||
var forceEnableAceTextCompletor = autoCompleteFiredTwiceInThreshold();
|
||
if (!forceEnableAceTextCompletor) {
|
||
var t = getCurrentToken(editor);
|
||
if (t && t.type && t.type.indexOf('comment') !== -1) forceEnableAceTextCompletor = true;
|
||
}
|
||
var groupName = '';
|
||
if (debugCompletions) {
|
||
groupName = Math.random().toString(36).slice(2);
|
||
//console.group(groupName);
|
||
//console.time('get completions from tern server');
|
||
}
|
||
// 请求分析提示对应代码
|
||
ts.request(editor, {
|
||
type: "completions",
|
||
types: true,
|
||
urls: true,
|
||
origins: true,
|
||
docs: true,
|
||
filter: false,
|
||
omitObjectPrototype: false, // 隐藏prototype
|
||
sort: false,
|
||
includeKeywords: true,
|
||
guess: true,
|
||
expandWordForward: true,
|
||
prefix: prefix,
|
||
pos: pos
|
||
},
|
||
function (error, data) {
|
||
if (debugCompletions) console.timeEnd('get completions from tern server');
|
||
if (error) {
|
||
callback(error, data)
|
||
return showError(ts, editor, error);
|
||
}
|
||
callback(null, data.completions.map(function (item) {
|
||
// let isClassName = class_code.includes(item.name);
|
||
// let isClassName_other = false;
|
||
// if (item.class) {
|
||
// isClassName_other = class_code.includes(item.class)
|
||
// }
|
||
let caption = item.name;
|
||
if (/[\u4E00-\u9FA5]/g.test(caption)) {
|
||
caption = pinyin_transliteration(item.name);
|
||
if (item.translated) {
|
||
caption += "|" + item.translated
|
||
}
|
||
}
|
||
if (item.end && pos.row === item.end.line && pos.column === item.end.ch) {
|
||
return null;
|
||
}
|
||
let value_suffix = '';
|
||
if (/^fn\(/.test(item.type) /*&& !isClassName&& !isClassName_other*/) {
|
||
value_suffix = "()"
|
||
};
|
||
return {
|
||
//iconClass: " " + (item.guess ? cls + "guess" : typeToIcon(item.type)), // 图标
|
||
iconClass: " " + (item.guess ? typeToIcon("?") : typeToIcon(item.type)),
|
||
description: item.doc, // 解释
|
||
type: (item.type === "?") ? item.name : item.type, // 类型
|
||
caption: caption, // 筛选名称
|
||
title: item.name,
|
||
title_en: item.translated,
|
||
explain: item.explain,
|
||
parameter: item.parameter,
|
||
value: item.value || item.name + value_suffix, // 值
|
||
score: 99999, // 排序
|
||
page_id: item.origin, // 页面id
|
||
overall: item.overall || false,
|
||
meta: item.origin ? item.origin.replace(/^.*[\\\/]/, '') : '', // 提示类型
|
||
other: item.origin ? 1 : 0,
|
||
pt: item.pt || ""
|
||
};
|
||
}));
|
||
if (debugCompletions) console.time('get and merge other completions');
|
||
});
|
||
}
|
||
// 生成类型图标
|
||
function typeToIcon(type) {
|
||
var suffix;
|
||
if (type == "?") suffix = "unknown";
|
||
else if (type == "number" || type == "string" || type == "bool") suffix = type;
|
||
else if (/^fn\(/.test(type)) suffix = "fn";
|
||
else if (/^\[/.test(type)) suffix = "array";
|
||
else suffix = "object";
|
||
return cls + "completion " + cls + "completion-" + suffix;
|
||
};
|
||
var popupSelectBound = false;
|
||
// 转真实类型
|
||
function stringToType(type) {
|
||
var suffix;
|
||
if (type == "?") suffix = "未知";
|
||
else if (type == "number" || type == "string" || type == "bool") suffix = type;
|
||
else if (/^fn\(/.test(type)) suffix = "function";
|
||
else if (/^\[/.test(type)) suffix = "array";
|
||
else suffix = "object";
|
||
return suffix;
|
||
}
|
||
// 弹出对应的提示
|
||
var debounce_updateArgHints; // 防抖
|
||
function updateArgHints(ts, editor) {
|
||
clearTimeout(debounce_updateArgHints);
|
||
var callPos = getCallPos(editor);
|
||
if (!callPos) {
|
||
return;
|
||
}
|
||
var start = callPos.start;
|
||
var argpos = callPos.argpos;
|
||
var cache = ts.cachedArgHints;
|
||
if (cache && cache.doc == editor && cmpPos(start, cache.start) === 0) {
|
||
if (window.isCloseArgHints) {
|
||
return;
|
||
}
|
||
var line = editor.session.getLine(cache.start["line"]);
|
||
var name = "";
|
||
for (let index = cache.start["ch"] - 1; index >= 0; index--) {
|
||
if (!/\s/.test(line[index])) {
|
||
name = line[index] + name;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
if (name === cache.name) {
|
||
if (cache?.argpos == argpos) {
|
||
return;
|
||
}
|
||
return showArgHints(ts, editor, argpos);
|
||
}
|
||
} else {
|
||
window.isCloseArgHints = false;
|
||
};
|
||
debounce_updateArgHints = setTimeout(inner, 300);
|
||
function inner() {
|
||
ts.request(editor, {
|
||
type: "type",
|
||
types: true,
|
||
origins: true,
|
||
docs: true,
|
||
urls: true,
|
||
end: start,
|
||
preferFunction: true,
|
||
}, function (error, data) {
|
||
if (error) {
|
||
if (error.toString().toLowerCase().indexOf('no expression at') === -1 && error.toString().toLowerCase().indexOf('no type found at') === -1) {
|
||
return showError(editor, error);
|
||
}
|
||
}
|
||
if (error || !data.type || !(/^fn\(/).test(data.type)) {
|
||
return;
|
||
};
|
||
if (cache && [data?.exprName, data?.name].includes(cache.name)) {
|
||
if (cache?.argpos == argpos) {
|
||
return;
|
||
}
|
||
showArgHints(ts, editor, argpos);
|
||
return;
|
||
}
|
||
let view = null;
|
||
if (ExeFun_SelectJSTextIf(data.name)) {
|
||
view = JSON.parse(ExeFun_SelectJSTextData(data.name))
|
||
} else {
|
||
let lib_data = tree.getData("librarys", data.exprName || data.name)
|
||
if (lib_data.length > 0) {
|
||
let 参数信息 = [];
|
||
if (lib_data[0]["parameter"].length > 0) {
|
||
lib_data[0]["parameter"].forEach(参数 => {
|
||
参数信息.push({
|
||
"内部解释": "是否可空【1代表可空,2代表必须输入】-是否扩展参数【1代表可扩展参数,2代表不可扩展参数】",
|
||
"参数类型": 参数["Class"],
|
||
"名称": 参数["title"],
|
||
"描述": 参数["usage"],
|
||
"排序": 参数["int"]
|
||
})
|
||
})
|
||
}
|
||
view = {
|
||
"type": "9",
|
||
"三方命令": 1,
|
||
"中文名称": lib_data[0]["title"],
|
||
"中文命令示例": lib_data[0]["body"],
|
||
"内部注意信息": "浏览器:1支持,0不支持",
|
||
"参数信息": JSON.stringify(参数信息),
|
||
"命令类型": lib_data[0]["CodeType"],
|
||
"描述信息": `<p>${lib_data[0]["usage"]}</p>`,
|
||
"返回值描述": lib_data[0]["ReturnText"],
|
||
"返回值类型": lib_data[0]["ReturnClass"]
|
||
}
|
||
}
|
||
}
|
||
ts.cachedArgHints = {
|
||
start: start,
|
||
type: parseFnType(data.type),
|
||
name: data.exprName || data.name || "fn",
|
||
guess: data.guess,
|
||
doc: editor,
|
||
comments: data.doc,
|
||
view: view,
|
||
argpos: argpos
|
||
};
|
||
showArgHints(ts, editor, argpos);
|
||
});
|
||
}
|
||
}
|
||
|
||
function parseFnType(text) {
|
||
if (text.substring(0, 2) !== 'fn') return null;
|
||
if (text.indexOf('(') === -1) return null;
|
||
|
||
var args = [],
|
||
pos = 3;
|
||
|
||
function skipMatching(upto) {
|
||
var depth = 0,
|
||
start = pos;
|
||
for (; ;) {
|
||
var next = text.charAt(pos);
|
||
if (upto.test(next) && !depth) return text.slice(start, pos);
|
||
if (/[{\[\(]/.test(next)) ++depth;
|
||
else if (/[}\]\)]/.test(next)) --depth;
|
||
++pos;
|
||
}
|
||
}
|
||
if (text.charAt(pos) != ")")
|
||
for (; ;) {
|
||
var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
|
||
if (name) {
|
||
pos += name[0].length;
|
||
name = name[1];
|
||
}
|
||
args.push({
|
||
name: name,
|
||
type: skipMatching(/[\),]/)
|
||
});
|
||
if (text.charAt(pos) == ")") break;
|
||
pos += 2;
|
||
}
|
||
|
||
var rettype = text.slice(pos).match(/^\) -> (.*)$/);
|
||
return {
|
||
args: args,
|
||
rettype: rettype && rettype[1]
|
||
};
|
||
}
|
||
|
||
function getCurrentToken(editor) {
|
||
try {
|
||
var pos = editor.getSelectionRange().end;
|
||
return editor.session.getTokenAt(pos.row, pos.column);
|
||
} catch (ex) {
|
||
showError(ts, editor, ex);
|
||
}
|
||
}
|
||
// 获取调用位置
|
||
function getCallPos(editor, pos) {
|
||
var start = {};
|
||
var currentPosistion = pos || editor.getSelectionRange().start; //{row,column}
|
||
currentPosistion = toAceLoc(currentPosistion); //just in case
|
||
var currentLine = currentPosistion.row;
|
||
var currentCol = currentPosistion.column;
|
||
// var firstLineToCheck = Math.max(0, currentLine - 6);
|
||
var firstLineToCheck = 0;
|
||
var ch = '';
|
||
var depth = 0;
|
||
var commas = [];
|
||
for (var row = currentLine; row >= firstLineToCheck; row--) {
|
||
var thisRow = editor.session.getLine(row);
|
||
if (row === currentLine) {
|
||
thisRow = thisRow.substr(0, currentCol);
|
||
}
|
||
for (var col = thisRow.length; col >= 0; col--) {
|
||
ch = thisRow.substr(col, 1);
|
||
if (ch === '}' || ch === ')' || ch === ']') {
|
||
depth += 1;
|
||
} else if (ch === '{' || ch === '(' || ch === '[') {
|
||
if (depth > 0) {
|
||
depth -= 1;
|
||
} else if (ch === '(') {
|
||
var upToParen = thisRow.substr(0, col);
|
||
if (!upToParen.length) {
|
||
break;
|
||
}
|
||
if (upToParen.substr(upToParen.length - 1) === ' ') {
|
||
break;
|
||
}
|
||
var wordBeforeFnName = upToParen.split(' ').reverse()[1];
|
||
if (wordBeforeFnName && wordBeforeFnName.toLowerCase() === 'function') {
|
||
break;
|
||
}
|
||
if (wordBeforeFnName && wordBeforeFnName.toLowerCase() === '定义函数') {
|
||
break;
|
||
}
|
||
var token = editor.session.getTokenAt(row, col);
|
||
if (token) {
|
||
if (token.type.toString().indexOf('comment') !== -1 || token.type === 'keyword' || token.type === 'storage.type') {
|
||
break;
|
||
}
|
||
}
|
||
start = {
|
||
line: row,
|
||
ch: col
|
||
};
|
||
break;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (start.hasOwnProperty('line')) {
|
||
break;
|
||
}
|
||
}
|
||
if (!start.hasOwnProperty('line')) return; //start not found
|
||
depth = 0;
|
||
for (var row = start["line"]; row <= currentLine; row++) {
|
||
var thisRow = editor.session.getLine(row);
|
||
if (row === currentLine) {
|
||
thisRow = thisRow.substr(0, currentCol);
|
||
}
|
||
for (var col = 0; col <= thisRow.length; col++) {
|
||
ch = thisRow.substr(col, 1);
|
||
if (ch === '}' || ch === ')' || ch === ']') {
|
||
depth -= 1;
|
||
} else if (ch === '{' || ch === '(' || ch === '[') {
|
||
depth += 1;
|
||
|
||
} else if (ch === ',' && depth === 1) {
|
||
commas.push({
|
||
line: row,
|
||
ch: col
|
||
});
|
||
}
|
||
}
|
||
}
|
||
var argpos = 0;
|
||
for (var i = 0; i < commas.length; i++) {
|
||
var p = commas[i];
|
||
if ((p.line === start.line && p.ch > start.ch) || (p.line > start.line)) {
|
||
argpos += 1;
|
||
}
|
||
}
|
||
|
||
return {
|
||
start: toTernLoc(start),
|
||
"argpos": argpos
|
||
};
|
||
}
|
||
|
||
function cmpPos(a, b) {
|
||
a = toTernLoc(a);
|
||
b = toTernLoc(b);
|
||
return a.line - b.line || a.ch - b.ch;
|
||
}
|
||
|
||
function showArgHints(ts, editor, pos) {
|
||
var cache = ts.cachedArgHints;
|
||
// var place = getCusorPosForTooltip(editor);
|
||
if (!cache.hasOwnProperty('params')) {
|
||
if (!cache.comments) {
|
||
cache.params = null;
|
||
} else {
|
||
var params = parseJsDocParams(cache.comments);
|
||
if (!params || params.length === 0) {
|
||
cache.params = null;
|
||
} else {
|
||
cache.params = params;
|
||
}
|
||
}
|
||
};
|
||
var data = {
|
||
name: cache.name,
|
||
guess: cache.guess,
|
||
fnArgs: cache.type,
|
||
doc: cache.comments,
|
||
params: cache.params,
|
||
view: cache.view
|
||
};
|
||
var tip = createInfoDataTip(data, true, pos);
|
||
// ts.activeArgHints = makeTooltip(place.left, place.top, tip, editor, true);
|
||
// $(".ace_active_tooltip").remove();
|
||
var node = elt("div", cls + "tooltip ace_doc-tooltip ace_active_tooltip", tip);
|
||
document.querySelector("#funtipText").innerHTML = node.innerHTML;
|
||
if (cache.view && openIDETooltipType == "右侧") {
|
||
MenuTool.viewDoc(true, cache.view, true);
|
||
return
|
||
};
|
||
return;
|
||
}
|
||
// function showArgHints(ts, editor, pos) {
|
||
// var cache = ts.cachedArgHints;
|
||
// var place = getCusorPosForTooltip(editor);
|
||
// if (!cache.hasOwnProperty('params')) {
|
||
// if (!cache.comments) {
|
||
// cache.params = null;
|
||
// } else {
|
||
// var params = parseJsDocParams(cache.comments);
|
||
// if (!params || params.length === 0) {
|
||
// cache.params = null;
|
||
// } else {
|
||
// cache.params = params;
|
||
// }
|
||
// }
|
||
// };
|
||
// var data = {
|
||
// name: cache.name,
|
||
// guess: cache.guess,
|
||
// fnArgs: cache.type,
|
||
// doc: cache.comments,
|
||
// params: cache.params,
|
||
// view: cache.view
|
||
// };
|
||
// var tip = createInfoDataTip(data, true, pos);
|
||
// ts.activeArgHints = makeTooltip(place.left, place.top, tip, editor, true);
|
||
// if (cache.view && openIDETooltipType == "右侧") {
|
||
// MenuTool.viewDoc(true, cache.view, true);
|
||
// return
|
||
// };
|
||
// return;
|
||
// }
|
||
|
||
function makeTooltip(x, y, content, editor, closeOnCusorActivity, fadeOutDuration) {
|
||
var rect;
|
||
if (x === null || y === null) {
|
||
var location = getCusorPosForTooltip(editor);
|
||
x = location.left;
|
||
y = location.top;
|
||
rect = location.rect;
|
||
}
|
||
// $(".ace_active_tooltip").remove();
|
||
var node = elt("div", cls + "tooltip ace_doc-tooltip ace_active_tooltip", content);
|
||
node.style.left = x + "px";
|
||
node.style.top = y + "px";
|
||
node.style.width = "500px";
|
||
node.style.minHeight = "auto";
|
||
node.style.maxHeight = "300px";
|
||
node.style.overflow = "hidden";
|
||
node.style.overflowY = "auto";
|
||
node.style.opacity = 0;
|
||
node.style.transition = "none";
|
||
if (is_openIDETooltip && openIDETooltipType == "右侧") {
|
||
document.querySelector("#funtipText").innerHTML = node.innerHTML;
|
||
} else {
|
||
document.body.appendChild(node);
|
||
}
|
||
var _height = node.clientHeight;
|
||
var place = editor.renderer.$cursorLayer.getPixelPosition();
|
||
if (!rect) {
|
||
rect = editor.container.getBoundingClientRect();
|
||
}
|
||
// rect.height += $(".layui-tab2").height();
|
||
if (rect.height - editor.renderer.$cursorLayer.cursors[0].offsetTop - 30 < _height) {
|
||
node.style.transform = `translateY(-${parseInt(_height) + 60}px)`;
|
||
}
|
||
node.style.opacity = 1;
|
||
if (closeOnCusorActivity === true) {
|
||
if (!editor) {
|
||
throw Error('tern.makeTooltip called with closeOnCursorActivity=true but editor was not passed. Need to pass editor!');
|
||
}
|
||
var closeThisTip = function () {
|
||
setTimeout(() => {
|
||
if (!node.parentNode) return; //not sure what this is for, its from CM
|
||
remove(node);
|
||
editor.getSession().selection.off('changeCursor', closeThisTip);
|
||
editor.getSession().off('changeScrollTop', closeThisTip);
|
||
editor.getSession().off('changeScrollLeft', closeThisTip);
|
||
}, 100)
|
||
};
|
||
editor.getSession().selection.on('changeCursor', closeThisTip);
|
||
editor.getSession().on('changeScrollTop', closeThisTip);
|
||
editor.getSession().on('changeScrollLeft', closeThisTip);
|
||
}
|
||
|
||
if (fadeOutDuration) {
|
||
fadeOutDuration = parseInt(fadeOutDuration, 10);
|
||
if (fadeOutDuration > 100) {
|
||
var fadeThistip = function () {
|
||
if (!node.parentNode) return; //not sure what this is for, its from CM
|
||
fadeOut(node, fadeOutDuration);
|
||
try {
|
||
editor.getSession().selection.off('changeCursor', closeThisTip);
|
||
editor.getSession().off('changeScrollTop', closeThisTip);
|
||
editor.getSession().off('changeScrollLeft', closeThisTip);
|
||
} catch (ex) { }
|
||
};
|
||
setTimeout(fadeThistip, fadeOutDuration);
|
||
}
|
||
}
|
||
|
||
return node;
|
||
}
|
||
|
||
function getCusorPosForTooltip(editor) {
|
||
var renderer = editor.renderer;
|
||
var place = renderer.$cursorLayer.getPixelPosition();
|
||
var rect = editor.container.getBoundingClientRect();
|
||
place.top = editor.renderer.$cursorLayer.cursors[0].offsetTop;
|
||
place.top += rect.top;
|
||
place.left += rect.left - editor.renderer.scrollLeft;
|
||
place.left += renderer.gutterWidth
|
||
if (rect.width - (place.left - rect.left) - 45 < 500) {
|
||
place.left = rect.width + rect.left - 500 - 45
|
||
}
|
||
return {
|
||
left: place.left,
|
||
top: place.top + 30,
|
||
rect: rect
|
||
};
|
||
}
|
||
|
||
function moveTooltip(tip, x, y, editor) {
|
||
if (x === null || y === null) {
|
||
var location = getCusorPosForTooltip(editor);
|
||
x = location.left;
|
||
y = location.top;
|
||
}
|
||
tip.style.left = x + "px";
|
||
tip.style.top = y + "px";
|
||
}
|
||
|
||
function remove(node) {
|
||
var p = node && node.parentNode;
|
||
if (p) p.removeChild(node);
|
||
}
|
||
|
||
function fadeOut(tooltip, timeout) {
|
||
if (!timeout) {
|
||
timeout = 1100;
|
||
}
|
||
if (timeout === -1) {
|
||
remove(tooltip);
|
||
return;
|
||
}
|
||
tooltip.style.opacity = "0";
|
||
setTimeout(function () {
|
||
remove(tooltip);
|
||
}, timeout);
|
||
}
|
||
// 查询参数解释
|
||
function parseJsDocParams(str) {
|
||
if (!str) return [];
|
||
str = str.replace(/@param/gi, '@参数');
|
||
var params = [];
|
||
while (str.indexOf('@参数') !== -1) {
|
||
str = str.substring(str.indexOf('@参数') + 3);
|
||
var nextTagStart = str.indexOf('@');
|
||
|
||
var paramStr = nextTagStart === -1 ? str : str.substr(0, nextTagStart);
|
||
var thisParam = {
|
||
name: "",
|
||
parentName: "",
|
||
type: "",
|
||
description: "",
|
||
optional: false,
|
||
defaultValue: ""
|
||
};
|
||
var re = /\s{[^}]{1,50}}\s/;
|
||
var m;
|
||
while ((m = re.exec(paramStr)) !== null) {
|
||
if (m.index === re.lastIndex) {
|
||
re.lastIndex++;
|
||
}
|
||
thisParam.type = m[0];
|
||
paramStr = paramStr.replace(thisParam.type, '').trim();
|
||
thisParam.type = thisParam.type.replace('{', '').replace('}', '').replace(' ', '').trim();
|
||
}
|
||
paramStr = paramStr.trim();
|
||
if (paramStr.substr(0, 1) === '[') {
|
||
thisParam.optional = true;
|
||
var endBracketIdx = paramStr.indexOf(']');
|
||
if (endBracketIdx === -1) {
|
||
showError('failed to parse parameter name; Found starting \'[\' but missing closing \']\'');
|
||
continue;
|
||
}
|
||
var nameStr = paramStr.substring(0, endBracketIdx + 1);
|
||
paramStr = paramStr.replace(nameStr, '').trim();
|
||
nameStr = nameStr.replace('[', '').replace(']', '');
|
||
if (nameStr.indexOf('=') !== -1) {
|
||
var defaultValue = nameStr.substr(nameStr.indexOf('=') + 1);
|
||
if (defaultValue.trim() === '') {
|
||
thisParam.defaultValue = "undefined";
|
||
} else {
|
||
thisParam.defaultValue = defaultValue.trim();
|
||
}
|
||
thisParam.name = nameStr.substring(0, nameStr.indexOf('=')).trim();
|
||
} else {
|
||
thisParam.name = nameStr.trim();
|
||
}
|
||
} else {
|
||
var nextSpace = paramStr.indexOf(' ');
|
||
if (nextSpace !== -1) {
|
||
thisParam.name = paramStr.substr(0, nextSpace);
|
||
paramStr = paramStr.substr(nextSpace).trim();
|
||
} else {
|
||
thisParam.name = paramStr;
|
||
paramStr = '';
|
||
}
|
||
}
|
||
var nameDotIdx = thisParam.name.indexOf('.');
|
||
if (nameDotIdx !== -1) {
|
||
thisParam.parentName = thisParam.name.substring(0, nameDotIdx);
|
||
thisParam.name = thisParam.name.substring(nameDotIdx + 1);
|
||
}
|
||
paramStr = paramStr.trim();
|
||
if (paramStr.length > 0) {
|
||
thisParam.description = paramStr.replace('-', '').trim();
|
||
}
|
||
thisParam.name = lang.escapeHTML(thisParam.name);
|
||
thisParam.parentName = lang.escapeHTML(thisParam.parentName);
|
||
thisParam.description = lang.escapeHTML(thisParam.description);
|
||
thisParam.type = lang.escapeHTML(thisParam.type);
|
||
thisParam.defaultValue = lang.escapeHTML(thisParam.defaultValue);
|
||
params.push(thisParam);
|
||
}
|
||
return params;
|
||
}
|
||
// 制作虚拟节点
|
||
function elFromString(s) {
|
||
var frag = document.createDocumentFragment(),
|
||
temp = document.createElement('span');
|
||
temp.innerHTML = s;
|
||
while (temp.firstChild) {
|
||
frag.appendChild(temp.firstChild);
|
||
}
|
||
return frag;
|
||
}
|
||
// 制作html标签
|
||
function elt(tagname, cls /*, ... elts*/) {
|
||
var e = document.createElement(tagname);
|
||
if (cls) e.className = cls;
|
||
for (var i = 2; i < arguments.length; ++i) {
|
||
var elt = arguments[i];
|
||
if (typeof elt == "string") elt = document.createTextNode(elt);
|
||
e.appendChild(elt);
|
||
}
|
||
return e;
|
||
}
|
||
// 制作函数提示弹出
|
||
function createInfoDataTip(data, includeType, activeArg) {
|
||
var tip = elt("span", null);
|
||
tip.style = {
|
||
"padding": "10px"
|
||
};
|
||
var d = data.doc;
|
||
var params = data.params || parseJsDocParams(d); //parse params
|
||
var data_arr = []
|
||
var activeData = [];
|
||
if (data.view) {
|
||
try {
|
||
data_arr = JSON.parse(data.view["参数信息"]);
|
||
activeData = data_arr[activeArg];
|
||
} catch (error) { }
|
||
}
|
||
if (includeType) {
|
||
var fnArgs = data.fnArgs ? data.fnArgs : data.type ? parseFnType(data.type) : null; //will be null if parseFnType detects that this is not a function
|
||
if (fnArgs) {
|
||
var getParam = function (arg, getChildren) {
|
||
if (params === null) return null;
|
||
if (!arg.name) return null;
|
||
var children = [];
|
||
for (var i = 0; i < params.length; i++) {
|
||
if (getChildren === true) {
|
||
if (params[i].parentName.toLowerCase().trim() === arg.name.toLowerCase().trim()) {
|
||
children.push(params[i]);
|
||
}
|
||
} else {
|
||
if (params[i].name.toLowerCase().trim() === arg.name.toLowerCase().trim()) {
|
||
return params[i];
|
||
}
|
||
}
|
||
}
|
||
if (getChildren === true) return children;
|
||
return null;
|
||
};
|
||
var getParamDetailedName = function (param) {
|
||
var name = param.name;
|
||
if (param.optional === true) {
|
||
if (param.defaultValue) {
|
||
name = "[" + name + " = " + param.defaultValue + "]";
|
||
} else {
|
||
name = "[" + name + "]";
|
||
}
|
||
}
|
||
return name;
|
||
};
|
||
var useDetailedArgHints = params.length === 0 || !isNaN(parseInt(activeArg));
|
||
var typeStr = '';
|
||
typeStr += lang.escapeHTML(data.exprName || data.name || "fn");
|
||
typeStr += "(";
|
||
var activeParam = null,
|
||
activeParamChildren = []; //one ore more child params for multiple object properties
|
||
|
||
for (var i = 0; i < fnArgs.args.length; i++) {
|
||
var paramStr = '';
|
||
var isCurrent = !isNaN(parseInt(activeArg)) ? i === activeArg : false;
|
||
var arg = fnArgs.args[i]; //name,type
|
||
var name = arg.name || "?";
|
||
if (name.length > 1 && name.substr(name.length - 1) === '?') {
|
||
name = name.substr(0, name.length - 1);
|
||
arg.name = name; //update the arg var with proper name for use below
|
||
}
|
||
if (!useDetailedArgHints) {
|
||
paramStr += lang.escapeHTML(name);
|
||
} else {
|
||
var param = getParam(arg, false);
|
||
var children = getParam(arg, true);
|
||
var type = arg.type;
|
||
var optional = false;
|
||
var defaultValue = '';
|
||
if (param !== null) {
|
||
name = param.name;
|
||
if (param.type) {
|
||
type = param.type;
|
||
}
|
||
if (isCurrent) {
|
||
activeParam = param;
|
||
}
|
||
optional = param.optional;
|
||
defaultValue = param.defaultValue.trim();
|
||
}
|
||
if (children && children.length > 0) {
|
||
if (isCurrent) {
|
||
activeParamChildren = children;
|
||
}
|
||
type = "{";
|
||
for (var c = 0; c < children.length; c++) {
|
||
type += children[c].name;
|
||
if (c + 1 !== children.length && children.length > 1) type += ", ";
|
||
}
|
||
type += "}";
|
||
}
|
||
paramStr += '<span class="' + cls + (isCurrent ? "farg-current" : "farg") + '">' + (lang.escapeHTML(name) || "?") + '</span>';
|
||
if (data_arr[i]) {
|
||
paramStr += type ? ':<span class="' + cls + 'type">' + data_arr[i]["参数类型"] + '</span> ' : '';
|
||
} else {
|
||
paramStr += type ? ':<span class="' + cls + 'type">' + (typeTips[lang.escapeHTML(type)] || lang.escapeHTML(type)) + '</span> ' : '';
|
||
}
|
||
if (defaultValue !== '') {
|
||
paramStr += '<span class="' + cls + 'jsdoc-param-defaultValue">= ' + lang.escapeHTML(defaultValue) + '</span>';
|
||
}
|
||
if (optional) {
|
||
paramStr = '<span class="' + cls + 'jsdoc-param-optionalWrapper">' + '<span class="' + cls + 'farg-optionalBracket">[</span>' + paramStr + '<span class="' + cls + 'jsdoc-param-optionalBracket">]</span>' + '</span>';
|
||
}
|
||
}
|
||
if (i > 0) paramStr = ', ' + paramStr;
|
||
typeStr += paramStr;
|
||
}
|
||
|
||
typeStr += ")";
|
||
if (data.view && data.view["返回值类型"]) {
|
||
typeStr += ' -> ' + data.view["返回值类型"];
|
||
} else if (fnArgs.rettype) {
|
||
if (useDetailedArgHints) {
|
||
typeStr += ' -> <span class="' + cls + 'type">' + (typeTips[lang.escapeHTML(fnArgs.rettype)] || lang.escapeHTML(fnArgs.rettype)) + '</span>';
|
||
} else {
|
||
typeStr += ' -> ' + lang.escapeHTML(fnArgs.rettype);
|
||
}
|
||
}
|
||
typeStr = '<span class="' + cls + (useDetailedArgHints ? "typeHeader" : "typeHeader-simple") + '">' + typeStr + '</span>'; //outer wrapper
|
||
if (useDetailedArgHints) {
|
||
if (activeParam && activeParam.description) {
|
||
typeStr += '<div class="' + cls + 'farg-current-description"><span class="' + cls + 'farg-current-name">' + activeParam.name + ': </span>' + activeParam.description + '</div>';
|
||
}
|
||
if (activeParamChildren && activeParamChildren.length > 0) {
|
||
for (var i = 0; i < activeParamChildren.length; i++) {
|
||
var t = activeParamChildren[i].type ? '<span class="' + cls + 'type">{' + (typeTips[activeParamChildren[i].type] || activeParamChildren[i].type) + '} </span>' : '';
|
||
typeStr += '<div class="' + cls + 'farg-current-description">' + t + '<span class="' + cls + 'farg-current-name">' + getParamDetailedName(activeParamChildren[i]) + ': </span>' + activeParamChildren[i].description + '</div>';
|
||
}
|
||
}
|
||
}
|
||
if (data.view) {
|
||
try {
|
||
typeStr += `<hr><div>参数简介:${activeData["描述"].replace(/\r\n/g, "<br>")}</div>`;
|
||
} catch (error) { }
|
||
}
|
||
var closeBtn = elt("span", null);
|
||
closeBtn.className = "closeBtn";
|
||
$(closeBtn).css({
|
||
"position": "absolute",
|
||
"top": "5px",
|
||
"right": "5px",
|
||
"background": "red",
|
||
"borderRadius": "50px",
|
||
"cursor": "pointer",
|
||
"padding": "0 2px"
|
||
});
|
||
// $(closeBtn).on("click", function () {
|
||
// window.isCloseArgHints = true;
|
||
// $(".ace_active_tooltip").remove();
|
||
// });
|
||
closeBtn.appendChild(elFromString(`<i class="layui-icon layui-icon-close" style="color:#fff;font-size:12px"></i>`));
|
||
tip.appendChild(elFromString(typeStr));
|
||
tip.appendChild(closeBtn);
|
||
}
|
||
}
|
||
if (!data["view"]) {
|
||
if (data.doc) {
|
||
var replaceParams = function (str, params) {
|
||
if (params.length === 0) {
|
||
return str;
|
||
}
|
||
str = str.replace(/@param/gi, '@参数').replace(/@returns/gi, '@返回'); //make sure all param tags are lowercase
|
||
var beforeParams = str.substr(0, str.indexOf('@参数'));
|
||
while (str.indexOf('@参数') !== -1) {
|
||
str = str.substring(str.indexOf('@参数') + 6); //starting after first param match
|
||
}
|
||
if (str.indexOf('@') !== -1) {
|
||
str = str.substr(str.indexOf('@')); //start at next tag that is not a param
|
||
} else {
|
||
str = ''; //@param was likely the last tag, trim remaining as its likely the end of a param description
|
||
}
|
||
var paramStr = '';
|
||
for (var i = 0; i < params.length; i++) {
|
||
paramStr += '<div>';
|
||
if (params[i].parentName.trim() === '') {
|
||
paramStr += ' <span class="' + cls + 'jsdoc-tag">@参数</span> ';
|
||
} else {
|
||
paramStr += '<span class="' + cls + 'jsdoc-tag-param-child">|-</span> '; //dont show param tag for child param
|
||
}
|
||
paramStr += params[i].type.trim() === '' ? '' : '<span class="' + cls + 'type">{' + (typeTips[params[i].type] || params[i].type) + '}</span> ';
|
||
|
||
if (params[i].name.trim() !== '') {
|
||
var name = params[i].name.trim();
|
||
if (params[i].parentName.trim() !== '') {
|
||
name = params[i].parentName.trim() + '.' + name;
|
||
}
|
||
var pName = '<span class="' + cls + 'jsdoc-param-name">' + name + '</span>';
|
||
if (params[i].defaultValue.trim() !== '') {
|
||
pName += '<span class="' + cls + 'jsdoc-param-defaultValue"> = ' + params[i].defaultValue + '</span>';
|
||
}
|
||
if (params[i].optional) {
|
||
pName = '<span class="' + cls + 'jsdoc-param-optionalWrapper">' + '<span class="' + cls + 'farg-optionalBracket">[</span>' + pName + '<span class="' + cls + 'jsdoc-param-optionalBracket">]</span>' + '</span>';
|
||
}
|
||
paramStr += pName;
|
||
}
|
||
paramStr += params[i].description.trim() === '' ? '' : ' - <span class="' + cls + 'jsdoc-param-description">' + params[i].description + '</span>';
|
||
paramStr += '</div>';
|
||
}
|
||
if (paramStr !== '') {
|
||
str = '<span class="' + cls + 'jsdoc-param-wrapper">' + paramStr + '</span>' + str;
|
||
}
|
||
|
||
return beforeParams + str;
|
||
};
|
||
var highlighTags = function (str) {
|
||
try {
|
||
str = ' ' + str + ' '; //add white space for regex
|
||
var re = / ?@([\w\u4e00-\u9fa5]{1,50})\s ?/gi;
|
||
var m;
|
||
while ((m = re.exec(str)) !== null) {
|
||
if (m.index === re.lastIndex) {
|
||
re.lastIndex++;
|
||
}
|
||
str = str.replace(m[0], ' <span class="' + cls + 'jsdoc-tag">' + m[0].trim() + '</span> ');
|
||
}
|
||
} catch (ex) {
|
||
showError(ts, editor, ex);
|
||
}
|
||
return str.trim();
|
||
};
|
||
var highlightTypes = function (str) {
|
||
str = ' ' + str + ' '; //add white space for regex
|
||
try {
|
||
var re = /\s{[^}]{1,100}}\s/g;
|
||
var m;
|
||
while ((m = re.exec(str)) !== null) {
|
||
if (m.index === re.lastIndex) {
|
||
re.lastIndex++;
|
||
}
|
||
str = str.replace(m[0], ' <span class="' + cls + 'type">{' + (typeTips[m[0].trim().replace(/{|}/g, "")] || m[0].trim().replace(/{|}/g, "")) + '}</span> ');
|
||
}
|
||
} catch (ex) {
|
||
showError(ts, editor, ex);
|
||
}
|
||
return str.trim();
|
||
};
|
||
var createLinks = function (str) {
|
||
try {
|
||
var httpProto = 'HTTP_PROTO_PLACEHOLDER';
|
||
var httpsProto = 'HTTPS_PROTO_PLACEHOLDER';
|
||
var re = /\bhttps?:\/\/[^\s<>"`{}|\^\[\]\\]+/gi;
|
||
var m;
|
||
while ((m = re.exec(str)) !== null) {
|
||
if (m.index === re.lastIndex) {
|
||
re.lastIndex++;
|
||
}
|
||
var withoutProtocol = m[0].replace(/https/i, httpsProto).replace(/http/i, httpProto);
|
||
var text = m[0].replace(new RegExp('https://', 'i'), '').replace(new RegExp('http://', 'i'), '');
|
||
str = str.replace(m[0], '<a class="' + cls + 'tooltip-link" href="' + withoutProtocol + '" target="_blank">' + text + ' </a>');
|
||
}
|
||
str = str.replace(new RegExp(httpsProto, 'gi'), 'https').replace(new RegExp(httpProto, 'gi'), 'http');
|
||
} catch (ex) {
|
||
showError(ts, editor, ex);
|
||
}
|
||
return str;
|
||
};
|
||
|
||
if (d.substr(0, 1) === '*') {
|
||
d = d.substr(1); //tern leaves this for jsDoc as they start with /**, not exactly sure why...
|
||
}
|
||
d = lang.escapeHTML(d.trim());
|
||
d = replaceParams(d, params);
|
||
d = highlighTags(d);
|
||
d = highlightTypes(d);
|
||
d = createLinks(d);
|
||
tip.appendChild(elFromString(d));
|
||
}
|
||
if (data.url) {
|
||
tip.appendChild(document.createTextNode(" "));
|
||
var link = elt("a", null, "[docs]");
|
||
link.target = "_blank";
|
||
link.href = data.url;
|
||
tip.appendChild(link);
|
||
}
|
||
if (data.origin) {
|
||
tip.appendChild(elt("div", null, elt("em", null, "source: " + data.origin)));
|
||
}
|
||
}
|
||
return tip;
|
||
}
|
||
|
||
exports.tern_server = new TernServer();
|
||
});
|
||
(function () {
|
||
ace.require(["ace/ext/tern"], function (m) {
|
||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||
module.exports = m;
|
||
}
|
||
});
|
||
})(); |