Skip to content

Commit a3278c0

Browse files
author
Dominique Chuo
committed
better language support
1 parent b1317d8 commit a3278c0

File tree

8 files changed

+230
-107
lines changed

8 files changed

+230
-107
lines changed

docs/getting-started/interactive-queries.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ Learn how to query data in ReifyDB with interactive examples. You can run these
1414

1515
The most fundamental query in ReifyDB is the SELECT statement. Try running this query to see all users:
1616

17-
<InlineCodeBlock
18-
query="Map {1}"
19-
/>
17+
<InlineCodeBlock query="map {1}"/>
2018

2119
### Filtering with WHERE
2220

src/components/CodeEditor/index.tsx

Lines changed: 21 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { forwardRef, useImperativeHandle, useRef, useEffect } from 'react
22
import Editor, { Monaco } from '@monaco-editor/react';
33
import { editor } from 'monaco-editor';
44
import styles from './styles.module.css';
5+
import { rqlLanguageDefinition, rqlLanguageConfiguration, rqlCompletionProvider } from './rqlLanguage';
56

67
interface CodeEditorProps {
78
value: string;
@@ -19,7 +20,7 @@ export interface CodeEditorRef {
1920
}
2021

2122
const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>(
22-
({ value, onChange, onExecute, language = 'sql', theme = 'brutalist-light', readOnly = false }, ref) => {
23+
({ value, onChange, onExecute, language = 'rql', theme = 'brutalist-light', readOnly = false }, ref) => {
2324
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
2425
const monacoRef = useRef<Monaco | null>(null);
2526
const onExecuteRef = useRef<(() => void) | undefined>(onExecute);
@@ -51,18 +52,33 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>(
5152
}));
5253

5354
const handleBeforeMount = (monaco: Monaco) => {
55+
// Register RQL language only if not already registered
56+
const languages = monaco.languages.getLanguages();
57+
const rqlRegistered = languages.some(lang => lang.id === 'rql');
58+
59+
if (!rqlRegistered) {
60+
monaco.languages.register({ id: 'rql' });
61+
monaco.languages.setMonarchTokensProvider('rql', rqlLanguageDefinition);
62+
monaco.languages.setLanguageConfiguration('rql', rqlLanguageConfiguration);
63+
monaco.languages.registerCompletionItemProvider('rql', rqlCompletionProvider);
64+
}
65+
5466
// Define brutalist light theme
5567
monaco.editor.defineTheme('brutalist-light', {
5668
base: 'vs',
5769
inherit: true,
5870
rules: [
5971
{ token: 'keyword', foreground: '383838', fontStyle: 'bold' },
72+
{ token: 'string', foreground: '16A34A' },
6073
{ token: 'string.sql', foreground: '16A34A' },
74+
{ token: 'string.quote', foreground: '16A34A' },
75+
{ token: 'string.escape', foreground: '16A34A', fontStyle: 'italic' },
6176
{ token: 'comment', foreground: '5A5A5A' },
6277
{ token: 'number', foreground: 'B91C1C', fontStyle: 'bold' },
6378
{ token: 'operator', foreground: '383838', fontStyle: 'bold' },
6479
{ token: 'identifier', foreground: '1A1A1A' },
6580
{ token: 'type', foreground: '7C3AED', fontStyle: 'bold' },
81+
{ token: 'key', foreground: '7C3AED', fontStyle: 'italic' },
6682
],
6783
colors: {
6884
'editor.background': '#F8F8F7',
@@ -85,12 +101,16 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>(
85101
inherit: true,
86102
rules: [
87103
{ token: 'keyword', foreground: 'F8F8F7', fontStyle: 'bold' },
104+
{ token: 'string', foreground: '53DBC9' },
88105
{ token: 'string.sql', foreground: '53DBC9' },
106+
{ token: 'string.quote', foreground: '53DBC9' },
107+
{ token: 'string.escape', foreground: '53DBC9', fontStyle: 'italic' },
89108
{ token: 'comment', foreground: '999999' },
90109
{ token: 'number', foreground: 'FF6B35', fontStyle: 'bold' },
91110
{ token: 'operator', foreground: 'F8F8F7', fontStyle: 'bold' },
92111
{ token: 'identifier', foreground: 'F8F8F7' },
93112
{ token: 'type', foreground: '6FC2FF', fontStyle: 'bold' },
113+
{ token: 'key', foreground: '6FC2FF', fontStyle: 'italic' },
94114
],
95115
colors: {
96116
'editor.background': '#2A2A2A',
@@ -130,73 +150,6 @@ const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>(
130150

131151
editor.focus();
132152

133-
// Register ReifyDB SQL language configuration
134-
monaco.languages.registerCompletionItemProvider('sql', {
135-
provideCompletionItems: (model, position) => {
136-
const word = model.getWordUntilPosition(position);
137-
const range = {
138-
startLineNumber: position.lineNumber,
139-
endLineNumber: position.lineNumber,
140-
startColumn: word.startColumn,
141-
endColumn: word.endColumn,
142-
};
143-
144-
const suggestions = [
145-
// ReifyDB specific keywords
146-
...['REIFY', 'FLOW', 'STREAM', 'MATERIALIZE'].map((keyword) => ({
147-
label: keyword,
148-
kind: monaco.languages.CompletionItemKind.Keyword,
149-
insertText: keyword,
150-
documentation: `ReifyDB keyword: ${keyword}`,
151-
range,
152-
})),
153-
...[
154-
'SELECT',
155-
'FROM',
156-
'WHERE',
157-
'INSERT',
158-
'UPDATE',
159-
'DELETE',
160-
'CREATE',
161-
'DROP',
162-
'ALTER',
163-
'TABLE',
164-
'INDEX',
165-
'VIEW',
166-
'JOIN',
167-
'LEFT',
168-
'RIGHT',
169-
'INNER',
170-
'OUTER',
171-
'ON',
172-
'GROUP BY',
173-
'ORDER BY',
174-
'HAVING',
175-
'LIMIT',
176-
'OFFSET',
177-
'AS',
178-
'INTO',
179-
'VALUES',
180-
'SET',
181-
].map((keyword) => ({
182-
label: keyword,
183-
kind: monaco.languages.CompletionItemKind.Keyword,
184-
insertText: keyword,
185-
range,
186-
})),
187-
// Functions
188-
...['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'NOW', 'CURRENT_TIMESTAMP'].map((func) => ({
189-
label: func,
190-
kind: monaco.languages.CompletionItemKind.Function,
191-
insertText: `${func}()`,
192-
range,
193-
})),
194-
];
195-
196-
return { suggestions };
197-
},
198-
});
199-
200153
// Add execute shortcut
201154
editor.onKeyDown((e) => {
202155
if ((e.ctrlKey || e.metaKey) && e.keyCode === monaco.KeyCode.Enter) {
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import {languages} from 'monaco-editor';
2+
3+
const TRANSFORMS = [
4+
"aggregate",
5+
"append",
6+
"create",
7+
"derive",
8+
"deferred",
9+
"filter",
10+
"from",
11+
"group",
12+
"join",
13+
"map",
14+
"select",
15+
"sort",
16+
"take",
17+
"union",
18+
"view",
19+
"with"
20+
];
21+
22+
const MODULES = ["date", "math", "text"];
23+
const BUILTIN_FUNCTIONS = ["case"];
24+
const KEYWORDS = ["set" ];
25+
const LITERALS = ["undefined", "true", "false"];
26+
27+
export const rqlLanguageDefinition: languages.IMonarchLanguage = {
28+
defaultToken: 'invalid',
29+
ignoreCase: true,
30+
31+
keywords: [
32+
...TRANSFORMS,
33+
...MODULES,
34+
...BUILTIN_FUNCTIONS,
35+
...KEYWORDS,
36+
...LITERALS,
37+
],
38+
39+
operators: [
40+
"+", "-", "*", "/", "//", "%",
41+
"=", "==", "!=", "->", "=>", ">", "<", ">=", "<=", "~=",
42+
"&&", "||", "??"
43+
],
44+
45+
tokenizer: {
46+
root: [
47+
// Comments
48+
[/#.*/, 'comment'],
49+
50+
// Named arguments
51+
[/(\w+)\s*:/, 'key'],
52+
53+
// Identifiers and keywords (case insensitive)
54+
[/[a-zA-Z_$][\w$]*/, {
55+
cases: {
56+
'@keywords': 'keyword',
57+
'@default': 'identifier'
58+
}
59+
}],
60+
61+
// Whitespace
62+
{include: '@whitespace'},
63+
64+
// Brackets
65+
[/[()[\]]/, '@brackets'],
66+
67+
// Numbers with underscores support
68+
[/[+-]?(?:[\d_]+(?:\.[\d_]+)?|\.[\d_]+)/, 'number'],
69+
70+
// Strings
71+
[/"([^"\\]|\\.)*$/, 'string.invalid'],
72+
[/"/, {token: 'string.quote', bracket: '@open', next: '@string'}],
73+
74+
// Single-quoted strings
75+
[/'([^'\\]|\\.)*$/, 'string.invalid'],
76+
[/'/, {token: 'string.quote', bracket: '@open', next: '@singlestring'}],
77+
78+
// Operators
79+
[/[+\-*/%]/, 'operator'],
80+
[/\/\//, 'operator'],
81+
[/==|!=|->|=>|>=|<=|~=|>|</, 'operator'],
82+
[/&&|\|\||\?\?/, 'operator'],
83+
],
84+
85+
string: [
86+
[/[^\\"]+/, 'string'],
87+
[/\\./, 'string.escape'],
88+
[/"/, {token: 'string.quote', bracket: '@close', next: '@pop'}],
89+
],
90+
91+
singlestring: [
92+
[/[^\\']+/, 'string'],
93+
[/\\./, 'string.escape'],
94+
[/'/, {token: 'string.quote', bracket: '@close', next: '@pop'}],
95+
],
96+
97+
whitespace: [
98+
[/[ \t\r\n]+/, 'white'],
99+
[/\/\*/, 'comment', '@comment'],
100+
[/\/\/.*$/, 'comment'],
101+
],
102+
103+
comment: [
104+
[/[^/*]+/, 'comment'],
105+
[/\/\*/, 'comment', '@push'],
106+
[/\*\//, 'comment', '@pop'],
107+
[/[/*]/, 'comment']
108+
],
109+
}
110+
};
111+
112+
export const rqlLanguageConfiguration: languages.LanguageConfiguration = {
113+
comments: {
114+
lineComment: '#',
115+
blockComment: ['/*', '*/']
116+
},
117+
brackets: [
118+
['{', '}'],
119+
['[', ']'],
120+
['(', ')']
121+
],
122+
autoClosingPairs: [
123+
{open: '{', close: '}'},
124+
{open: '[', close: ']'},
125+
{open: '(', close: ')'},
126+
{open: '"', close: '"', notIn: ['string']},
127+
{open: "'", close: "'", notIn: ['string']}
128+
],
129+
surroundingPairs: [
130+
{open: '{', close: '}'},
131+
{open: '[', close: ']'},
132+
{open: '(', close: ')'},
133+
{open: '"', close: '"'},
134+
{open: "'", close: "'"}
135+
],
136+
folding: {
137+
offSide: true
138+
}
139+
};
140+
141+
export const rqlCompletionProvider: languages.CompletionItemProvider = {
142+
provideCompletionItems: (model, position, context, token) => {
143+
const word = model.getWordUntilPosition(position);
144+
const range = {
145+
startLineNumber: position.lineNumber,
146+
endLineNumber: position.lineNumber,
147+
startColumn: word.startColumn,
148+
endColumn: word.endColumn,
149+
};
150+
151+
const suggestions: languages.CompletionItem[] = [
152+
// Transforms
153+
...TRANSFORMS.map(keyword => ({
154+
label: keyword,
155+
kind: languages.CompletionItemKind.Keyword,
156+
insertText: keyword,
157+
documentation: `RQL transform: ${keyword}`,
158+
range,
159+
})),
160+
161+
// Modules
162+
...MODULES.map(module => ({
163+
label: module,
164+
kind: languages.CompletionItemKind.Module,
165+
insertText: module,
166+
documentation: `RQL module: ${module}`,
167+
range,
168+
})),
169+
170+
// Built-in functions
171+
...BUILTIN_FUNCTIONS.map(func => ({
172+
label: func,
173+
kind: languages.CompletionItemKind.Function,
174+
insertText: func,
175+
documentation: `RQL function: ${func}`,
176+
range,
177+
})),
178+
179+
// Keywords
180+
...KEYWORDS.map(keyword => ({
181+
label: keyword,
182+
kind: languages.CompletionItemKind.Keyword,
183+
insertText: keyword,
184+
documentation: `RQL keyword: ${keyword}`,
185+
range,
186+
})),
187+
188+
// Literals
189+
...LITERALS.map(literal => ({
190+
label: literal,
191+
kind: languages.CompletionItemKind.Constant,
192+
insertText: literal,
193+
documentation: `RQL literal: ${literal}`,
194+
range,
195+
})),
196+
];
197+
198+
return {suggestions};
199+
}
200+
};

src/components/InlineCodeBlock/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function InlineCodeBlock({
1414
query,
1515
defaultExpanded = false,
1616
editable = true,
17-
language = 'sql',
17+
language = 'rql',
1818
forceViewer = false
1919
}: InlineCodeBlockProps) {
2020
const [isMobile, setIsMobile] = useState(() => {

0 commit comments

Comments
 (0)