@matdata/yasqe 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -0
- package/build/ts/grammar/tokenizer.d.ts +37 -0
- package/build/ts/src/CodeMirror.d.ts +21 -0
- package/build/ts/src/autocompleters/classes.d.ts +3 -0
- package/build/ts/src/autocompleters/index.d.ts +39 -0
- package/build/ts/src/autocompleters/prefixes.d.ts +3 -0
- package/build/ts/src/autocompleters/properties.d.ts +3 -0
- package/build/ts/src/autocompleters/variables.d.ts +3 -0
- package/build/ts/src/defaults.d.ts +75 -0
- package/build/ts/src/imgs.d.ts +7 -0
- package/build/ts/src/index.d.ts +303 -0
- package/build/ts/src/prefixFold.d.ts +8 -0
- package/build/ts/src/prefixUtils.d.ts +9 -0
- package/build/ts/src/sparql.d.ts +20 -0
- package/build/ts/src/tokenUtils.d.ts +4 -0
- package/build/ts/src/tooltip.d.ts +2 -0
- package/build/ts/src/trie.d.ts +13 -0
- package/build/yasqe.html +108 -0
- package/build/yasqe.min.css +2 -0
- package/build/yasqe.min.css.map +1 -0
- package/build/yasqe.min.js +3 -0
- package/build/yasqe.min.js.LICENSE.txt +3 -0
- package/build/yasqe.min.js.map +1 -0
- package/grammar/README.md +12 -0
- package/grammar/_tokenizer-table.js +4776 -0
- package/grammar/build.sh +2 -0
- package/grammar/sparql11-grammar.pl +834 -0
- package/grammar/sparqljs-browser-min.js +4535 -0
- package/grammar/tokenizer.ts +729 -0
- package/grammar/util/gen_ll1.pl +37 -0
- package/grammar/util/gen_sparql11.pl +11 -0
- package/grammar/util/ll1.pl +175 -0
- package/grammar/util/output_to_javascript.pl +75 -0
- package/grammar/util/prune.pl +49 -0
- package/grammar/util/rewrite.pl +104 -0
- package/package.json +40 -0
- package/src/CodeMirror.ts +54 -0
- package/src/autocompleters/classes.ts +32 -0
- package/src/autocompleters/index.ts +346 -0
- package/src/autocompleters/prefixes.ts +130 -0
- package/src/autocompleters/properties.ts +28 -0
- package/src/autocompleters/show-hint.scss +38 -0
- package/src/autocompleters/variables.ts +52 -0
- package/src/defaults.ts +149 -0
- package/src/imgs.ts +14 -0
- package/src/index.ts +1089 -0
- package/src/prefixFold.ts +93 -0
- package/src/prefixUtils.ts +65 -0
- package/src/scss/buttons.scss +275 -0
- package/src/scss/codemirrorMods.scss +36 -0
- package/src/scss/yasqe.scss +89 -0
- package/src/sparql.ts +215 -0
- package/src/tokenUtils.ts +121 -0
- package/src/tooltip.ts +31 -0
- package/src/trie.ts +238 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { default as Yasqe, Token, Hint, Position, Config, HintFn, HintConfig } from "../";
|
|
2
|
+
import Trie from "../trie";
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import { take } from "lodash-es";
|
|
5
|
+
const CodeMirror = require("codemirror");
|
|
6
|
+
require("./show-hint.scss");
|
|
7
|
+
export interface CompleterConfig {
|
|
8
|
+
onInitialize?: (this: CompleterConfig, yasqe: Yasqe) => void; //allows for e.g. registering event listeners in yasqe, like the prefix autocompleter does
|
|
9
|
+
isValidCompletionPosition: (yasqe: Yasqe) => boolean;
|
|
10
|
+
get: (yasqe: Yasqe, token?: AutocompletionToken) => Promise<string[]> | string[];
|
|
11
|
+
preProcessToken?: (yasqe: Yasqe, token: Token) => AutocompletionToken;
|
|
12
|
+
postProcessSuggestion?: (yasqe: Yasqe, token: AutocompletionToken, suggestedString: string) => string;
|
|
13
|
+
postprocessHints?: (yasqe: Yasqe, hints: Hint[]) => Hint[];
|
|
14
|
+
bulk: boolean;
|
|
15
|
+
autoShow?: boolean;
|
|
16
|
+
persistenceId?: Config["persistenceId"];
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
const SUGGESTIONS_LIMIT = 100;
|
|
20
|
+
export interface AutocompletionToken extends Token {
|
|
21
|
+
autocompletionString?: string;
|
|
22
|
+
tokenPrefix?: string;
|
|
23
|
+
tokenPrefixUri?: string;
|
|
24
|
+
from?: Partial<Position>;
|
|
25
|
+
to?: Partial<Position>;
|
|
26
|
+
}
|
|
27
|
+
export class Completer extends EventEmitter {
|
|
28
|
+
protected yasqe: Yasqe;
|
|
29
|
+
private trie?: Trie;
|
|
30
|
+
private config: CompleterConfig;
|
|
31
|
+
constructor(yasqe: Yasqe, config: CompleterConfig) {
|
|
32
|
+
super();
|
|
33
|
+
this.yasqe = yasqe;
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// private selectHint(data:EditorChange, completion:any) {
|
|
38
|
+
// if (completion.text != this.yasqe.getTokenAt(this.yasqe.getDoc().getCursor()).string) {
|
|
39
|
+
// this.yasqe.getDoc().replaceRange(completion.text, data.from, data.to);
|
|
40
|
+
// }
|
|
41
|
+
// };
|
|
42
|
+
private getStorageId() {
|
|
43
|
+
return this.yasqe.getStorageId(this.config.persistenceId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Store bulk completion in local storage, and populates the trie
|
|
48
|
+
*/
|
|
49
|
+
private storeBulkCompletions(completions: string[]) {
|
|
50
|
+
if (!completions || !(completions instanceof Array)) return;
|
|
51
|
+
// store array as trie
|
|
52
|
+
this.trie = new Trie();
|
|
53
|
+
for (const c of completions) {
|
|
54
|
+
this.trie.insert(c);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// store in localstorage as well
|
|
58
|
+
var storageId = this.getStorageId();
|
|
59
|
+
if (storageId)
|
|
60
|
+
this.yasqe.storage.set(storageId, completions, 60 * 60 * 24 * 30, this.yasqe.handleLocalStorageQuotaFull);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get completion list from `get` function
|
|
65
|
+
*/
|
|
66
|
+
public getCompletions(token?: AutocompletionToken): Promise<string[]> {
|
|
67
|
+
if (!this.config.get) return Promise.resolve([]);
|
|
68
|
+
|
|
69
|
+
//No token, so probably getting as bulk
|
|
70
|
+
if (!token) {
|
|
71
|
+
if (this.config.get instanceof Array) return Promise.resolve(this.config.get);
|
|
72
|
+
//wrapping call in a promise.resolve, so this when a `get` is both async or sync
|
|
73
|
+
return Promise.resolve(this.config.get(this.yasqe)).then((suggestions) => {
|
|
74
|
+
if (suggestions instanceof Array) return suggestions;
|
|
75
|
+
return [];
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//ok, there is a token
|
|
80
|
+
const stringToAutocomplete = token.autocompletionString || token.string;
|
|
81
|
+
if (this.trie) return Promise.resolve(take(this.trie.autoComplete(stringToAutocomplete), SUGGESTIONS_LIMIT));
|
|
82
|
+
if (this.config.get instanceof Array)
|
|
83
|
+
return Promise.resolve(
|
|
84
|
+
this.config.get.filter((possibleMatch) => possibleMatch.indexOf(stringToAutocomplete) === 0)
|
|
85
|
+
);
|
|
86
|
+
//assuming it's a function
|
|
87
|
+
return Promise.resolve(this.config.get(this.yasqe, token)).then((r) => {
|
|
88
|
+
if (r instanceof Array) return r;
|
|
89
|
+
return [];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Populates completions. Pre-fetches those if bulk is set to true
|
|
95
|
+
*/
|
|
96
|
+
public initialize(): Promise<void> {
|
|
97
|
+
if (this.config.onInitialize) this.config.onInitialize(this.yasqe);
|
|
98
|
+
if (this.config.bulk) {
|
|
99
|
+
if (this.config.get instanceof Array) {
|
|
100
|
+
// we don't care whether the completions are already stored in
|
|
101
|
+
// localstorage. just use this one
|
|
102
|
+
this.storeBulkCompletions(this.config.get);
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
} else {
|
|
105
|
+
// if completions are defined in localstorage, use those! (calling the
|
|
106
|
+
// function may come with overhead (e.g. async calls))
|
|
107
|
+
var completionsFromStorage: string[] | undefined;
|
|
108
|
+
var storageId = this.getStorageId();
|
|
109
|
+
if (storageId) completionsFromStorage = this.yasqe.storage.get<string[]>(storageId);
|
|
110
|
+
if (completionsFromStorage && completionsFromStorage.length > 0) {
|
|
111
|
+
this.storeBulkCompletions(completionsFromStorage);
|
|
112
|
+
return Promise.resolve();
|
|
113
|
+
} else {
|
|
114
|
+
return this.getCompletions().then((c) => this.storeBulkCompletions(c));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private isValidPosition(): boolean {
|
|
122
|
+
if (!this.config.isValidCompletionPosition) return false; //no way to check whether we are in a valid position
|
|
123
|
+
if (!this.config.isValidCompletionPosition(this.yasqe)) {
|
|
124
|
+
this.emit("invalidPosition", this);
|
|
125
|
+
this.yasqe.hideNotification(this.config.name);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (!this.config.autoShow) {
|
|
129
|
+
this.yasqe.showNotification(this.config.name, "Press CTRL - <spacebar> to autocomplete");
|
|
130
|
+
}
|
|
131
|
+
this.emit("validPosition", this);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private getHint(autocompletionToken: AutocompletionToken, suggestedString: string): Hint {
|
|
136
|
+
if (this.config.postProcessSuggestion) {
|
|
137
|
+
suggestedString = this.config.postProcessSuggestion(this.yasqe, autocompletionToken, suggestedString);
|
|
138
|
+
}
|
|
139
|
+
let from: Position | undefined;
|
|
140
|
+
let to: Position;
|
|
141
|
+
const cursor = this.yasqe.getDoc().getCursor();
|
|
142
|
+
if (autocompletionToken.from) {
|
|
143
|
+
from = { ...cursor, ...autocompletionToken.from };
|
|
144
|
+
}
|
|
145
|
+
// Need to set a 'to' part as well, as otherwise we'd be appending the result to the already typed filter
|
|
146
|
+
const line = this.yasqe.getDoc().getCursor().line;
|
|
147
|
+
if (autocompletionToken.to) {
|
|
148
|
+
to = { ch: autocompletionToken?.to?.ch || this.yasqe.getCompleteToken().end, line: line };
|
|
149
|
+
} else if (autocompletionToken.string.length > 0) {
|
|
150
|
+
to = { ch: this.yasqe.getCompleteToken().end, line: line };
|
|
151
|
+
} else {
|
|
152
|
+
to = <any>autocompletionToken.from;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
text: suggestedString,
|
|
156
|
+
displayText: suggestedString,
|
|
157
|
+
from: from,
|
|
158
|
+
to: to,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private getHints(token: AutocompletionToken): Promise<Hint[]> {
|
|
163
|
+
if (this.config.preProcessToken) {
|
|
164
|
+
token = this.config.preProcessToken(this.yasqe, token);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (token)
|
|
168
|
+
return this.getCompletions(token)
|
|
169
|
+
.then((suggestions) => suggestions.map((s) => this.getHint(token, s)))
|
|
170
|
+
.then((hints) => {
|
|
171
|
+
if (this.config.postprocessHints) return this.config.postprocessHints(this.yasqe, hints);
|
|
172
|
+
return hints;
|
|
173
|
+
});
|
|
174
|
+
return Promise.resolve([]);
|
|
175
|
+
}
|
|
176
|
+
public autocomplete(fromAutoShow: boolean) {
|
|
177
|
+
//this part goes before the autoshow check, as we _would_ like notification showing to indicate a user can press ctrl-space
|
|
178
|
+
if (!this.isValidPosition()) return false;
|
|
179
|
+
const previousCompletionItem = this.yasqe.state.completionActive;
|
|
180
|
+
|
|
181
|
+
// Showhint by defaults takes the autocomplete start position (the location of the cursor at the time of starting the autocompletion).
|
|
182
|
+
const cursor = this.yasqe.getDoc().getCursor();
|
|
183
|
+
if (
|
|
184
|
+
// When the cursor goes before current completionItem (e.g. using arrow keys), it would close the autocompletions.
|
|
185
|
+
// We want the autocompletion to be active at whatever point we are in the token, so let's modify this start pos with the start pos of the token
|
|
186
|
+
previousCompletionItem &&
|
|
187
|
+
cursor.sticky && // Is undefined at the end of the token, otherwise it is set as either "before" or "after" (The movement of the cursor)
|
|
188
|
+
cursor.ch !== previousCompletionItem.startPos.ch
|
|
189
|
+
) {
|
|
190
|
+
this.yasqe.state.completionActive.startPos = cursor;
|
|
191
|
+
} else if (previousCompletionItem && !cursor.sticky && cursor.ch < previousCompletionItem.startPos.ch) {
|
|
192
|
+
// A similar thing happens when pressing backspace, CodeMirror will close this autocomplete when 'startLen' changes downward
|
|
193
|
+
cursor.sticky = previousCompletionItem.startPos.sticky;
|
|
194
|
+
this.yasqe.state.completionActive.startPos.ch = cursor.ch;
|
|
195
|
+
this.yasqe.state.completionActive.startLen--;
|
|
196
|
+
}
|
|
197
|
+
if (
|
|
198
|
+
fromAutoShow && // from autoShow, i.e. this gets called each time the editor content changes
|
|
199
|
+
(!this.config.autoShow || this.yasqe.state.completionActive) // Don't show and don't create a new instance when its already active
|
|
200
|
+
) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const getHints: HintFn = () => {
|
|
205
|
+
return this.getHints(this.yasqe.getCompleteToken()).then((list) => {
|
|
206
|
+
const cur = this.yasqe.getDoc().getCursor();
|
|
207
|
+
const token: AutocompletionToken = this.yasqe.getCompleteToken();
|
|
208
|
+
const hintResult = {
|
|
209
|
+
list: list,
|
|
210
|
+
from: <Position>{
|
|
211
|
+
line: cur.line,
|
|
212
|
+
ch: token.start,
|
|
213
|
+
},
|
|
214
|
+
to: <Position>{
|
|
215
|
+
line: cur.line,
|
|
216
|
+
ch: token.end,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
CodeMirror.on(hintResult, "shown", () => {
|
|
220
|
+
this.yasqe.emit("autocompletionShown", (this.yasqe as any).state.completionActive.widget);
|
|
221
|
+
});
|
|
222
|
+
CodeMirror.on(hintResult, "close", () => {
|
|
223
|
+
this.yasqe.emit("autocompletionClose");
|
|
224
|
+
});
|
|
225
|
+
return hintResult;
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
getHints.async = false; //in their code, async means using a callback
|
|
230
|
+
//we always return a promise, which should be properly handled regardless of this val
|
|
231
|
+
var hintConfig: HintConfig = {
|
|
232
|
+
closeCharacters: /[\s>"]/,
|
|
233
|
+
completeSingle: false,
|
|
234
|
+
hint: getHints,
|
|
235
|
+
container: this.yasqe.rootEl,
|
|
236
|
+
// Override these actions back to use their default function
|
|
237
|
+
// Otherwise these would navigate to the start/end of the suggestion list, while this can also be accomplished with PgUp and PgDn
|
|
238
|
+
extraKeys: {
|
|
239
|
+
Home: (yasqe, event) => {
|
|
240
|
+
yasqe.getDoc().setCursor({ ch: 0, line: event.data.from.line });
|
|
241
|
+
},
|
|
242
|
+
End: (yasqe, event) => {
|
|
243
|
+
yasqe.getDoc().setCursor({ ch: yasqe.getLine(event.data.to.line).length, line: event.data.to.line });
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
...this.yasqe.config.hintConfig,
|
|
247
|
+
};
|
|
248
|
+
this.yasqe.showHint(hintConfig);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Converts rdf:type to http://.../type and converts <http://...> to http://...
|
|
255
|
+
* Stores additional info such as the used namespace and prefix in the token object
|
|
256
|
+
*/
|
|
257
|
+
export function preprocessIriForCompletion(yasqe: Yasqe, token: AutocompletionToken) {
|
|
258
|
+
const queryPrefixes = yasqe.getPrefixesFromQuery();
|
|
259
|
+
const stringToPreprocess = token.string;
|
|
260
|
+
|
|
261
|
+
if (stringToPreprocess.indexOf("<") < 0) {
|
|
262
|
+
token.tokenPrefix = stringToPreprocess.substring(0, stringToPreprocess.indexOf(":") + 1);
|
|
263
|
+
|
|
264
|
+
if (queryPrefixes[token.tokenPrefix.slice(0, -1)] != null) {
|
|
265
|
+
token.tokenPrefixUri = queryPrefixes[token.tokenPrefix.slice(0, -1)];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
token.autocompletionString = stringToPreprocess.trim();
|
|
270
|
+
if (stringToPreprocess.indexOf("<") < 0 && stringToPreprocess.indexOf(":") > -1) {
|
|
271
|
+
// hmm, the token is prefixed. We still need the complete uri for autocompletions. generate this!
|
|
272
|
+
for (var prefix in queryPrefixes) {
|
|
273
|
+
if (token.tokenPrefix === prefix + ":") {
|
|
274
|
+
token.autocompletionString = queryPrefixes[prefix];
|
|
275
|
+
token.autocompletionString += stringToPreprocess.substring(prefix.length + 1);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (token.autocompletionString.indexOf("<") == 0)
|
|
282
|
+
token.autocompletionString = token.autocompletionString.substring(1);
|
|
283
|
+
if (token.autocompletionString.indexOf(">", token.autocompletionString.length - 1) > 0)
|
|
284
|
+
token.autocompletionString = token.autocompletionString.substring(0, token.autocompletionString.length - 1);
|
|
285
|
+
return token;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function postprocessIriCompletion(_yasqe: Yasqe, token: AutocompletionToken, suggestedString: string) {
|
|
289
|
+
if (token.tokenPrefix && token.autocompletionString && token.tokenPrefixUri) {
|
|
290
|
+
// we need to get the suggested string back to prefixed form
|
|
291
|
+
suggestedString = token.tokenPrefix + suggestedString.substring(token.tokenPrefixUri.length);
|
|
292
|
+
} else {
|
|
293
|
+
// it is a regular uri. add '<' and '>' to string
|
|
294
|
+
suggestedString = "<" + suggestedString + ">";
|
|
295
|
+
}
|
|
296
|
+
return suggestedString;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
//Use protocol relative request when served via http[s]*. Otherwise (e.g. file://, fetch via http)
|
|
300
|
+
export const fetchFromLov = (
|
|
301
|
+
yasqe: Yasqe,
|
|
302
|
+
type: "class" | "property",
|
|
303
|
+
token?: AutocompletionToken
|
|
304
|
+
): Promise<string[]> => {
|
|
305
|
+
var reqProtocol = window.location.protocol.indexOf("http") === 0 ? "https://" : "http://";
|
|
306
|
+
const notificationKey = "autocomplete_" + type;
|
|
307
|
+
if (!token || !token.string || token.string.trim().length == 0) {
|
|
308
|
+
yasqe.showNotification(notificationKey, "Nothing to autocomplete yet!");
|
|
309
|
+
return Promise.resolve([]);
|
|
310
|
+
}
|
|
311
|
+
// //if notification bar is there, show a loader
|
|
312
|
+
// yasqe.autocompleters.notifications
|
|
313
|
+
// .getEl(completer)
|
|
314
|
+
// .empty()
|
|
315
|
+
// .append($("<span>Fetchting autocompletions </span>"))
|
|
316
|
+
// .append($(yutils.svg.getElement(require("../imgs.js").loader)).addClass("notificationLoader"));
|
|
317
|
+
// doRequests();
|
|
318
|
+
|
|
319
|
+
const params = new URLSearchParams();
|
|
320
|
+
if (token.autocompletionString) params.append("q", token.autocompletionString);
|
|
321
|
+
params.append("page_size", "50");
|
|
322
|
+
params.append("type", type);
|
|
323
|
+
const url = `${reqProtocol}lov.linkeddata.es/dataset/lov/api/v2/autocomplete/terms?${params.toString()}`;
|
|
324
|
+
return fetch(url)
|
|
325
|
+
.then((response) => {
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new Error("Failed fetching suggestions from Linked Open Vocabularies");
|
|
328
|
+
}
|
|
329
|
+
return response.json();
|
|
330
|
+
})
|
|
331
|
+
.then((result) => {
|
|
332
|
+
if (result.results) {
|
|
333
|
+
return result.results.map((r: any) => r.uri[0]);
|
|
334
|
+
}
|
|
335
|
+
return [];
|
|
336
|
+
})
|
|
337
|
+
.catch((_e) => {
|
|
338
|
+
yasqe.showNotification(notificationKey, "Failed fetching suggestions");
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
import variableCompleter from "./variables";
|
|
343
|
+
import prefixCompleter from "./prefixes";
|
|
344
|
+
import propertyCompleter from "./properties";
|
|
345
|
+
import classCompleter from "./classes";
|
|
346
|
+
export var completers: CompleterConfig[] = [variableCompleter, prefixCompleter, propertyCompleter, classCompleter];
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as Autocompleter from "./";
|
|
2
|
+
import { sortBy } from "lodash-es";
|
|
3
|
+
var tokenTypes: { [id: string]: "prefixed" | "var" } = {
|
|
4
|
+
"string-2": "prefixed",
|
|
5
|
+
atom: "var",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
var conf: Autocompleter.CompleterConfig = {
|
|
9
|
+
postprocessHints: function (_yasqe, hints) {
|
|
10
|
+
return sortBy(hints, (hint) => hint.text.split(":")[0]);
|
|
11
|
+
},
|
|
12
|
+
onInitialize: function (yasqe) {
|
|
13
|
+
/**
|
|
14
|
+
* This event listener makes sure we auto-add prefixes whenever we use them
|
|
15
|
+
*/
|
|
16
|
+
yasqe.on("change", () => {
|
|
17
|
+
if (!yasqe.config.autocompleters || yasqe.config.autocompleters.indexOf(this.name) == -1) return; //this autocompleter is disabled
|
|
18
|
+
var cur = yasqe.getDoc().getCursor();
|
|
19
|
+
|
|
20
|
+
var token: Autocompleter.AutocompletionToken = yasqe.getTokenAt(cur);
|
|
21
|
+
if (token.type && tokenTypes[token.type] == "prefixed") {
|
|
22
|
+
var colonIndex = token.string.indexOf(":");
|
|
23
|
+
if (colonIndex !== -1) {
|
|
24
|
+
// check previous token isnt PREFIX, or a '<'(which would mean we are in a uri)
|
|
25
|
+
// var firstTokenString = yasqe.getNextNonWsToken(cur.line).string.toUpperCase();
|
|
26
|
+
var lastNonWsTokenString = yasqe.getPreviousNonWsToken(cur.line, token).string.toUpperCase();
|
|
27
|
+
var previousToken = yasqe.getTokenAt({
|
|
28
|
+
line: cur.line,
|
|
29
|
+
ch: token.start,
|
|
30
|
+
}); // needs to be null (beginning of line), or whitespace
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
lastNonWsTokenString !== "PREFIX" &&
|
|
34
|
+
(previousToken.type == "ws" ||
|
|
35
|
+
previousToken.type == null ||
|
|
36
|
+
(previousToken.type === "punc" &&
|
|
37
|
+
(previousToken.string === "|" ||
|
|
38
|
+
previousToken.string === "/" ||
|
|
39
|
+
previousToken.string == "^^" ||
|
|
40
|
+
previousToken.string == "{" ||
|
|
41
|
+
previousToken.string === "(")))
|
|
42
|
+
) {
|
|
43
|
+
// check whether it isn't defined already (saves us from looping
|
|
44
|
+
// through the array)
|
|
45
|
+
var currentPrefix = token.string.substring(0, colonIndex + 1);
|
|
46
|
+
|
|
47
|
+
var queryPrefixes = yasqe.getPrefixesFromQuery();
|
|
48
|
+
if (queryPrefixes[currentPrefix.slice(0, -1)] == null) {
|
|
49
|
+
// ok, so it isn't added yet!
|
|
50
|
+
// var completions = yasqe.autocompleters.getTrie(completerName).autoComplete(currentPrefix);
|
|
51
|
+
token.autocompletionString = currentPrefix;
|
|
52
|
+
yasqe.autocompleters[this.name]?.getCompletions(token).then((suggestions) => {
|
|
53
|
+
if (suggestions.length) {
|
|
54
|
+
yasqe.addPrefixes(suggestions[0]);
|
|
55
|
+
// Re-activate auto-completer after adding prefixes, so another auto-completer can kick in
|
|
56
|
+
yasqe.autocomplete();
|
|
57
|
+
}
|
|
58
|
+
}, console.warn);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
isValidCompletionPosition: function (yasqe) {
|
|
66
|
+
var cur = yasqe.getDoc().getCursor(),
|
|
67
|
+
token = yasqe.getTokenAt(cur);
|
|
68
|
+
|
|
69
|
+
// not at end of line
|
|
70
|
+
if (yasqe.getDoc().getLine(cur.line).length > cur.ch) return false;
|
|
71
|
+
|
|
72
|
+
if (token.type != "ws") {
|
|
73
|
+
// we want to complete token, e.g. when the prefix starts with an a
|
|
74
|
+
// (treated as a token in itself..)
|
|
75
|
+
// but we to avoid including the PREFIX tag. So when we have just
|
|
76
|
+
// typed a space after the prefix tag, don't get the complete token
|
|
77
|
+
token = yasqe.getCompleteToken();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// we shouldnt be at the uri part the prefix declaration
|
|
81
|
+
// also check whether current token isnt 'a' (that makes codemirror
|
|
82
|
+
// thing a namespace is a possiblecurrent
|
|
83
|
+
if (token.string.indexOf("a") !== 0 && token.state.possibleCurrent.indexOf("PNAME_NS") < 0) return false;
|
|
84
|
+
|
|
85
|
+
// First token of line needs to be PREFIX,
|
|
86
|
+
// there should be no trailing text (otherwise, text is wrongly inserted
|
|
87
|
+
// in between)
|
|
88
|
+
var previousToken = yasqe.getPreviousNonWsToken(cur.line, token);
|
|
89
|
+
if (!previousToken || previousToken.string.toUpperCase() != "PREFIX") return false;
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
get: function (yasqe) {
|
|
93
|
+
return fetch(yasqe.config.prefixCcApi)
|
|
94
|
+
.then((response) => {
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error("Failed to fetch prefixes");
|
|
97
|
+
}
|
|
98
|
+
return response.json();
|
|
99
|
+
})
|
|
100
|
+
.then((resp) => {
|
|
101
|
+
const prefixArray: string[] = [];
|
|
102
|
+
for (const prefix in resp) {
|
|
103
|
+
const completeString = `${prefix}: <${resp[prefix]}>`;
|
|
104
|
+
prefixArray.push(completeString); // the array we want to store in local storage
|
|
105
|
+
}
|
|
106
|
+
return prefixArray.sort();
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
preProcessToken: function (yasqe, token) {
|
|
110
|
+
var previousToken = yasqe.getPreviousNonWsToken(yasqe.getDoc().getCursor().line, token);
|
|
111
|
+
if (previousToken && previousToken.string && previousToken.string.slice(-1) == ":") {
|
|
112
|
+
//combine both tokens! In this case we have the cursor at the end of line "PREFIX bla: <".
|
|
113
|
+
//we want the token to be "bla: <", en not "<"
|
|
114
|
+
token = {
|
|
115
|
+
start: previousToken.start,
|
|
116
|
+
end: token.end,
|
|
117
|
+
string: previousToken.string + " " + token.string,
|
|
118
|
+
state: token.state,
|
|
119
|
+
type: token.type,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return token;
|
|
123
|
+
},
|
|
124
|
+
bulk: true,
|
|
125
|
+
autoShow: true,
|
|
126
|
+
persistenceId: "prefixes",
|
|
127
|
+
name: "prefixes",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export default conf;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as Autocompleter from "./";
|
|
2
|
+
|
|
3
|
+
var conf: Autocompleter.CompleterConfig = {
|
|
4
|
+
onInitialize: function (_yasqe) {
|
|
5
|
+
// validPosition: yasqe.autocompleters.notifications.show,
|
|
6
|
+
// invalidPosition: yasqe.autocompleters.notifications.hide
|
|
7
|
+
},
|
|
8
|
+
get: function (yasqe, token) {
|
|
9
|
+
return Autocompleter.fetchFromLov(yasqe, "property", token);
|
|
10
|
+
},
|
|
11
|
+
isValidCompletionPosition: function (yasqe) {
|
|
12
|
+
const token = yasqe.getCompleteToken();
|
|
13
|
+
if (token.string.length == 0) return false; //we want -something- to autocomplete
|
|
14
|
+
if (token.string[0] === "?" || token.string[0] === "$") return false; // we are typing a var
|
|
15
|
+
if (token.state.possibleCurrent.indexOf("a") >= 0) return true; // predicate pos
|
|
16
|
+
return false;
|
|
17
|
+
},
|
|
18
|
+
preProcessToken: function (yasqe, token) {
|
|
19
|
+
return Autocompleter.preprocessIriForCompletion(yasqe, token);
|
|
20
|
+
},
|
|
21
|
+
postProcessSuggestion: function (yasqe, token, suggestedString) {
|
|
22
|
+
return Autocompleter.postprocessIriCompletion(yasqe, token, suggestedString);
|
|
23
|
+
},
|
|
24
|
+
bulk: false,
|
|
25
|
+
name: "property",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default conf;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//This is a copy of the codemirror show-hint css
|
|
2
|
+
//Including this ourselves, as we don't want this css to be auto-scoped to yasqe by webpack
|
|
3
|
+
.CodeMirror-hints {
|
|
4
|
+
position: absolute;
|
|
5
|
+
z-index: 10;
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
list-style: none;
|
|
8
|
+
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 2px;
|
|
11
|
+
|
|
12
|
+
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
13
|
+
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
14
|
+
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
15
|
+
border-radius: 3px;
|
|
16
|
+
border: 1px solid silver;
|
|
17
|
+
|
|
18
|
+
background: white;
|
|
19
|
+
font-size: 90%;
|
|
20
|
+
font-family: monospace;
|
|
21
|
+
|
|
22
|
+
max-height: 20em;
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.CodeMirror-hint {
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0 4px;
|
|
29
|
+
border-radius: 2px;
|
|
30
|
+
white-space: pre;
|
|
31
|
+
color: black;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
li.CodeMirror-hint-active {
|
|
36
|
+
background: #08f;
|
|
37
|
+
color: white;
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as autocompleter from "./";
|
|
2
|
+
|
|
3
|
+
var conf: autocompleter.CompleterConfig = {
|
|
4
|
+
name: "variables",
|
|
5
|
+
isValidCompletionPosition: function (yasqe) {
|
|
6
|
+
var token = yasqe.getTokenAt(yasqe.getDoc().getCursor());
|
|
7
|
+
if (token.type != "ws") {
|
|
8
|
+
token = yasqe.getCompleteToken(token);
|
|
9
|
+
if (token && (token.string[0] === "?" || token.string[0] === "$")) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
},
|
|
15
|
+
get: function (yasqe, token) {
|
|
16
|
+
if (!token || token.string.length == 0) return []; //nothing to autocomplete
|
|
17
|
+
const distinctVars: { [varname: string]: any } = {};
|
|
18
|
+
const vars: string[] = [];
|
|
19
|
+
//do this outside of codemirror. I expect jquery to be faster here (just finding dom elements with classnames)
|
|
20
|
+
//and: this'll still work when the query is incorrect (i.e., when simply typing '?')
|
|
21
|
+
const atoms = yasqe.getWrapperElement().querySelectorAll(".cm-atom");
|
|
22
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
23
|
+
const atom = atoms[i];
|
|
24
|
+
var variable = atom.innerHTML;
|
|
25
|
+
if (variable[0] === "?" || variable[0] === "$") {
|
|
26
|
+
//ok, lets check if the next element in the div is an atom as well. In that case, they belong together (may happen sometimes when query is not syntactically valid)
|
|
27
|
+
// var nextElClass = nextEl.attr("class");
|
|
28
|
+
const nextEl: HTMLElement = <any>atom.nextSibling;
|
|
29
|
+
if (nextEl && nextEl.className && nextEl.className.indexOf("cm-atom") >= 0) {
|
|
30
|
+
variable += nextEl.innerText;
|
|
31
|
+
}
|
|
32
|
+
if (distinctVars[variable]) continue; //already in list
|
|
33
|
+
//skip single questionmarks
|
|
34
|
+
if (variable.length <= 1) continue;
|
|
35
|
+
|
|
36
|
+
//it should match our token ofcourse
|
|
37
|
+
if (variable.indexOf(token.string) !== 0) continue;
|
|
38
|
+
|
|
39
|
+
//skip exact matches
|
|
40
|
+
if (variable === token.string) continue;
|
|
41
|
+
|
|
42
|
+
//store in map so we have a unique list
|
|
43
|
+
distinctVars[variable] = true;
|
|
44
|
+
vars.push(variable);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return vars.sort();
|
|
48
|
+
},
|
|
49
|
+
bulk: false,
|
|
50
|
+
autoShow: true,
|
|
51
|
+
};
|
|
52
|
+
export default conf;
|