@bestcodetools/graphql-playground 0.0.0

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.
@@ -0,0 +1,1896 @@
1
+ ((app) => {
2
+ const TEMPLATE_VERSION = '20260409p';
3
+
4
+ app.component('queryEditor', {
5
+ templateUrl: `components/query-editor.html?v=${TEMPLATE_VERSION}`,
6
+ bindings: {
7
+ query: '=',
8
+ schema: '<',
9
+ api: '=?',
10
+ onChange: '&?'
11
+ },
12
+ controller: ['$element', function ($element) {
13
+ const $ctrl = this;
14
+ let editor = null;
15
+ let tooltipElement = null;
16
+ let hideTooltipTimeout = null;
17
+ let resizeHandler = null;
18
+ const graphqlKeywords = [
19
+ 'query',
20
+ 'mutation',
21
+ 'subscription',
22
+ 'fragment',
23
+ 'on',
24
+ 'schema',
25
+ 'type',
26
+ 'interface',
27
+ 'union',
28
+ 'enum',
29
+ 'input',
30
+ 'scalar',
31
+ 'directive',
32
+ 'implements',
33
+ 'extend',
34
+ 'true',
35
+ 'false',
36
+ 'null'
37
+ ];
38
+
39
+ function ensureGraphqlMode() {
40
+ if (typeof window.CodeMirror === 'undefined') {
41
+ return;
42
+ }
43
+
44
+ if (window.CodeMirror.modes && window.CodeMirror.modes['graphql-playground']) {
45
+ return;
46
+ }
47
+
48
+ const operationKeywordPattern = /^(?:query|mutation|subscription|fragment|on)\b/;
49
+ const schemaKeywordPattern = /^(?:schema|type|interface|union|enum|input|scalar|directive|implements|extend)\b/;
50
+ const atomPattern = /^(?:true|false|null)\b/;
51
+
52
+ window.CodeMirror.defineMode('graphql-playground', function () {
53
+ return {
54
+ startState() {
55
+ return {
56
+ inString: false,
57
+ blockString: false,
58
+ blockComment: false,
59
+ expectField: false,
60
+ expectArgument: false,
61
+ expectType: false,
62
+ argumentDepth: 0,
63
+ selectionDepth: 0
64
+ };
65
+ },
66
+ token(stream, state) {
67
+ if (state.blockComment) {
68
+ if (stream.match(/.*?\*\//)) {
69
+ state.blockComment = false;
70
+ } else {
71
+ stream.skipToEnd();
72
+ }
73
+
74
+ return 'comment';
75
+ }
76
+
77
+ if (state.blockString) {
78
+ if (stream.match(/.*?"""/)) {
79
+ state.blockString = false;
80
+ } else {
81
+ stream.skipToEnd();
82
+ }
83
+
84
+ return 'string';
85
+ }
86
+
87
+ if (state.inString) {
88
+ let escaped = false;
89
+ let next;
90
+
91
+ while ((next = stream.next()) != null) {
92
+ if (next === '"' && !escaped) {
93
+ state.inString = false;
94
+ break;
95
+ }
96
+
97
+ escaped = !escaped && next === '\\';
98
+ }
99
+
100
+ return 'string';
101
+ }
102
+
103
+ if (stream.eatSpace()) {
104
+ return null;
105
+ }
106
+
107
+ if (stream.match(/^#.*/)) {
108
+ return 'comment';
109
+ }
110
+
111
+ if (stream.match('/*')) {
112
+ state.blockComment = true;
113
+ return 'comment';
114
+ }
115
+
116
+ if (stream.match('"""')) {
117
+ state.blockString = true;
118
+ return 'string';
119
+ }
120
+
121
+ if (stream.peek() === '"') {
122
+ stream.next();
123
+ state.inString = true;
124
+ return 'string';
125
+ }
126
+
127
+ if (stream.match(atomPattern)) {
128
+ return 'atom';
129
+ }
130
+
131
+ if (stream.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)) {
132
+ return 'number';
133
+ }
134
+
135
+ if (stream.match(/^@[A-Za-z_][A-Za-z0-9_]*/)) {
136
+ return 'meta';
137
+ }
138
+
139
+ if (stream.match(/^\$[A-Za-z_][A-Za-z0-9_]*/)) {
140
+ state.expectArgument = false;
141
+ return 'variable-2';
142
+ }
143
+
144
+ if (stream.match(/^!/)) {
145
+ return 'non-null-modifier';
146
+ }
147
+
148
+ if (stream.match(/^[()\[\]{}]/)) {
149
+ const bracket = stream.current();
150
+
151
+ if (bracket === '(') {
152
+ state.argumentDepth += 1;
153
+ state.expectArgument = true;
154
+ } else if (bracket === ')') {
155
+ state.argumentDepth = Math.max(0, state.argumentDepth - 1);
156
+ state.expectArgument = state.argumentDepth > 0;
157
+ } else if (bracket === '{') {
158
+ state.selectionDepth += 1;
159
+ } else if (bracket === '}') {
160
+ state.selectionDepth = Math.max(0, state.selectionDepth - 1);
161
+ }
162
+
163
+ if (bracket === '[' || bracket === ']') {
164
+ return 'list-modifier';
165
+ }
166
+
167
+ return 'bracket';
168
+ }
169
+
170
+ if (stream.match(/^,/)) {
171
+ if (state.argumentDepth > 0) {
172
+ state.expectArgument = true;
173
+ state.expectType = false;
174
+ }
175
+
176
+ return 'operator';
177
+ }
178
+
179
+ if (stream.match(/^[:=|&!]/)) {
180
+ const operator = stream.current();
181
+
182
+ if (operator === ':') {
183
+ state.expectType = true;
184
+ } else if (operator === '=') {
185
+ state.expectType = false;
186
+ } else if (operator === '|') {
187
+ state.expectType = true;
188
+ }
189
+
190
+ return 'operator';
191
+ }
192
+
193
+ if (stream.match(/^[A-Z][A-Za-z0-9_]*/)) {
194
+ state.expectType = false;
195
+ return 'type-name';
196
+ }
197
+
198
+ if (stream.match(/^[a-z_][A-Za-z0-9_]*/i)) {
199
+ const identifier = stream.current();
200
+
201
+ if (state.expectArgument) {
202
+ state.expectArgument = false;
203
+ return 'variable';
204
+ }
205
+
206
+ if (state.expectField) {
207
+ state.expectField = false;
208
+ return 'def';
209
+ }
210
+
211
+ if (state.selectionDepth === 0 && state.argumentDepth === 0 && operationKeywordPattern.test(identifier)) {
212
+ state.expectField = true;
213
+ state.expectArgument = false;
214
+ state.expectType = false;
215
+ return 'keyword';
216
+ }
217
+
218
+ if (state.selectionDepth === 0 && state.argumentDepth === 0 && schemaKeywordPattern.test(identifier)) {
219
+ state.expectField = false;
220
+ state.expectArgument = false;
221
+ state.expectType = true;
222
+ return 'keyword';
223
+ }
224
+
225
+ return 'property';
226
+ }
227
+
228
+ stream.next();
229
+ return null;
230
+ },
231
+ lineComment: '#'
232
+ };
233
+ });
234
+ }
235
+
236
+ function createCompletion(text, meta, renderText, completionType) {
237
+ return {
238
+ text,
239
+ displayText: renderText || text,
240
+ className: completionType ? `cm-hint-${completionType}` : '',
241
+ hint(cm, data, completion) {
242
+ const from = data.from || cm.getCursor();
243
+ const to = data.to || cm.getCursor();
244
+
245
+ if (typeof completion.apply === 'function') {
246
+ completion.apply(cm, from, to, completion);
247
+ return;
248
+ }
249
+
250
+ cm.replaceRange(completion.text, from, to);
251
+
252
+ const cursorOffset = completion.cursorOffset;
253
+
254
+ if (typeof cursorOffset === 'number') {
255
+ const startIndex = cm.indexFromPos(from);
256
+ cm.setCursor(cm.posFromIndex(startIndex + cursorOffset));
257
+ }
258
+ }
259
+ };
260
+ }
261
+
262
+ function getNamedType(typeRef) {
263
+ if (!typeRef) {
264
+ return null;
265
+ }
266
+
267
+ if (typeRef.name) {
268
+ return typeRef;
269
+ }
270
+
271
+ return typeRef.ofType ? getNamedType(typeRef.ofType) : null;
272
+ }
273
+
274
+ function getTypeMap(schema) {
275
+ if (!schema || !Array.isArray(schema.types)) {
276
+ return new Map();
277
+ }
278
+
279
+ return new Map(schema.types.filter((type) => type && type.name).map((type) => [type.name, type]));
280
+ }
281
+
282
+ function getRootTypeName(schema, operation) {
283
+ if (!schema) {
284
+ return null;
285
+ }
286
+
287
+ if (operation === 'mutation') {
288
+ return schema.mutationType && schema.mutationType.name;
289
+ }
290
+
291
+ if (operation === 'subscription') {
292
+ return schema.subscriptionType && schema.subscriptionType.name;
293
+ }
294
+
295
+ return schema.queryType && schema.queryType.name;
296
+ }
297
+
298
+ function getFieldMap(typeRef) {
299
+ if (!typeRef || !Array.isArray(typeRef.fields)) {
300
+ return new Map();
301
+ }
302
+
303
+ return new Map(typeRef.fields.filter((field) => field && field.name).map((field) => [field.name, field]));
304
+ }
305
+
306
+ function getFieldByName(typeMap, typeName, fieldName) {
307
+ if (!typeName || !fieldName) {
308
+ return null;
309
+ }
310
+
311
+ const typeRef = typeMap.get(typeName);
312
+ const fieldMap = getFieldMap(typeRef);
313
+
314
+ return fieldMap.get(fieldName) || null;
315
+ }
316
+
317
+ function typeRefToString(typeRef) {
318
+ if (!typeRef) {
319
+ return '';
320
+ }
321
+
322
+ if (typeRef.kind === 'NON_NULL') {
323
+ return `${typeRefToString(typeRef.ofType)}!`;
324
+ }
325
+
326
+ if (typeRef.kind === 'LIST') {
327
+ return `[${typeRefToString(typeRef.ofType)}]`;
328
+ }
329
+
330
+ return typeRef.name || '';
331
+ }
332
+
333
+ function buildTypeTokens(typeRef) {
334
+ if (!typeRef) {
335
+ return [];
336
+ }
337
+
338
+ if (typeRef.kind === 'LIST') {
339
+ return [
340
+ { text: '[', className: 'field-type-prefix' },
341
+ ...buildTypeTokens(typeRef.ofType),
342
+ { text: ']', className: 'field-type-suffix' }
343
+ ];
344
+ }
345
+
346
+ if (typeRef.kind === 'NON_NULL') {
347
+ return [
348
+ ...buildTypeTokens(typeRef.ofType),
349
+ { text: '!', className: 'field-type-non-null' }
350
+ ];
351
+ }
352
+
353
+ return [{
354
+ text: typeRef.name || '',
355
+ className: 'field-type'
356
+ }];
357
+ }
358
+
359
+ function ensureTooltipElement() {
360
+ if (tooltipElement || typeof document === 'undefined') {
361
+ return tooltipElement;
362
+ }
363
+
364
+ tooltipElement = document.createElement('div');
365
+ tooltipElement.className = 'schema-tooltip';
366
+ tooltipElement.style.display = 'none';
367
+ document.body.appendChild(tooltipElement);
368
+ return tooltipElement;
369
+ }
370
+
371
+ function renderTooltip(payload, eventTarget, event) {
372
+ const tooltip = ensureTooltipElement();
373
+
374
+ if (!tooltip || !payload || !eventTarget || typeof eventTarget.getBoundingClientRect !== 'function') {
375
+ return;
376
+ }
377
+
378
+ const rect = eventTarget.getBoundingClientRect();
379
+ const tooltipWidth = 320;
380
+ const gap = 8;
381
+ const maxLeft = Math.max(gap, window.innerWidth - tooltipWidth - gap);
382
+ const anchorLeft = event && typeof event.clientX === 'number' ? event.clientX : rect.left;
383
+ const anchorTop = event && typeof event.clientY === 'number' ? event.clientY : rect.bottom;
384
+ const top = Math.min(window.innerHeight - 120, anchorTop + gap);
385
+ const left = Math.min(anchorLeft, maxLeft);
386
+ const typeTokens = buildTypeTokens(payload.typeRef);
387
+ const nameClass = payload.labelClass || 'field';
388
+
389
+ tooltip.innerHTML = `
390
+ <div class="schema-tooltip-signature">
391
+ <span class="schema-tooltip-name schema-tooltip-name-${nameClass}"></span>
392
+ ${Array.isArray(payload.arguments) && payload.arguments.length ? '<span class="schema-tooltip-inline-args"></span>' : ''}
393
+ ${typeTokens.length ? '<span>:</span><span class="schema-tooltip-type-tokens"></span>' : ''}
394
+ </div>
395
+ ${Array.isArray(payload.usages) && payload.usages.length ? '<div class="schema-tooltip-usage-list"></div>' : ''}
396
+ ${payload.description ? '<div class="schema-tooltip-description"></div>' : ''}
397
+ `;
398
+
399
+ const nameElement = tooltip.querySelector('.schema-tooltip-name');
400
+ if (nameElement) {
401
+ nameElement.textContent = payload.label || '';
402
+ }
403
+
404
+ const typeTokensContainer = tooltip.querySelector('.schema-tooltip-type-tokens');
405
+ if (typeTokensContainer) {
406
+ typeTokens.forEach((token) => {
407
+ const tokenElement = document.createElement('span');
408
+ tokenElement.className = token.className;
409
+ tokenElement.textContent = token.text;
410
+ typeTokensContainer.appendChild(tokenElement);
411
+ });
412
+ }
413
+
414
+ const descriptionElement = tooltip.querySelector('.schema-tooltip-description');
415
+ if (descriptionElement) {
416
+ descriptionElement.textContent = payload.description || '';
417
+ }
418
+
419
+ const argsElement = tooltip.querySelector('.schema-tooltip-inline-args');
420
+ if (argsElement) {
421
+ const openParen = document.createElement('span');
422
+ openParen.textContent = '(';
423
+ argsElement.appendChild(openParen);
424
+
425
+ payload.arguments.forEach((arg, index) => {
426
+ const name = document.createElement('span');
427
+ name.className = 'schema-tooltip-name schema-tooltip-name-argument';
428
+ name.textContent = arg.name;
429
+ argsElement.appendChild(name);
430
+
431
+ const separator = document.createElement('span');
432
+ separator.textContent = ':';
433
+ argsElement.appendChild(separator);
434
+
435
+ const typeContainer = document.createElement('span');
436
+ typeContainer.className = 'schema-tooltip-type-tokens';
437
+ buildTypeTokens(arg.typeRef).forEach((token) => {
438
+ const tokenElement = document.createElement('span');
439
+ tokenElement.className = token.className;
440
+ tokenElement.textContent = token.text;
441
+ typeContainer.appendChild(tokenElement);
442
+ });
443
+ argsElement.appendChild(typeContainer);
444
+
445
+ if (index < payload.arguments.length - 1) {
446
+ const comma = document.createElement('span');
447
+ comma.textContent = ',';
448
+ argsElement.appendChild(comma);
449
+ }
450
+ });
451
+
452
+ const closeParen = document.createElement('span');
453
+ closeParen.textContent = ')';
454
+ argsElement.appendChild(closeParen);
455
+ }
456
+
457
+ const usageListElement = tooltip.querySelector('.schema-tooltip-usage-list');
458
+ if (usageListElement) {
459
+ payload.usages.forEach((usage) => {
460
+ const usageItem = document.createElement('div');
461
+ usageItem.className = 'schema-tooltip-usage-item';
462
+
463
+ const fieldName = document.createElement('span');
464
+ fieldName.className = 'schema-tooltip-name schema-tooltip-name-field';
465
+ fieldName.textContent = usage.fieldName;
466
+ usageItem.appendChild(fieldName);
467
+
468
+ if (Array.isArray(usage.arguments) && usage.arguments.length) {
469
+ const inlineArgs = document.createElement('span');
470
+ inlineArgs.className = 'schema-tooltip-inline-args';
471
+
472
+ const openParen = document.createElement('span');
473
+ openParen.className = 'schema-tooltip-muted';
474
+ openParen.textContent = '(';
475
+ inlineArgs.appendChild(openParen);
476
+
477
+ usage.arguments.forEach((arg, index) => {
478
+ const argName = document.createElement('span');
479
+ argName.className = arg.isHighlighted
480
+ ? 'schema-tooltip-name schema-tooltip-name-argument'
481
+ : 'schema-tooltip-muted';
482
+ argName.textContent = arg.name;
483
+ inlineArgs.appendChild(argName);
484
+
485
+ const separator = document.createElement('span');
486
+ separator.className = 'schema-tooltip-muted';
487
+ separator.textContent = ':';
488
+ inlineArgs.appendChild(separator);
489
+
490
+ const typeContainer = document.createElement('span');
491
+ typeContainer.className = 'schema-tooltip-type-tokens';
492
+
493
+ buildTypeTokens(arg.typeRef).forEach((token) => {
494
+ const tokenElement = document.createElement('span');
495
+ tokenElement.className = arg.isHighlighted
496
+ ? token.className
497
+ : `${token.className} schema-tooltip-muted`;
498
+ tokenElement.textContent = token.text;
499
+ typeContainer.appendChild(tokenElement);
500
+ });
501
+ inlineArgs.appendChild(typeContainer);
502
+
503
+ if (index < usage.arguments.length - 1) {
504
+ const comma = document.createElement('span');
505
+ comma.className = 'schema-tooltip-muted';
506
+ comma.textContent = ',';
507
+ inlineArgs.appendChild(comma);
508
+ }
509
+ });
510
+
511
+ const closeParen = document.createElement('span');
512
+ closeParen.className = 'schema-tooltip-muted';
513
+ closeParen.textContent = ')';
514
+ inlineArgs.appendChild(closeParen);
515
+ usageItem.appendChild(inlineArgs);
516
+ }
517
+
518
+ if (usage.returnTypeRef) {
519
+ const returnSeparator = document.createElement('span');
520
+ returnSeparator.className = 'schema-tooltip-muted';
521
+ returnSeparator.textContent = ':';
522
+ usageItem.appendChild(returnSeparator);
523
+
524
+ const returnType = document.createElement('span');
525
+ returnType.className = 'schema-tooltip-type-tokens';
526
+ buildTypeTokens(usage.returnTypeRef).forEach((token) => {
527
+ const tokenElement = document.createElement('span');
528
+ tokenElement.className = `${token.className} schema-tooltip-muted`;
529
+ tokenElement.textContent = token.text;
530
+ returnType.appendChild(tokenElement);
531
+ });
532
+ usageItem.appendChild(returnType);
533
+ }
534
+
535
+ usageListElement.appendChild(usageItem);
536
+ });
537
+ }
538
+
539
+ tooltip.style.top = `${Math.max(gap, top)}px`;
540
+ tooltip.style.left = `${Math.max(gap, left)}px`;
541
+ tooltip.style.display = 'block';
542
+ tooltip.classList.remove('is-visible');
543
+
544
+ if (hideTooltipTimeout) {
545
+ window.clearTimeout(hideTooltipTimeout);
546
+ hideTooltipTimeout = null;
547
+ }
548
+
549
+ window.requestAnimationFrame(() => {
550
+ if (tooltip) {
551
+ tooltip.classList.add('is-visible');
552
+ }
553
+ });
554
+ }
555
+
556
+ function hideTooltip() {
557
+ if (tooltipElement) {
558
+ tooltipElement.classList.remove('is-visible');
559
+
560
+ if (hideTooltipTimeout) {
561
+ window.clearTimeout(hideTooltipTimeout);
562
+ }
563
+
564
+ hideTooltipTimeout = window.setTimeout(() => {
565
+ if (tooltipElement) {
566
+ tooltipElement.style.display = 'none';
567
+ }
568
+ hideTooltipTimeout = null;
569
+ }, 300);
570
+ }
571
+ }
572
+
573
+ function toVariableNameSuffix(typeRef) {
574
+ const typeText = typeRefToString(typeRef);
575
+
576
+ if (!typeText) {
577
+ return 'Value';
578
+ }
579
+
580
+ return typeText
581
+ .replace(/[\[\]!]/g, ' ')
582
+ .trim()
583
+ .split(/\s+/)
584
+ .filter(Boolean)
585
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
586
+ .join('') || 'Value';
587
+ }
588
+
589
+ function buildOperationArgumentBindings(args, existingVariables) {
590
+ const variableRegistry = new Map();
591
+ const bindings = [];
592
+
593
+ (existingVariables || []).forEach((variable) => {
594
+ if (!variable || !variable.name) {
595
+ return;
596
+ }
597
+
598
+ variableRegistry.set(variable.name, variable.typeString || '');
599
+ });
600
+
601
+ (args || []).forEach((arg) => {
602
+ if (!arg || !arg.name) {
603
+ return;
604
+ }
605
+
606
+ const typeString = typeRefToString(arg.type);
607
+ const baseVariableName = arg.name;
608
+ const existingType = variableRegistry.get(baseVariableName);
609
+ let variableName = baseVariableName;
610
+
611
+ if (existingType && existingType !== typeString) {
612
+ variableName = `${baseVariableName}${toVariableNameSuffix(arg.type)}`;
613
+
614
+ let duplicateIndex = 2;
615
+ while (variableRegistry.has(variableName) && variableRegistry.get(variableName) !== typeString) {
616
+ variableName = `${baseVariableName}${toVariableNameSuffix(arg.type)}${duplicateIndex}`;
617
+ duplicateIndex += 1;
618
+ }
619
+ }
620
+
621
+ if (!variableRegistry.has(variableName)) {
622
+ variableRegistry.set(variableName, typeString);
623
+ }
624
+
625
+ bindings.push({
626
+ argumentName: arg.name,
627
+ variableName,
628
+ typeString
629
+ });
630
+ });
631
+
632
+ return bindings;
633
+ }
634
+
635
+ function parseOperationVariables(headerText) {
636
+ const variables = [];
637
+ const variablePattern = /\$([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([!\[\]A-Za-z0-9_]+)/g;
638
+ let match = variablePattern.exec(headerText);
639
+
640
+ while (match) {
641
+ variables.push({
642
+ name: match[1],
643
+ typeString: match[2]
644
+ });
645
+ match = variablePattern.exec(headerText);
646
+ }
647
+
648
+ return variables;
649
+ }
650
+
651
+ function resolveOperationVariable(schema, queryText, variableName) {
652
+ const operationHeaderMatch = /(query|mutation|subscription)\b([\s\S]*?)\{/.exec(queryText || '');
653
+
654
+ if (!operationHeaderMatch) {
655
+ return null;
656
+ }
657
+
658
+ const variables = parseOperationVariables(operationHeaderMatch[2] || '');
659
+ const variable = variables.find((item) => item.name === variableName);
660
+
661
+ if (!variable) {
662
+ return null;
663
+ }
664
+
665
+ return {
666
+ label: `$${variable.name}`,
667
+ labelClass: 'argument',
668
+ typeRef: parseTypeRefFromString(variable.typeString, schema),
669
+ usages: collectVariableUsages(schema, queryText, variableName),
670
+ description: ''
671
+ };
672
+ }
673
+
674
+ function collectVariableUsages(schema, text, variableName) {
675
+ const typeMap = getTypeMap(schema);
676
+ let operation = 'query';
677
+ let rootTypeName = getRootTypeName(schema, operation);
678
+ const typeStack = rootTypeName ? [rootTypeName] : [];
679
+ let pendingFieldRef = null;
680
+ let currentArgumentField = null;
681
+ let currentArgumentName = null;
682
+ let parenDepth = 0;
683
+ let i = 0;
684
+ const usages = [];
685
+
686
+ while (i < text.length) {
687
+ const char = text[i];
688
+ const nextThree = text.slice(i, i + 3);
689
+
690
+ if (nextThree === '"""') {
691
+ const end = text.indexOf('"""', i + 3);
692
+ i = end === -1 ? text.length : end + 3;
693
+ continue;
694
+ }
695
+
696
+ if (text.slice(i, i + 2) === '/*') {
697
+ const end = text.indexOf('*/', i + 2);
698
+ i = end === -1 ? text.length : end + 2;
699
+ continue;
700
+ }
701
+
702
+ if (char === '#') {
703
+ const end = text.indexOf('\n', i + 1);
704
+ i = end === -1 ? text.length : end + 1;
705
+ continue;
706
+ }
707
+
708
+ if (char === '"') {
709
+ i += 1;
710
+ while (i < text.length) {
711
+ const stringChar = text[i];
712
+
713
+ if (stringChar === '\\') {
714
+ i += 2;
715
+ continue;
716
+ }
717
+
718
+ if (stringChar === '"') {
719
+ i += 1;
720
+ break;
721
+ }
722
+
723
+ i += 1;
724
+ }
725
+
726
+ continue;
727
+ }
728
+
729
+ if (/\s/.test(char)) {
730
+ i += 1;
731
+ continue;
732
+ }
733
+
734
+ if (isNameStart(char)) {
735
+ let end = i + 1;
736
+
737
+ while (end < text.length && isNamePart(text[end])) {
738
+ end += 1;
739
+ }
740
+
741
+ const word = text.slice(i, end);
742
+
743
+ if (word === 'query' || word === 'mutation' || word === 'subscription') {
744
+ operation = word;
745
+ rootTypeName = getRootTypeName(schema, operation);
746
+ typeStack.length = 0;
747
+ if (rootTypeName) {
748
+ typeStack.push(rootTypeName);
749
+ }
750
+ pendingFieldRef = null;
751
+ currentArgumentField = null;
752
+ currentArgumentName = null;
753
+ parenDepth = 0;
754
+ i = end;
755
+ continue;
756
+ }
757
+
758
+ if (parenDepth > 0 && currentArgumentField) {
759
+ const argumentRef = (currentArgumentField.args || []).find((arg) => arg && arg.name === word);
760
+
761
+ if (argumentRef) {
762
+ currentArgumentName = word;
763
+ }
764
+ } else {
765
+ const currentTypeName = typeStack[typeStack.length - 1];
766
+ pendingFieldRef = getFieldByName(typeMap, currentTypeName, word);
767
+ }
768
+
769
+ i = end;
770
+ continue;
771
+ }
772
+
773
+ if (char === '$') {
774
+ let end = i + 1;
775
+
776
+ while (end < text.length && isNamePart(text[end])) {
777
+ end += 1;
778
+ }
779
+
780
+ const usedVariableName = text.slice(i + 1, end);
781
+
782
+ if (usedVariableName === variableName && currentArgumentField && currentArgumentName) {
783
+ usages.push({
784
+ fieldName: currentArgumentField.name,
785
+ arguments: (currentArgumentField.args || []).map((arg) => ({
786
+ name: arg.name,
787
+ typeRef: arg.type,
788
+ isHighlighted: arg.name === currentArgumentName
789
+ })),
790
+ returnTypeRef: currentArgumentField.type
791
+ });
792
+ }
793
+
794
+ i = end;
795
+ continue;
796
+ }
797
+
798
+ if (char === '(') {
799
+ parenDepth += 1;
800
+ if (pendingFieldRef) {
801
+ currentArgumentField = pendingFieldRef;
802
+ }
803
+ i += 1;
804
+ continue;
805
+ }
806
+
807
+ if (char === ')') {
808
+ parenDepth = Math.max(0, parenDepth - 1);
809
+ if (parenDepth === 0) {
810
+ currentArgumentField = null;
811
+ currentArgumentName = null;
812
+ }
813
+ i += 1;
814
+ continue;
815
+ }
816
+
817
+ if (char === '{') {
818
+ if (pendingFieldRef) {
819
+ const childType = getNamedType(pendingFieldRef.type);
820
+ if (childType && childType.name) {
821
+ typeStack.push(childType.name);
822
+ }
823
+ }
824
+ pendingFieldRef = null;
825
+ i += 1;
826
+ continue;
827
+ }
828
+
829
+ if (char === '}') {
830
+ if (typeStack.length > 1) {
831
+ typeStack.pop();
832
+ }
833
+ pendingFieldRef = null;
834
+ i += 1;
835
+ continue;
836
+ }
837
+
838
+ if (char === ',') {
839
+ currentArgumentName = null;
840
+ i += 1;
841
+ continue;
842
+ }
843
+
844
+ i += 1;
845
+ }
846
+
847
+ return usages;
848
+ }
849
+
850
+ function parseTypeRefFromString(typeString) {
851
+ if (!typeString) {
852
+ return null;
853
+ }
854
+
855
+ let index = 0;
856
+
857
+ function parseInner() {
858
+ if (typeString[index] === '[') {
859
+ index += 1;
860
+ const inner = parseInner();
861
+
862
+ if (typeString[index] === ']') {
863
+ index += 1;
864
+ }
865
+
866
+ let listType = { kind: 'LIST', ofType: inner };
867
+
868
+ if (typeString[index] === '!') {
869
+ index += 1;
870
+ listType = { kind: 'NON_NULL', ofType: listType };
871
+ }
872
+
873
+ return listType;
874
+ }
875
+
876
+ const nameMatch = /^[A-Za-z_][A-Za-z0-9_]*/.exec(typeString.slice(index));
877
+
878
+ if (!nameMatch) {
879
+ return null;
880
+ }
881
+
882
+ index += nameMatch[0].length;
883
+ let namedType = getTypeMap($ctrl.schema).get(nameMatch[0]) || { kind: 'NAMED', name: nameMatch[0] };
884
+
885
+ if (typeString[index] === '!') {
886
+ index += 1;
887
+ namedType = { kind: 'NON_NULL', ofType: namedType };
888
+ }
889
+
890
+ return namedType;
891
+ }
892
+
893
+ return parseInner();
894
+ }
895
+
896
+ function resolveQueryHoverPayload(cm, position) {
897
+ const token = cm.getTokenAt(position);
898
+
899
+ if (!token || !token.string || !token.type) {
900
+ return null;
901
+ }
902
+
903
+ const tokenText = token.string.replace(/^"+|"+$/g, '');
904
+ const textBeforeToken = cm.getValue().slice(0, cm.indexFromPos({ line: position.line, ch: token.start }));
905
+ const context = resolveQueryContext($ctrl.schema, textBeforeToken);
906
+
907
+ if (token.type.indexOf('variable-2') !== -1) {
908
+ return resolveOperationVariable($ctrl.schema, cm.getValue(), tokenText.replace(/^\$/, ''));
909
+ }
910
+
911
+ if (token.type.indexOf('type-name') !== -1) {
912
+ const schemaType = getTypeMap($ctrl.schema).get(tokenText);
913
+
914
+ if (!schemaType) {
915
+ return null;
916
+ }
917
+
918
+ return {
919
+ label: schemaType.name,
920
+ labelClass: 'type',
921
+ typeRef: schemaType,
922
+ description: schemaType.description || ''
923
+ };
924
+ }
925
+
926
+ if (token.type.indexOf('variable') !== -1 && context.inArguments && context.currentArgumentField) {
927
+ const arg = (context.currentArgumentField.args || []).find((item) => item.name === tokenText);
928
+
929
+ if (!arg) {
930
+ return null;
931
+ }
932
+
933
+ return {
934
+ label: arg.name,
935
+ labelClass: 'argument',
936
+ typeRef: arg.type,
937
+ description: arg.description || ''
938
+ };
939
+ }
940
+
941
+ if ((token.type.indexOf('property') !== -1 || token.type.indexOf('def') !== -1) && context.parentTypeName) {
942
+ const field = getFieldByName(getTypeMap($ctrl.schema), context.parentTypeName, tokenText);
943
+
944
+ if (!field) {
945
+ return null;
946
+ }
947
+
948
+ return {
949
+ label: field.name,
950
+ labelClass: 'field',
951
+ typeRef: field.type,
952
+ arguments: Array.isArray(field.args)
953
+ ? field.args.map((arg) => ({
954
+ name: arg.name,
955
+ typeRef: arg.type,
956
+ description: arg.description || ''
957
+ }))
958
+ : [],
959
+ description: field.description || ''
960
+ };
961
+ }
962
+
963
+ return null;
964
+ }
965
+
966
+ function bindHoverHandlers(cm) {
967
+ const wrapper = cm.getWrapperElement();
968
+ let lastTooltipKey = '';
969
+ let hoverTimer = null;
970
+
971
+ function clearHoverTimer() {
972
+ if (hoverTimer) {
973
+ window.clearTimeout(hoverTimer);
974
+ hoverTimer = null;
975
+ }
976
+ }
977
+
978
+ wrapper.addEventListener('mousemove', (event) => {
979
+ const target = event.target;
980
+
981
+ if (!target || !(target instanceof HTMLElement) || !target.closest('.CodeMirror-lines')) {
982
+ clearHoverTimer();
983
+ hideTooltip();
984
+ lastTooltipKey = '';
985
+ return;
986
+ }
987
+
988
+ const position = cm.coordsChar({ left: event.clientX, top: event.clientY }, 'window');
989
+ const payload = resolveQueryHoverPayload(cm, position);
990
+
991
+ if (!payload) {
992
+ clearHoverTimer();
993
+ hideTooltip();
994
+ lastTooltipKey = '';
995
+ return;
996
+ }
997
+
998
+ const nextKey = `${payload.label}|${payload.description}|${typeRefToString(payload.typeRef)}`;
999
+
1000
+ if (lastTooltipKey !== nextKey) {
1001
+ clearHoverTimer();
1002
+ hideTooltip();
1003
+ lastTooltipKey = nextKey;
1004
+ hoverTimer = window.setTimeout(() => {
1005
+ renderTooltip(payload, target, event);
1006
+ hoverTimer = null;
1007
+ }, 1000);
1008
+ return;
1009
+ }
1010
+ });
1011
+
1012
+ wrapper.addEventListener('mouseleave', () => {
1013
+ clearHoverTimer();
1014
+ lastTooltipKey = '';
1015
+ hideTooltip();
1016
+ });
1017
+ }
1018
+
1019
+ function isNameStart(char) {
1020
+ return /[A-Za-z_]/.test(char);
1021
+ }
1022
+
1023
+ function isNamePart(char) {
1024
+ return /[A-Za-z0-9_]/.test(char);
1025
+ }
1026
+
1027
+ function resolveQueryContext(schema, text) {
1028
+ const typeMap = getTypeMap(schema);
1029
+ let operation = 'query';
1030
+ let rootTypeName = getRootTypeName(schema, operation);
1031
+ const typeStack = rootTypeName ? [rootTypeName] : [];
1032
+ const fieldStack = [];
1033
+ let pendingFieldName = null;
1034
+ let pendingFieldRef = null;
1035
+ let currentArgumentField = null;
1036
+ let parenDepth = 0;
1037
+ let selectionDepth = 0;
1038
+ let lastOperationKeywordIndex = 0;
1039
+ let operationHeader = null;
1040
+ let i = 0;
1041
+
1042
+ while (i < text.length) {
1043
+ const char = text[i];
1044
+ const nextThree = text.slice(i, i + 3);
1045
+
1046
+ if (nextThree === '"""') {
1047
+ const end = text.indexOf('"""', i + 3);
1048
+ i = end === -1 ? text.length : end + 3;
1049
+ continue;
1050
+ }
1051
+
1052
+ if (text.slice(i, i + 2) === '/*') {
1053
+ const end = text.indexOf('*/', i + 2);
1054
+ i = end === -1 ? text.length : end + 2;
1055
+ continue;
1056
+ }
1057
+
1058
+ if (char === '#') {
1059
+ const end = text.indexOf('\n', i + 1);
1060
+ i = end === -1 ? text.length : end + 1;
1061
+ continue;
1062
+ }
1063
+
1064
+ if (char === '"') {
1065
+ i += 1;
1066
+ while (i < text.length) {
1067
+ const stringChar = text[i];
1068
+
1069
+ if (stringChar === '\\') {
1070
+ i += 2;
1071
+ continue;
1072
+ }
1073
+
1074
+ if (stringChar === '"') {
1075
+ i += 1;
1076
+ break;
1077
+ }
1078
+
1079
+ i += 1;
1080
+ }
1081
+
1082
+ continue;
1083
+ }
1084
+
1085
+ if (/\s/.test(char)) {
1086
+ i += 1;
1087
+ continue;
1088
+ }
1089
+
1090
+ if (char === '$') {
1091
+ i += 1;
1092
+ while (i < text.length && isNamePart(text[i])) {
1093
+ i += 1;
1094
+ }
1095
+ continue;
1096
+ }
1097
+
1098
+ if (char === '@') {
1099
+ i += 1;
1100
+ while (i < text.length && isNamePart(text[i])) {
1101
+ i += 1;
1102
+ }
1103
+ continue;
1104
+ }
1105
+
1106
+ if (isNameStart(char)) {
1107
+ let end = i + 1;
1108
+
1109
+ while (end < text.length && isNamePart(text[end])) {
1110
+ end += 1;
1111
+ }
1112
+
1113
+ const word = text.slice(i, end);
1114
+
1115
+ if (word === 'query' || word === 'mutation' || word === 'subscription') {
1116
+ operation = word;
1117
+ rootTypeName = getRootTypeName(schema, operation);
1118
+ typeStack.length = 0;
1119
+ fieldStack.length = 0;
1120
+ if (rootTypeName) {
1121
+ typeStack.push(rootTypeName);
1122
+ }
1123
+ pendingFieldName = null;
1124
+ pendingFieldRef = null;
1125
+ currentArgumentField = null;
1126
+ parenDepth = 0;
1127
+ selectionDepth = 0;
1128
+ lastOperationKeywordIndex = i;
1129
+ operationHeader = null;
1130
+ i = end;
1131
+ continue;
1132
+ }
1133
+
1134
+ if (word === 'fragment' || word === 'on') {
1135
+ i = end;
1136
+ continue;
1137
+ }
1138
+
1139
+ if (parenDepth === 0) {
1140
+ const currentTypeName = typeStack[typeStack.length - 1];
1141
+ const fieldRef = getFieldByName(typeMap, currentTypeName, word);
1142
+
1143
+ if (fieldRef) {
1144
+ pendingFieldName = word;
1145
+ pendingFieldRef = fieldRef;
1146
+ }
1147
+ }
1148
+
1149
+ i = end;
1150
+ continue;
1151
+ }
1152
+
1153
+ if (char === '(') {
1154
+ parenDepth += 1;
1155
+ currentArgumentField = pendingFieldRef;
1156
+ i += 1;
1157
+ continue;
1158
+ }
1159
+
1160
+ if (char === ')') {
1161
+ parenDepth = Math.max(0, parenDepth - 1);
1162
+ if (parenDepth === 0) {
1163
+ currentArgumentField = null;
1164
+ }
1165
+ i += 1;
1166
+ continue;
1167
+ }
1168
+
1169
+ if (char === '{') {
1170
+ selectionDepth += 1;
1171
+
1172
+ if (selectionDepth === 1 && lastOperationKeywordIndex <= i) {
1173
+ operationHeader = {
1174
+ operation,
1175
+ keywordIndex: lastOperationKeywordIndex,
1176
+ openBraceIndex: i
1177
+ };
1178
+ }
1179
+
1180
+ if (pendingFieldRef) {
1181
+ const childType = getNamedType(pendingFieldRef.type);
1182
+
1183
+ if (childType && childType.name) {
1184
+ typeStack.push(childType.name);
1185
+ fieldStack.push(pendingFieldRef.name);
1186
+ }
1187
+ }
1188
+
1189
+ pendingFieldName = null;
1190
+ pendingFieldRef = null;
1191
+ i += 1;
1192
+ continue;
1193
+ }
1194
+
1195
+ if (char === '}') {
1196
+ selectionDepth = Math.max(0, selectionDepth - 1);
1197
+
1198
+ if (typeStack.length > 1) {
1199
+ typeStack.pop();
1200
+ fieldStack.pop();
1201
+ }
1202
+
1203
+ pendingFieldName = null;
1204
+ pendingFieldRef = null;
1205
+ i += 1;
1206
+ continue;
1207
+ }
1208
+
1209
+ if (char === ',') {
1210
+ if (parenDepth === 0) {
1211
+ pendingFieldName = null;
1212
+ pendingFieldRef = null;
1213
+ }
1214
+ i += 1;
1215
+ continue;
1216
+ }
1217
+
1218
+ i += 1;
1219
+ }
1220
+
1221
+ return {
1222
+ operation,
1223
+ parentTypeName: typeStack[typeStack.length - 1] || rootTypeName,
1224
+ currentArgumentField,
1225
+ inArguments: parenDepth > 0 && Boolean(currentArgumentField),
1226
+ inSelectionSet: selectionDepth > 0 && parenDepth === 0,
1227
+ operationHeader
1228
+ };
1229
+ }
1230
+
1231
+ function supportsSelectionSet(field) {
1232
+ const namedType = getNamedType(field && field.type);
1233
+
1234
+ return Boolean(namedType && ['OBJECT', 'INTERFACE'].includes(namedType.kind));
1235
+ }
1236
+
1237
+ function hasRequiredArguments(field) {
1238
+ return Boolean(field && Array.isArray(field.args) && field.args.some((arg) => arg && arg.type && arg.type.kind === 'NON_NULL'));
1239
+ }
1240
+
1241
+ function buildExpandedSelectionSet(typeRef, indentLevel, visitedTypes) {
1242
+ const namedType = getNamedType(typeRef);
1243
+ const typeMap = getTypeMap($ctrl.schema);
1244
+ const schemaType = namedType ? typeMap.get(namedType.name) : null;
1245
+
1246
+ if (!schemaType || !Array.isArray(schemaType.fields) || !schemaType.fields.length) {
1247
+ return [];
1248
+ }
1249
+
1250
+ const nextVisited = new Set(visitedTypes || []);
1251
+ if (namedType && namedType.name) {
1252
+ nextVisited.add(namedType.name);
1253
+ }
1254
+
1255
+ return schemaType.fields
1256
+ .filter((childField) => childField && childField.name && !hasRequiredArguments(childField))
1257
+ .flatMap((childField) => {
1258
+ if (!supportsSelectionSet(childField)) {
1259
+ return [`${indentLevel}${childField.name}`];
1260
+ }
1261
+
1262
+ const childNamedType = getNamedType(childField.type);
1263
+ if (!childNamedType || nextVisited.has(childNamedType.name)) {
1264
+ return [];
1265
+ }
1266
+
1267
+ const nestedLines = buildExpandedSelectionSet(childField.type, `${indentLevel} `, nextVisited);
1268
+
1269
+ if (!nestedLines.length) {
1270
+ return [];
1271
+ }
1272
+
1273
+ return [
1274
+ `${indentLevel}${childField.name} {`,
1275
+ ...nestedLines,
1276
+ `${indentLevel}}`
1277
+ ];
1278
+ });
1279
+ }
1280
+
1281
+ function buildFieldInsertion(field) {
1282
+ if (!field || !Array.isArray(field.args) || field.args.length === 0) {
1283
+ return {
1284
+ text: field && field.name ? field.name : '',
1285
+ cursorOffset: field && field.name ? field.name.length : 0
1286
+ };
1287
+ }
1288
+
1289
+ const argsText = field.args.map((arg) => `${arg.name}: `).join(', ');
1290
+ const text = `${field.name}(${argsText})`;
1291
+
1292
+ return {
1293
+ text,
1294
+ cursorOffset: field.name.length + 1
1295
+ };
1296
+ }
1297
+
1298
+ function buildFieldApply(field) {
1299
+ return function applyFieldCompletion(cm, from, to, completion) {
1300
+ const insertion = buildFieldInsertion(field);
1301
+ const baseText = insertion.text;
1302
+ const operation = completion && completion.operation ? completion.operation : 'query';
1303
+ const shouldWrapInOperation = Boolean(completion && completion.wrapInOperation);
1304
+ const shouldBindOperationVariables = Boolean(completion && completion.bindOperationVariables);
1305
+ const shouldIncludeAllFields = Boolean(completion && completion.includeAllFields);
1306
+
1307
+ if (shouldWrapInOperation) {
1308
+ const currentValue = cm.getValue();
1309
+ const plainText = currentValue
1310
+ .replace(/\/\*[\s\S]*?\*\//g, '')
1311
+ .replace(/#.*/g, '')
1312
+ .trim();
1313
+ const shouldReplaceWholeEditor = plainText === '';
1314
+ const rootIndent = '';
1315
+ const fieldIndent = ' ';
1316
+ const childIndent = ' ';
1317
+ const argumentBindings = buildOperationArgumentBindings(field.args);
1318
+ const variableDefinitions = argumentBindings.length
1319
+ ? ` (${argumentBindings.map((binding) => `$${binding.variableName}: ${binding.typeString}`).join(', ')})`
1320
+ : '';
1321
+ const fieldArguments = argumentBindings.length
1322
+ ? `(${argumentBindings.map((binding) => `${binding.argumentName}: $${binding.variableName}`).join(', ')})`
1323
+ : '';
1324
+ const fieldCall = `${field.name}${fieldArguments}`;
1325
+ const expandedLines = shouldIncludeAllFields ? buildExpandedSelectionSet(field.type, childIndent, new Set()) : [];
1326
+ const blockText = supportsSelectionSet(field)
1327
+ ? `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall} {\n${expandedLines.length ? `${expandedLines.join('\n')}\n` : `${childIndent}\n`}${fieldIndent}}\n${rootIndent}}`
1328
+ : `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall}\n${rootIndent}}`;
1329
+ const replaceFrom = shouldReplaceWholeEditor ? { line: 0, ch: 0 } : from;
1330
+ const replaceTo = shouldReplaceWholeEditor ? { line: cm.lastLine(), ch: cm.getLine(cm.lastLine()).length } : to;
1331
+ const startIndex = cm.indexFromPos(replaceFrom);
1332
+ const cursorIndex = supportsSelectionSet(field)
1333
+ ? startIndex + `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall} {\n${expandedLines.length ? expandedLines[0] : childIndent}`.length
1334
+ : startIndex + `${operation}${variableDefinitions} {\n${fieldIndent}${fieldCall}`.length;
1335
+
1336
+ cm.replaceRange(blockText, replaceFrom, replaceTo);
1337
+ cm.setCursor(cm.posFromIndex(cursorIndex));
1338
+ return;
1339
+ }
1340
+
1341
+ if (shouldBindOperationVariables && Array.isArray(field.args) && field.args.length) {
1342
+ const currentValue = cm.getValue();
1343
+ const context = resolveQueryContext($ctrl.schema, currentValue.slice(0, cm.indexFromPos(from)));
1344
+ const header = context.operationHeader;
1345
+ const headerText = header ? currentValue.slice(header.keywordIndex, header.openBraceIndex) : '';
1346
+ const existingVariables = parseOperationVariables(headerText);
1347
+ const argumentBindings = buildOperationArgumentBindings(field.args, existingVariables);
1348
+ const fieldArguments = argumentBindings.length
1349
+ ? `(${argumentBindings.map((binding) => `${binding.argumentName}: $${binding.variableName}`).join(', ')})`
1350
+ : '';
1351
+ const fieldCall = `${field.name}${fieldArguments}`;
1352
+
1353
+ cm.replaceRange(fieldCall, from, to);
1354
+
1355
+ if (header) {
1356
+ const nextValue = cm.getValue();
1357
+ const updatedHeaderText = nextValue.slice(header.keywordIndex, header.openBraceIndex);
1358
+ const nextExistingVariables = parseOperationVariables(updatedHeaderText);
1359
+ const newBindings = argumentBindings.filter((binding) => !nextExistingVariables.some((variable) => variable.name === binding.variableName));
1360
+
1361
+ if (newBindings.length) {
1362
+ const definitionsText = newBindings.map((binding) => `$${binding.variableName}: ${binding.typeString}`).join(', ');
1363
+ const openParenIndex = updatedHeaderText.indexOf('(');
1364
+ const insertIndex = header.keywordIndex + updatedHeaderText.length;
1365
+
1366
+ if (openParenIndex === -1) {
1367
+ cm.replaceRange(` (${definitionsText})`, cm.posFromIndex(insertIndex), cm.posFromIndex(insertIndex));
1368
+ } else {
1369
+ const closeParenIndex = updatedHeaderText.lastIndexOf(')');
1370
+ const absoluteCloseParenIndex = header.keywordIndex + closeParenIndex;
1371
+ const prefixVariables = updatedHeaderText.slice(openParenIndex + 1, closeParenIndex).trim();
1372
+ const separator = prefixVariables ? ', ' : '';
1373
+ cm.replaceRange(`${separator}${definitionsText}`, cm.posFromIndex(absoluteCloseParenIndex), cm.posFromIndex(absoluteCloseParenIndex));
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ if (!supportsSelectionSet(field)) {
1379
+ cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + fieldCall.length));
1380
+ return;
1381
+ }
1382
+
1383
+ const updatedFrom = from;
1384
+ const lineText = cm.getLine(updatedFrom.line) || '';
1385
+ const lineIndent = (lineText.match(/^\s*/) || [''])[0];
1386
+ const indentUnit = cm.getOption('indentUnit') || 2;
1387
+ const childIndent = `${lineIndent}${' '.repeat(indentUnit)}`;
1388
+ const expandedLines = shouldIncludeAllFields ? buildExpandedSelectionSet(field.type, childIndent, new Set()) : [];
1389
+ const blockText = `${fieldCall} {\n${expandedLines.length ? `${expandedLines.join('\n')}\n` : `${childIndent}\n`}${lineIndent}}`;
1390
+ const startIndex = cm.indexFromPos(updatedFrom);
1391
+ const cursorIndex = startIndex + `${fieldCall} {\n${expandedLines.length ? expandedLines[0] : childIndent}`.length;
1392
+
1393
+ cm.replaceRange(blockText, updatedFrom, cm.posFromIndex(startIndex + fieldCall.length));
1394
+ cm.setCursor(cm.posFromIndex(cursorIndex));
1395
+ return;
1396
+ }
1397
+
1398
+ if (!supportsSelectionSet(field)) {
1399
+ cm.replaceRange(baseText, from, to);
1400
+ cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + insertion.cursorOffset));
1401
+ return;
1402
+ }
1403
+
1404
+ const lineText = cm.getLine(from.line) || '';
1405
+ const lineIndent = (lineText.match(/^\s*/) || [''])[0];
1406
+ const indentUnit = cm.getOption('indentUnit') || 2;
1407
+ const childIndent = `${lineIndent}${' '.repeat(indentUnit)}`;
1408
+ const blockText = `${baseText} {\n${childIndent}\n${lineIndent}}`;
1409
+ const startIndex = cm.indexFromPos(from);
1410
+ const cursorIndex = startIndex + `${baseText} {\n${childIndent}`.length;
1411
+
1412
+ cm.replaceRange(blockText, from, to);
1413
+ cm.setCursor(cm.posFromIndex(cursorIndex));
1414
+ };
1415
+ }
1416
+
1417
+ function buildOperationNameApply(field) {
1418
+ return function applyOperationNameCompletion(cm, from, to) {
1419
+ const operationName = field && field.name ? field.name : '';
1420
+
1421
+ if (!operationName) {
1422
+ return;
1423
+ }
1424
+
1425
+ cm.replaceRange(operationName, from, to);
1426
+ cm.setCursor(cm.posFromIndex(cm.indexFromPos(from) + operationName.length));
1427
+ };
1428
+ }
1429
+
1430
+ function buildArgInsertion(arg) {
1431
+ return {
1432
+ text: `${arg.name}: `,
1433
+ cursorOffset: arg.name.length + 2
1434
+ };
1435
+ }
1436
+
1437
+ function getSchemaCompletions(schema) {
1438
+ const completionsByKey = new Map();
1439
+ const rootQueryTypeName = getRootTypeName(schema, 'query');
1440
+ const rootMutationTypeName = getRootTypeName(schema, 'mutation');
1441
+ const rootSubscriptionTypeName = getRootTypeName(schema, 'subscription');
1442
+
1443
+ function pushCompletion(completion) {
1444
+ if (!completion || !completion.text) {
1445
+ return;
1446
+ }
1447
+
1448
+ const key = `${completion.text}::${completion.displayText || ''}::${completion.className || ''}`;
1449
+
1450
+ if (!completionsByKey.has(key)) {
1451
+ completionsByKey.set(key, completion);
1452
+ }
1453
+ }
1454
+
1455
+ graphqlKeywords.forEach((keyword) => {
1456
+ pushCompletion(createCompletion(keyword, 'keyword', keyword, 'keyword'));
1457
+ });
1458
+
1459
+ if (!schema || !Array.isArray(schema.types)) {
1460
+ return Array.from(completionsByKey.values());
1461
+ }
1462
+
1463
+ schema.types.forEach((type) => {
1464
+ if (!type || !type.name || type.name.startsWith('__')) {
1465
+ return;
1466
+ }
1467
+
1468
+ pushCompletion(createCompletion(type.name, type.kind ? type.kind.toLowerCase() : 'type', `${type.name} (${type.kind})`, 'type'));
1469
+
1470
+ if (Array.isArray(type.fields)) {
1471
+ type.fields.forEach((field) => {
1472
+ if (!field || !field.name) {
1473
+ return;
1474
+ }
1475
+
1476
+ const insertion = buildFieldInsertion(field);
1477
+ const namedType = getNamedType(field.type);
1478
+ const renderText = namedType ? `${field.name} : ${namedType.name}` : field.name;
1479
+ const completion = createCompletion(insertion.text, 'field', renderText, 'field');
1480
+ completion.cursorOffset = insertion.cursorOffset;
1481
+
1482
+ if (type.name === rootQueryTypeName || type.name === rootMutationTypeName || type.name === rootSubscriptionTypeName) {
1483
+ completion.apply = buildFieldApply(field);
1484
+ completion.wrapInOperation = true;
1485
+ completion.operation = type.name === rootMutationTypeName
1486
+ ? 'mutation'
1487
+ : type.name === rootSubscriptionTypeName
1488
+ ? 'subscription'
1489
+ : 'query';
1490
+ }
1491
+
1492
+ pushCompletion(completion);
1493
+
1494
+ if (Array.isArray(field.args)) {
1495
+ field.args.forEach((arg) => {
1496
+ if (!arg || !arg.name) {
1497
+ return;
1498
+ }
1499
+
1500
+ const argInsertion = buildArgInsertion(arg);
1501
+ const argType = getNamedType(arg.type);
1502
+ const argRenderText = argType ? `${arg.name}: ${argType.name}` : `${arg.name}:`;
1503
+ const argCompletion = createCompletion(argInsertion.text, 'argument', argRenderText, 'argument');
1504
+ argCompletion.cursorOffset = argInsertion.cursorOffset;
1505
+ pushCompletion(argCompletion);
1506
+ });
1507
+ }
1508
+ });
1509
+ }
1510
+
1511
+ if (Array.isArray(type.inputFields)) {
1512
+ type.inputFields.forEach((field) => {
1513
+ if (!field || !field.name) {
1514
+ return;
1515
+ }
1516
+
1517
+ const insertion = buildArgInsertion(field);
1518
+ const namedType = getNamedType(field.type);
1519
+ const renderText = namedType ? `${field.name}: ${namedType.name}` : `${field.name}:`;
1520
+ const completion = createCompletion(insertion.text, 'input', renderText, 'argument');
1521
+ completion.cursorOffset = insertion.cursorOffset;
1522
+ pushCompletion(completion);
1523
+ });
1524
+ }
1525
+
1526
+ if (Array.isArray(type.enumValues)) {
1527
+ type.enumValues.forEach((value) => {
1528
+ if (!value || !value.name) {
1529
+ return;
1530
+ }
1531
+
1532
+ pushCompletion(createCompletion(value.name, 'enum', `${value.name} (enum)`, 'enum'));
1533
+ });
1534
+ }
1535
+ });
1536
+
1537
+ return Array.from(completionsByKey.values());
1538
+ }
1539
+
1540
+ function getFieldCompletionsForType(schema, typeName, options) {
1541
+ const typeMap = getTypeMap(schema);
1542
+ const typeRef = typeMap.get(typeName);
1543
+
1544
+ if (!typeRef || !Array.isArray(typeRef.fields)) {
1545
+ return [];
1546
+ }
1547
+
1548
+ return typeRef.fields
1549
+ .filter((field) => field && field.name)
1550
+ .flatMap((field) => {
1551
+ const insertion = buildFieldInsertion(field);
1552
+ const namedType = getNamedType(field.type);
1553
+ const renderText = namedType ? `${field.name} : ${namedType.name}` : field.name;
1554
+ const completion = createCompletion(insertion.text, 'field', renderText, 'field');
1555
+ completion.cursorOffset = insertion.cursorOffset;
1556
+ completion.apply = buildFieldApply(field);
1557
+ completion.wrapInOperation = Boolean(options && options.wrapInOperation);
1558
+ completion.bindOperationVariables = Boolean(options && options.bindOperationVariables);
1559
+ completion.operation = options && options.operation ? options.operation : 'query';
1560
+ const completions = [completion];
1561
+
1562
+ if (supportsSelectionSet(field)) {
1563
+ const includeAllCompletion = createCompletion(insertion.text, 'field', `* ${renderText} (add all fields)`, 'field');
1564
+ includeAllCompletion.cursorOffset = insertion.cursorOffset;
1565
+ includeAllCompletion.apply = buildFieldApply(field);
1566
+ includeAllCompletion.wrapInOperation = Boolean(options && options.wrapInOperation);
1567
+ includeAllCompletion.bindOperationVariables = Boolean(options && options.bindOperationVariables);
1568
+ includeAllCompletion.operation = options && options.operation ? options.operation : 'query';
1569
+ includeAllCompletion.includeAllFields = true;
1570
+ completions.push(includeAllCompletion);
1571
+ }
1572
+
1573
+ return completions;
1574
+ });
1575
+ }
1576
+
1577
+ function getArgumentCompletions(fieldRef) {
1578
+ if (!fieldRef || !Array.isArray(fieldRef.args)) {
1579
+ return [];
1580
+ }
1581
+
1582
+ return fieldRef.args
1583
+ .filter((arg) => arg && arg.name)
1584
+ .map((arg) => {
1585
+ const insertion = buildArgInsertion(arg);
1586
+ const argType = getNamedType(arg.type);
1587
+ const renderText = argType ? `${arg.name}: ${argType.name}` : `${arg.name}:`;
1588
+ const completion = createCompletion(insertion.text, 'argument', renderText, 'argument');
1589
+ completion.cursorOffset = insertion.cursorOffset;
1590
+ return completion;
1591
+ });
1592
+ }
1593
+
1594
+ function getContextualCompletions(schema, queryText, cursorIndex) {
1595
+ if (!schema) {
1596
+ return getSchemaCompletions(schema);
1597
+ }
1598
+
1599
+ const context = resolveQueryContext(schema, queryText.slice(0, cursorIndex));
1600
+ const rootTypeName = getRootTypeName(schema, context.operation);
1601
+ const queryPrefix = queryText.slice(0, cursorIndex);
1602
+ const querySuffix = queryText.slice(cursorIndex);
1603
+ const cleanedPrefix = queryPrefix.replace(/\/\*[\s\S]*?\*\//g, '').replace(/#.*/g, '');
1604
+ const cleanedSuffix = querySuffix.replace(/\/\*[\s\S]*?\*\//g, '').replace(/#.*/g, '');
1605
+ const normalizedPrefix = cleanedPrefix.trim();
1606
+ const normalizedSuffix = cleanedSuffix.trim();
1607
+ const isOperationNameContext = !context.inArguments
1608
+ && !context.inSelectionSet
1609
+ && Boolean(rootTypeName)
1610
+ && /\b(query|mutation|subscription)\s+[A-Za-z_]*$/i.test(cleanedPrefix)
1611
+ && /^\s*(\([^{}]*\))?\s*\{/.test(cleanedSuffix);
1612
+ const isTopLevelRootSelection = !context.inArguments
1613
+ && !context.inSelectionSet
1614
+ && context.parentTypeName
1615
+ && context.parentTypeName === rootTypeName
1616
+ && normalizedSuffix === ''
1617
+ && (normalizedPrefix === '' || /^[A-Za-z_][A-Za-z0-9_]*$/.test(normalizedPrefix));
1618
+
1619
+ if (context.inArguments && context.currentArgumentField) {
1620
+ return getArgumentCompletions(context.currentArgumentField);
1621
+ }
1622
+
1623
+ if (context.inSelectionSet && context.parentTypeName) {
1624
+ return getFieldCompletionsForType(schema, context.parentTypeName, {
1625
+ bindOperationVariables: context.parentTypeName === rootTypeName,
1626
+ operation: context.operation
1627
+ });
1628
+ }
1629
+
1630
+ if (isOperationNameContext && rootTypeName) {
1631
+ return getFieldCompletionsForType(schema, rootTypeName, {
1632
+ operation: context.operation
1633
+ }).filter((completion) => !completion.includeAllFields).map((completion) => {
1634
+ const operationName = completion.text.replace(/\(.*/, '');
1635
+ const operationNameCompletion = { ...completion };
1636
+ operationNameCompletion.text = operationName;
1637
+ operationNameCompletion.displayText = operationName;
1638
+ operationNameCompletion.apply = buildOperationNameApply({
1639
+ name: operationName
1640
+ });
1641
+ delete operationNameCompletion.wrapInOperation;
1642
+ delete operationNameCompletion.bindOperationVariables;
1643
+ delete operationNameCompletion.includeAllFields;
1644
+ delete operationNameCompletion.cursorOffset;
1645
+ return operationNameCompletion;
1646
+ });
1647
+ }
1648
+
1649
+ if (isTopLevelRootSelection && rootTypeName) {
1650
+ const keywordCompletions = graphqlKeywords
1651
+ .filter((keyword) => ['query', 'mutation', 'subscription'].includes(keyword))
1652
+ .map((keyword) => createCompletion(keyword, 'keyword', keyword, 'keyword'));
1653
+ return [
1654
+ ...getFieldCompletionsForType(schema, rootTypeName, {
1655
+ wrapInOperation: true,
1656
+ operation: context.operation
1657
+ }),
1658
+ ...keywordCompletions
1659
+ ];
1660
+ }
1661
+
1662
+ return getSchemaCompletions(schema);
1663
+ }
1664
+
1665
+ function getHints(cm) {
1666
+ const cursor = cm.getCursor();
1667
+ const token = cm.getTokenAt(cursor);
1668
+ const tokenStart = (token && typeof token.start === 'number') ? token.start : cursor.ch;
1669
+ const tokenEnd = (token && typeof token.end === 'number') ? token.end : cursor.ch;
1670
+ const rawToken = token && typeof token.string === 'string' ? token.string : '';
1671
+ const normalizedToken = /^[\w_]+$/.test(rawToken) ? rawToken : '';
1672
+ const prefix = normalizedToken.slice(0, Math.max(0, cursor.ch - tokenStart));
1673
+ const from = { line: cursor.line, ch: prefix ? tokenStart : cursor.ch };
1674
+ const to = { line: cursor.line, ch: prefix ? tokenEnd : cursor.ch };
1675
+ const cursorIndex = cm.indexFromPos(cursor);
1676
+ const allCompletions = getContextualCompletions($ctrl.schema, cm.getValue(), cursorIndex);
1677
+ const filtered = allCompletions.filter((completion) => {
1678
+ if (!prefix) {
1679
+ return true;
1680
+ }
1681
+
1682
+ return completion.text.toLowerCase().startsWith(prefix.toLowerCase());
1683
+ });
1684
+
1685
+ return {
1686
+ list: filtered.slice(0, 200),
1687
+ from,
1688
+ to
1689
+ };
1690
+ }
1691
+
1692
+ function triggerAutocomplete(cm) {
1693
+ if (typeof cm.showHint !== 'function') {
1694
+ return;
1695
+ }
1696
+
1697
+ cm.showHint({
1698
+ completeSingle: false,
1699
+ hint: getHints
1700
+ });
1701
+ }
1702
+
1703
+ function resetToOperation(operationName) {
1704
+ if (!editor) {
1705
+ return;
1706
+ }
1707
+
1708
+ const normalizedOperation = operationName === 'mutation' ? 'mutation' : 'query';
1709
+ const nextValue = `${normalizedOperation} {\n \n}`;
1710
+
1711
+ editor.setValue(nextValue);
1712
+ editor.setCursor({ line: 1, ch: 2 });
1713
+ $ctrl.query = nextValue;
1714
+ $ctrl._lastAppliedQuery = nextValue;
1715
+ $ctrl._lastEditorValue = nextValue;
1716
+
1717
+ if ($ctrl.onChange) {
1718
+ $ctrl.onChange();
1719
+ }
1720
+
1721
+ $ctrl._syncParent();
1722
+ }
1723
+
1724
+ function createEditor() {
1725
+ const textarea = $element[0].querySelector('textarea');
1726
+
1727
+ if (!textarea || typeof window.CodeMirror === 'undefined') {
1728
+ return;
1729
+ }
1730
+
1731
+ ensureGraphqlMode();
1732
+
1733
+ editor = window.CodeMirror.fromTextArea(textarea, {
1734
+ mode: 'graphql-playground',
1735
+ lineNumbers: true,
1736
+ lineWrapping: true,
1737
+ matchBrackets: true,
1738
+ indentUnit: 2,
1739
+ tabSize: 2,
1740
+ viewportMargin: Infinity,
1741
+ extraKeys: {
1742
+ Tab(cm) {
1743
+ if (cm.state.completionActive) {
1744
+ cm.execCommand('pick');
1745
+ return;
1746
+ }
1747
+
1748
+ if (cm.somethingSelected()) {
1749
+ cm.indentSelection('add');
1750
+ return;
1751
+ }
1752
+
1753
+ cm.replaceSelection(' ', 'end');
1754
+ },
1755
+ 'Shift-Tab'(cm) {
1756
+ cm.indentSelection('subtract');
1757
+ },
1758
+ 'Ctrl-Space'(cm) {
1759
+ triggerAutocomplete(cm);
1760
+ }
1761
+ }
1762
+ });
1763
+
1764
+ editor.setValue($ctrl.query || '');
1765
+
1766
+ editor.on('change', (instance) => {
1767
+ const nextValue = instance.getValue();
1768
+
1769
+ if ($ctrl.query === nextValue) {
1770
+ return;
1771
+ }
1772
+
1773
+ $ctrl.query = nextValue;
1774
+ $ctrl._lastEditorValue = nextValue;
1775
+ $ctrl._lastAppliedQuery = nextValue;
1776
+ if ($ctrl.onChange) {
1777
+ $ctrl.onChange();
1778
+ }
1779
+ $ctrl._syncParent();
1780
+ });
1781
+
1782
+ editor.on('inputRead', (cm, change) => {
1783
+ if (!change || change.origin === 'setValue' || typeof cm.showHint !== 'function') {
1784
+ return;
1785
+ }
1786
+
1787
+ const insertedText = Array.isArray(change.text) ? change.text.join('') : '';
1788
+
1789
+ if (/^[A-Za-z_]$/.test(insertedText)) {
1790
+ triggerAutocomplete(cm);
1791
+ }
1792
+ });
1793
+
1794
+ bindHoverHandlers(editor);
1795
+ $ctrl._lastEditorValue = editor.getValue();
1796
+ $ctrl._lastAppliedQuery = editor.getValue();
1797
+ scheduleRefresh();
1798
+ }
1799
+
1800
+ function scheduleRefresh() {
1801
+ if (!editor) {
1802
+ return;
1803
+ }
1804
+
1805
+ window.requestAnimationFrame(() => {
1806
+ if (editor) {
1807
+ editor.refresh();
1808
+ }
1809
+ });
1810
+ }
1811
+
1812
+ $ctrl._syncParent = function () {
1813
+ const rootScope = $element.scope();
1814
+
1815
+ if (!rootScope) {
1816
+ return;
1817
+ }
1818
+
1819
+ if (rootScope.$$phase) {
1820
+ return;
1821
+ }
1822
+
1823
+ rootScope.$applyAsync();
1824
+ };
1825
+
1826
+ $ctrl.$postLink = function () {
1827
+ $ctrl.api = $ctrl.api || {};
1828
+ $ctrl.api.resetToOperation = resetToOperation;
1829
+ $ctrl.api.refresh = scheduleRefresh;
1830
+ createEditor();
1831
+ resizeHandler = () => scheduleRefresh();
1832
+ window.addEventListener('resize', resizeHandler);
1833
+ window.setTimeout(scheduleRefresh, 0);
1834
+ window.setTimeout(scheduleRefresh, 120);
1835
+ };
1836
+
1837
+ $ctrl.$onChanges = function (changes) {
1838
+ if (!editor || !changes.query) {
1839
+ return;
1840
+ }
1841
+
1842
+ const nextValue = changes.query.currentValue || '';
1843
+
1844
+ if (nextValue === $ctrl._lastAppliedQuery || nextValue === editor.getValue()) {
1845
+ return;
1846
+ }
1847
+
1848
+ const cursor = editor.getCursor();
1849
+ editor.setValue(nextValue);
1850
+ editor.setCursor(cursor);
1851
+ $ctrl._lastAppliedQuery = nextValue;
1852
+ $ctrl._lastEditorValue = nextValue;
1853
+ };
1854
+
1855
+ $ctrl.$doCheck = function () {
1856
+ if (!editor) {
1857
+ return;
1858
+ }
1859
+
1860
+ const nextValue = typeof $ctrl.query === 'string' ? $ctrl.query : '';
1861
+
1862
+ if (nextValue === editor.getValue() || nextValue === $ctrl._lastAppliedQuery) {
1863
+ return;
1864
+ }
1865
+
1866
+ const cursor = editor.getCursor();
1867
+ editor.setValue(nextValue);
1868
+ editor.setCursor(cursor);
1869
+ $ctrl._lastAppliedQuery = nextValue;
1870
+ $ctrl._lastEditorValue = nextValue;
1871
+ };
1872
+
1873
+ $ctrl.$onDestroy = function () {
1874
+ if (resizeHandler) {
1875
+ window.removeEventListener('resize', resizeHandler);
1876
+ resizeHandler = null;
1877
+ }
1878
+
1879
+ if (editor) {
1880
+ hideTooltip();
1881
+ editor.toTextArea();
1882
+ editor = null;
1883
+ }
1884
+
1885
+ if (tooltipElement && tooltipElement.parentNode) {
1886
+ if (hideTooltipTimeout) {
1887
+ window.clearTimeout(hideTooltipTimeout);
1888
+ hideTooltipTimeout = null;
1889
+ }
1890
+ tooltipElement.parentNode.removeChild(tooltipElement);
1891
+ tooltipElement = null;
1892
+ }
1893
+ };
1894
+ }]
1895
+ });
1896
+ })(angular.module('app'));