Skip to content

Commit c9ca974

Browse files
committed
create fasthtml wgsl code editor (wip) in experimental/
1 parent 0e2ced3 commit c9ca974

File tree

4 files changed

+242
-3
lines changed

4 files changed

+242
-3
lines changed

Doxyfile

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ DOXYFILE_ENCODING = UTF-8
1010
# website
1111

1212
PROJECT_NAME = "gpu.cpp"
13-
PROJECT_NUMBER = " "
13+
PROJECT_NUMBER = "0.1.0"
1414

1515
# Using the PROJECT_BRIEF tag one can provide an optional one line description
1616
# for a project that appears at the top of each page and should give viewer a
@@ -1039,8 +1039,8 @@ FILTER_SOURCE_PATTERNS =
10391039
# (index.html). This can be useful if you have a project on for instance GitHub
10401040
# and want to reuse the introduction page also for the doxygen output.
10411041

1042-
USE_MDFILE_AS_MAINPAGE =
1043-
# USE_MDFILE_AS_MAINPAGE = README.md
1042+
# USE_MDFILE_AS_MAINPAGE =
1043+
USE_MDFILE_AS_MAINPAGE = README.md
10441044

10451045
#---------------------------------------------------------------------------
10461046
# Configuration options related to source browsing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Adapted from: https://github.com/AnswerDotAI/fasthtml-example/tree/baa67c5b2ca4d4a9ba091b9f9b72b8a2de384a37/code_editor
2+
3+
from fasthtml.common import *
4+
from .toolbar import Toolbar
5+
6+
editor_script = Script("""
7+
let editor;
8+
let completionTippy;
9+
let currentCompletion = '';
10+
11+
function initEditor() {
12+
editor = ace.edit("editor");
13+
editor.setTheme("ace/theme/monokai");
14+
editor.session.setMode("ace/mode/javascript");
15+
editor.setOptions({
16+
fontSize: "14px",
17+
showPrintMargin: false,
18+
// disable showing errors in gutter Ace WGSL parser is out of date
19+
showGutter: false,
20+
highlightActiveLine: true,
21+
wrap: true,
22+
});
23+
editor.setKeyboardHandler("ace/keyboard/vim");
24+
25+
editor.setValue(
26+
"// Puzzle 1 : Map\\n\
27+
// Implement a kernel that adds 10 to each position of vector\\n\
28+
// a and stores it in vector out. You have 1 thread per position.\\n\
29+
//\\n\
30+
// Warning: You are in vim mode.\\n\
31+
\\n\
32+
@group(0) @binding(0) var<storage, read_write> a: array<f32>;\\n\
33+
@group(0) @binding(1) var<storage, read_write> output : array<f32>;\\n\
34+
@compute @workgroup_size(256)\\n\
35+
fn main(\\n\
36+
@builtin(local_invocation_id) LocalInvocationID: vec3<u32>) {\\n\
37+
let local_idx = LocalInvocationID.x;\\n\
38+
if (local_idx < arrayLength(&a)) {\\n\
39+
output[local_idx] = a[local_idx] + 10;\\n\
40+
}\\n\
41+
}\\n\
42+
");
43+
44+
window.addEventListener('resize', function() {
45+
editor.resize();
46+
});
47+
48+
document.getElementById('language').addEventListener('change', function(e) {
49+
let mode = "ace/mode/" + e.target.value;
50+
editor.session.setMode(mode);
51+
});
52+
53+
editor.session.on('change', function(delta) {
54+
if (delta.action === 'insert' && (delta.lines[0] === '.' || delta.lines[0] === ' ')) {
55+
showCompletionSuggestion();
56+
}
57+
});
58+
59+
completionTippy = tippy(document.getElementById('editor'), {
60+
content: 'Loading...',
61+
trigger: 'manual',
62+
placement: 'top-start',
63+
arrow: true,
64+
interactive: true
65+
});
66+
67+
// Override the default tab behavior
68+
editor.commands.addCommand({
69+
name: 'insertCompletion',
70+
bindKey: {win: 'Tab', mac: 'Tab'},
71+
exec: function(editor) {
72+
if (currentCompletion) {
73+
editor.insert(currentCompletion);
74+
currentCompletion = '';
75+
completionTippy.hide();
76+
} else {
77+
editor.indent();
78+
}
79+
}
80+
});
81+
}
82+
83+
async function showCompletionSuggestion() {
84+
const cursorPosition = editor.getCursorPosition();
85+
const screenPosition = editor.renderer.textToScreenCoordinates(cursorPosition.row, cursorPosition.column);
86+
87+
completionTippy.setContent('Loading...');
88+
completionTippy.setProps({
89+
getReferenceClientRect: () => ({
90+
width: 0,
91+
height: 0,
92+
top: screenPosition.pageY,
93+
bottom: screenPosition.pageY,
94+
left: screenPosition.pageX,
95+
right: screenPosition.pageX,
96+
})
97+
});
98+
completionTippy.show();
99+
100+
try {
101+
const response = await fetch('/complete', {
102+
method: 'POST',
103+
headers: {
104+
'Content-Type': 'application/json',
105+
},
106+
body: JSON.stringify({
107+
code: editor.getValue(),
108+
row: cursorPosition.row,
109+
column: cursorPosition.column
110+
}),
111+
});
112+
113+
if (!response.ok) {
114+
throw new Error(`HTTP error! status: ${response.status}`);
115+
}
116+
117+
const data = await response.json();
118+
currentCompletion = data.completion;
119+
completionTippy.setContent(`${currentCompletion} (Press Tab to insert)`);
120+
} catch (error) {
121+
console.error('Error:', error);
122+
completionTippy.setContent('Error fetching completion');
123+
currentCompletion = '';
124+
}
125+
126+
setTimeout(() => {
127+
if (currentCompletion) {
128+
completionTippy.hide();
129+
currentCompletion = '';
130+
}
131+
}, 5000);
132+
}
133+
134+
document.addEventListener('DOMContentLoaded', initEditor);
135+
""")
136+
137+
def CodeEditor():
138+
return (
139+
Div(
140+
Toolbar(),
141+
Div(
142+
Div(id="editor", style="height: 90vh; width: 100vw;"),
143+
Script("""
144+
me().on('contextmenu', ev => {
145+
ev.preventDefault()
146+
me('#context-menu').send('show', {x: ev.pageX, y: ev.pageY})
147+
})
148+
"""),
149+
# cls="flex-grow w-full"
150+
style="height: 100vh; overflow: hidden;"
151+
),
152+
# cls="flex flex-col h-screen w-full", style="height: 100vh; overflow: hidden;"
153+
style="height: 100vh; overflow: hidden;"
154+
),
155+
editor_script
156+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from fasthtml.common import *
2+
3+
def Toolbar():
4+
return Div(
5+
Select(
6+
Option("WGSL", value="wgsl"),
7+
Option("C++", value="c++"),
8+
id="language",
9+
cls="mr-2 p-2 border rounded"
10+
),
11+
cls="bg-gray-200 p-4 shadow-md flex items-center w-full"
12+
)

experimental/fasthtml/run.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from components.code_editor import CodeEditor
2+
from fasthtml.common import *
3+
from functools import cache
4+
import os
5+
import uvicorn
6+
7+
TARGET = os.getenv("TARGET", "debug")
8+
9+
ace_editor = Script(src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js")
10+
global_style = Style("""
11+
#editor {
12+
height: 50vh;
13+
width: 50vw;
14+
}
15+
""")
16+
17+
HDRS = (
18+
picolink,
19+
ace_editor,
20+
global_style,
21+
*Socials(
22+
title="gpu.cpp gpu puzzles",
23+
description="",
24+
site_name="gpucpp.answer.ai",
25+
twitter_site="@answerdotai",
26+
image="",
27+
url="https://gpucpp.answer.ai",
28+
),
29+
)
30+
31+
if TARGET == "release":
32+
app = FastHTML(hdrs=HDRS)
33+
else:
34+
app = FastHTMLWithLiveReload(hdrs=HDRS)
35+
36+
rt = app.route
37+
38+
@app.get("/build/{fname:path}.{ext:static}")
39+
async def build(fname: str, ext: str):
40+
return FileResponse(f"build/{fname}.{ext}")
41+
42+
def page():
43+
return Title("GPU Puzzles"), Body(
44+
Div(
45+
Div(
46+
CodeEditor(),
47+
style="width: 66vw; height:100vh; background-color: #333; float: left;",
48+
),
49+
Div(
50+
"Output",
51+
style="width: 34vw; height:100vh; background-color: #444; float: right;",
52+
),
53+
),
54+
style="height: 100vh; overflow: hidden;")
55+
56+
57+
@rt("/")
58+
def get():
59+
return page()
60+
61+
62+
rt = app.route
63+
64+
if __name__ == "__main__":
65+
run_uv(
66+
fname=None,
67+
app="app",
68+
host="0.0.0.0",
69+
port=int(os.getenv("PORT", 8000)),
70+
reload=True,
71+
)

0 commit comments

Comments
 (0)