Skip to content

Commit

Permalink
feat: path highlight and autocompletion (#88)
Browse files Browse the repository at this point in the history
feat: path highlight and autocompletion
- [x] path highlight
- [x] path keywords autocompletion


obsolete now.

Refs: #57

---------

Signed-off-by: seven <zilisheng1996@gmail.com>
  • Loading branch information
Blankll committed Aug 19, 2024
1 parent 03eac40 commit 3b6bccb
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 194 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions src/common/monaco/completion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as monaco from 'monaco-editor';
import { keywords } from './lexerRules.ts';

const provideMethodCompletionItems = (lineContent: string) => {
const methods = new Map<RegExp, string>([
[/^ge?t?$/gi, 'GET '],
[/^put?$/gi, 'PUT '],
[/^pos?t?$/gi, 'POST '],
[/^de?l?e?t?e?$/gi, 'DELETE '],
]);
const matchedMethodKey = Array.from(methods.keys()).find(regex => regex.test(lineContent));
console.log('matchedMethodKey', { matchedMethodKey, lineContent });
if (!matchedMethodKey) {
return null;
}

const method = methods.get(matchedMethodKey);

return {
suggestions: [
{
label: method,
kind: monaco.languages.CompletionItemKind.Constant,
insertText: method,
},
],
};
};

const provideKeywordCompletionItems = (lineContent: string) => {
const word = lineContent.split(/[ /]+/).pop() || '';
const suggestions = keywords
.filter(keyword => {
return keyword.startsWith(word);
})
.map(keyword => ({
label: keyword,
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: keyword,
}));
return { suggestions };
};

export const searchCompletionProvider = (
model: monaco.editor.ITextModel,
position: monaco.Position,
) => {
const textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});

const methodCompletions = provideMethodCompletionItems(textUntilPosition);
if (methodCompletions) {
return methodCompletions;
}
const keywordCompletions = provideKeywordCompletionItems(textUntilPosition);

if (keywordCompletions) {
return keywordCompletions;
}
};
45 changes: 4 additions & 41 deletions src/common/monaco/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as monaco from 'monaco-editor';

import { search, executeActions } from './lexerRules.ts';
import { executeActions, search } from './lexerRules.ts';
import { monacoEnvironment } from './environment.ts';
import { buildSearchToken } from './tokenlizer.ts';
import { searchCompletionProvider } from './completion.ts';

self.MonacoEnvironment = monacoEnvironment;

Expand All @@ -19,47 +20,9 @@ monaco.languages.setLanguageConfiguration(
monaco.languages.registerCompletionItemProvider(search.id, {
triggerCharacters: ['g', 'p', 'd'],
// @ts-ignore
provideCompletionItems: function (model, position) {
const textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column,
});

const methods = new Map<RegExp, string>([
[/^ge?t?$/gi, 'GET '],
[/^put?$/gi, 'PUT '],
[/^pos?t?$/gi, 'POST '],
[/^de?l?e?t?e?$/gi, 'DELETE '],
]);
const matchedMethodKey = Array.from(methods.keys()).find(regex =>
regex.test(textUntilPosition),
);
if (!matchedMethodKey) {
return;
}

const method = methods.get(matchedMethodKey);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: position.column - method!.length,
endColumn: position.column,
};

return {
suggestions: [
{
label: method,
kind: monaco.languages.CompletionItemKind.Constant,
insertText: method,
range: range,
},
],
};
},
provideCompletionItems: searchCompletionProvider,
});

