-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodemirror-cloudjs.js
More file actions
190 lines (159 loc) · 5.88 KB
/
Copy pathcodemirror-cloudjs.js
File metadata and controls
190 lines (159 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
(function(mod) {
if (typeof exports == "object" && typeof module == "object")
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd)
define(["../../lib/codemirror"], mod);
else
mod(CodeMirror);
})(function(CodeMirror) {
var Pos = CodeMirror.Pos;
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
function customHint(editor, options) {
var config = (options && options.hintConfig) || window.__hintConfig;
if (!config) return;
var cur = editor.getCursor();
var token = editor.getTokenAt(cur);
if (/\b(?:string|comment)\b/.test(token.type)) return;
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
if (innerMode.mode.helperType === "json") return;
token.state = innerMode.state;
// Normalize token
if (!/^[\w$_]*$/.test(token.string)) {
token = { start: cur.ch, end: cur.ch, string: "", state: token.state,
type: token.string == "." ? "property" : null };
} else if (token.end > cur.ch) {
token.end = cur.ch;
token.string = token.string.slice(0, cur.ch - token.start);
}
var chain = parseChain(editor, cur, token);
var completions;
if (chain.length === 0) {
// No chain — top-level completions
completions = getTopLevel(config, token.state);
} else {
// Resolve chain to get available properties
var node = resolveChain(config, chain);
if (!node) return;
completions = getNodeCompletions(node, config);
}
// Filter by prefix
var prefix = token.string;
var list = [];
for (var i = 0; i < completions.length; i++) {
if (completions[i].lastIndexOf(prefix, 0) === 0 && list.indexOf(completions[i]) === -1) {
list.push(completions[i]);
}
}
return { list: list, from: Pos(cur.line, token.start), to: Pos(cur.line, token.end) };
}
// Walk backwards from the cursor token to build the chain
// Returns array of { name: string, called: boolean }
function parseChain(editor, cur, token) {
var chain = [];
var tprop = token;
while (tprop.type == "property") {
// Step back over the "."
tprop = editor.getTokenAt(Pos(cur.line, tprop.start));
if (tprop.string != ".") return chain;
// Step back to the identifier or ")"
tprop = editor.getTokenAt(Pos(cur.line, tprop.start));
if (tprop.string == ")") {
// Walk backwards to find matching "("
var depth = 1;
var pos = tprop.start;
while (depth > 0 && pos > 0) {
tprop = editor.getTokenAt(Pos(cur.line, pos));
if (tprop.string == ")") depth++;
else if (tprop.string == "(") depth--;
pos = tprop.start;
}
if (depth !== 0) return chain;
// Get the function name before "("
tprop = editor.getTokenAt(Pos(cur.line, tprop.start));
chain.push({ name: tprop.string, called: true });
} else {
chain.push({ name: tprop.string, called: false });
}
}
// The root identifier (variable type)
if (tprop.type && tprop.type.indexOf("variable") === 0 && chain.length === 0) {
// No chain was built — this means we're at a bare variable, not a property chain
return [];
}
chain.reverse();
return chain;
}
// Resolve a chain against the config
function resolveChain(config, chain) {
var root = chain[0];
// Look up root in custom, then global
var node = config.custom[root.name] || config.global[root.name];
if (!node) return null;
// If root was called as function: follow $returns
if (root.called) {
if (!node.$returns && !(node.props && node.props[root.name])) {
// Check if root itself is a function with $returns
var returns = node.$returns;
if (!returns) return null;
}
var builderName = node.$returns;
if (!builderName) return null;
var builderDef = config.builders[builderName];
if (!builderDef) return null;
node = { $type: 'object', props: builderDef };
}
// Walk remaining chain steps
for (var i = 1; i < chain.length; i++) {
var step = chain[i];
if (node.$type !== 'object') return null;
var child = node.props ? node.props[step.name] : null;
if (!child && node.$dynamic) {
child = node.$dynamic;
}
if (!child) return null;
if (step.called) {
if (!child.$returns) return null;
var bDef = config.builders[child.$returns];
if (!bDef) return null;
node = { $type: 'object', props: bDef };
} else {
node = child;
}
}
return node;
}
// Get completion strings from a resolved node
function getNodeCompletions(node, config) {
if (!node) return [];
if (node.$type === 'object' && node.props) {
return Object.keys(node.props);
}
return [];
}
// Get all top-level completions (no chain context)
function getTopLevel(config, state) {
var result = [];
// Local variables from CodeMirror's JS parser state
if (state) {
for (var v = state.localVars; v; v = v.next) result.push(v.name);
for (var c = state.context; c; c = c.prev)
for (var v = c.vars; v; v = v.next) result.push(v.name);
for (var v = state.globalVars; v; v = v.next) result.push(v.name);
}
// Custom objects
if (config.custom) {
for (var key in config.custom) result.push(key);
}
// Global objects
if (config.global) {
for (var key in config.global) result.push(key);
}
// JS keywords
for (var i = 0; i < javascriptKeywords.length; i++) {
result.push(javascriptKeywords[i]);
}
return result;
}
CodeMirror.registerHelper("hint", "cloudjs", customHint);
});