export * from './type.ts';
export { monaco, executeActions, buildSearchToken };
export * from './referDoc.ts';
215 changes: 95 additions & 120 deletions src/common/monaco/lexerRules.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,9 @@
export const xJson = {
id: 'xjson',
rules: {
defaultToken: 'invalid',
tokenPostfix: '',
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
tokenizer: {
root: [
[
/("(?:[^"]*_)?script"|"inline"|"source")(\s*?)(:)(\s*?)(""")/,
[
'variable',
'whitespace',
'ace.punctuation.colon',
'whitespace',
{
token: 'punctuation.start_triple_quote',
nextEmbedded: 'painless',
next: 'my_painless',
},
],
],
[
/(:)(\s*?)(""")(sql)/,
[
'ace.punctuation.colon',
'whitespace',
'punctuation.start_triple_quote',
{
token: 'punctuation.start_triple_quote.lang_marker',
nextEmbedded: 'opensearchql',
next: 'my_sql',
},
],
],
[/{/, { token: 'paren.lparen', next: '@push' }],
[/}/, { token: 'paren.rparen', next: '@pop' }],
[/[[(]/, { token: 'paren.lparen' }],
[/[\])]/, { token: 'paren.rparen' }],
[/,/, { token: 'punctuation.comma' }],
[/:/, { token: 'punctuation.colon' }],
[/\s+/, { token: 'whitespace' }],
[/["](?:(?:\\.)|(?:[^"\\]))*?["]\s*(?=:)/, { token: 'variable' }],
[/"""/, { token: 'string_literal', next: 'string_literal' }],
[/0[xX][0-9a-fA-F]+\b/, { token: 'constant.numeric' }],
[/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/, { token: 'constant.numeric' }],
[/(?:true|false)\b/, { token: 'constant.language.boolean' }],
// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
[
/"/,
{
token: 'string.quote',
bracket: '@open',
next: '@string',
},
],
[/['](?:(?:\\.)|(?:[^'\\]))*?[']/, { token: 'invalid' }],
[/.+?/, { token: 'text' }],
[/\/\/.*$/, { token: 'invalid' }],
],

my_painless: [
[
/"""/,
{
token: 'punctuation.end_triple_quote',
nextEmbedded: '@pop',
next: '@pop',
},
],
],

my_sql: [
[
/"""/,
{
token: 'punctuation.end_triple_quote',
nextEmbedded: '@pop',
next: '@pop',
},
],
],

string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
],

string_literal: [
[/"""/, { token: 'punctuation.end_triple_quote', next: '@pop' }],
[/./, { token: 'multi_string' }],
],
},
},
};
export const executeActions = {
regexp: /^(GET|DELETE|POST|PUT)\s\w+/,
decorationClassName: 'action-execute-decoration',
};
const keywords = [

export const keywords = [
'GET',
'POST',
'PUT',
Expand All @@ -117,6 +20,12 @@ const keywords = [
'size',
'explain',
'analyze',
'query',
'filter',
'aggs',
'sort',
'match',
'match_all',
'default_operator',
'df',
'analyzer',
Expand Down Expand Up @@ -198,6 +107,7 @@ const keywords = [
'version',
'version_type',
'_search',
'_cat',
'_count',
'_mapping',
'_cluster',
Expand Down Expand Up @@ -230,16 +140,16 @@ export const search = {
// The main tokenizer for our languages
tokenizer: {
root: [
[/^(GET|POST|PUT|DELETE)(\s+[a-zA-Z0-9_\/-?\-&,]*)/, ['type', 'regexp']],
{
regex: '{',
action: {
token: 'paren.lparen',
next: 'json',
next: 'xjson',
},
},
{ include: 'common' },
],
constant: [[executeActions.regexp, executeActions.decorationClassName]],
common: [
// identifiers and keywords
[
Expand All @@ -256,30 +166,95 @@ export const search = {
// whitespace
{ include: '@whitespace' },
// json block
{ include: '@json' },
{ include: '@xjson' },
],
xjson: [
[
/("(?:[^"]*_)?script"|"inline"|"source")(\s*?)(:)(\s*?)(""")/,
[
'variable',
'whitespace',
'delimiter',
'whitespace',
{
token: 'punctuation.start_triple_quote',
nextEmbedded: 'painless',
next: 'search_painless',
},
],
],
[
/(:)(\s*?)(""")(sql)/,
[
'delimiter',
'whitespace',
'punctuation.start_triple_quote',
{
token: 'punctuation.start_triple_quote.lang_marker',
nextEmbedded: 'opensearchql',
next: 'search_sql',
},
],
],
[/{/, { token: 'paren.lparen', next: '@push' }],
[/}/, { token: 'paren.rparen', next: '@pop' }],
[/[[(]/, { token: 'paren.lparen' }],
[/[\])]/, { token: 'paren.rparen' }],
[/,/, { token: 'delimiter' }],
[/:/, { token: 'delimiter' }],
[/\s+/, { token: 'whitespace' }],
[/["](?:(?:\\.)|(?:[^"\\]))*?["]\s*(?=:)/, { token: 'variable' }],
[/"""/, { token: 'string_literal', next: 'string_literal' }],
[/0[xX][0-9a-fA-F]+\b/, { token: 'constant.numeric' }],
[/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/, { token: 'constant.numeric' }],
[/(?:true|false)\b/, { token: 'constant.language.boolean' }],
// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
[
/"/,
{
token: 'string.quote',
bracket: '@open',
next: '@string',
},
],
[/['](?:(?:\\.)|(?:[^'\\]))*?[']/, { token: 'invalid' }],
[/.+?/, { token: 'text' }],
[/\/\/.*$/, { token: 'invalid' }],
],

json: [
// JSON strings
[/"(?:\\.|[^\\"])*"/, 'string'],

// JSON numbers
[/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, 'number'],

// JSON booleans
[/\b(?:true|false)\b/, 'keyword'],

// JSON null
[/\bnull\b/, 'keyword'],
search_painless: [
[
/"""/,
{
token: 'punctuation.end_triple_quote',
nextEmbedded: '@pop',
next: '@pop',
},
],
],

// JSON property names
[/"(?:\\.|[^\\"])*"(?=\s*:)/, 'key'],
search_sql: [
[
/"""/,
{
token: 'punctuation.end_triple_quote',
nextEmbedded: '@pop',
next: '@pop',
},
],
],

// JSON punctuation
[/[{}[\],:]/, 'delimiter'],
string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
],

// JSON whitespace
{ include: '@whitespace' },
string_literal: [
[/"""/, { token: 'punctuation.end_triple_quote', next: '@pop' }],
[/./, { token: 'multi_string' }],
],

whitespace: [
Expand Down
Loading

0 comments on commit 3b6bccb

Please sign in to comment.