Initial commit: Odin editor client + Kotlin daemon prototype
Native SDL3 editor written in Odin with a piece-table buffer core, backed by a Kotlin/JVM daemon speaking newline-delimited JSON over localhost TCP for Gradle import, Kotlin/Java diagnostics, and heuristic completion/hover/definition/references/rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
4a15350860
21 changed files with 8112 additions and 0 deletions
184
client/odin/main.odin
Normal file
184
client/odin/main.odin
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
|
||||
main :: proc() {
|
||||
args := os.args
|
||||
if len(args) < 2 {
|
||||
fmt.println("usage: odin run client/odin -- <port> [workspace]")
|
||||
fmt.println(" or: odin run client/odin -- --sdl [workspace] [port] [file]")
|
||||
return
|
||||
}
|
||||
|
||||
if args[1] == "--sdl" {
|
||||
workspace := "."
|
||||
if len(args) >= 3 {
|
||||
workspace = args[2]
|
||||
}
|
||||
port := 0
|
||||
file := ""
|
||||
if len(args) >= 4 {
|
||||
parsed_port, parsed_ok := strconv.parse_int(args[3])
|
||||
if parsed_ok {
|
||||
port = int(parsed_port)
|
||||
} else {
|
||||
file = args[3]
|
||||
}
|
||||
}
|
||||
if len(args) >= 5 {
|
||||
file = args[4]
|
||||
}
|
||||
run_sdl_editor(workspace, port, file)
|
||||
return
|
||||
}
|
||||
|
||||
port, ok := strconv.parse_int(args[1])
|
||||
if !ok {
|
||||
fmt.println("invalid port")
|
||||
return
|
||||
}
|
||||
|
||||
workspace := "."
|
||||
if len(args) >= 3 {
|
||||
workspace = args[2]
|
||||
}
|
||||
|
||||
run_buffer_smoke()
|
||||
editor := run_editor_smoke(workspace)
|
||||
defer editor_destroy(&editor)
|
||||
|
||||
socket, err := net.dial_tcp("127.0.0.1", int(port))
|
||||
if err != nil {
|
||||
fmt.println("connect failed:", err)
|
||||
return
|
||||
}
|
||||
defer net.close(socket)
|
||||
|
||||
send_line(socket, "{\"id\":1,\"method\":\"ping\",\"params\":{}}\n")
|
||||
read_messages(socket, 1)
|
||||
|
||||
open_workspace := fmt.tprintf("{{\"id\":2,\"method\":\"workspace/open\",\"params\":{{\"root\":%q}}}}\n", workspace)
|
||||
send_line(socket, open_workspace)
|
||||
read_messages(socket, 3)
|
||||
|
||||
send_line(socket, "{\"id\":3,\"method\":\"gradle/tasks\",\"params\":{}}\n")
|
||||
read_messages(socket, 1)
|
||||
|
||||
active := editor_active_buffer(&editor)
|
||||
diagnostics_path := active.path
|
||||
active_text := editor_active_text(&editor)
|
||||
defer delete(active_text)
|
||||
|
||||
text_change_request := fmt.tprintf("{{\"id\":4,\"method\":\"text/change\",\"params\":{{\"path\":%q,\"version\":1,\"text\":%q}}}}\n", diagnostics_path, string(active_text[:]))
|
||||
send_line(socket, text_change_request)
|
||||
read_messages(socket, 2)
|
||||
|
||||
diagnostics_request := fmt.tprintf("{{\"id\":5,\"method\":\"kotlin/diagnostics\",\"params\":{{\"path\":%q}}}}\n", diagnostics_path)
|
||||
send_line(socket, diagnostics_request)
|
||||
read_messages(socket, 1)
|
||||
|
||||
completion_request := fmt.tprintf("{{\"id\":6,\"method\":\"kotlin/completion\",\"params\":{{\"path\":%q,\"line\":1,\"column\":1}}}}\n", diagnostics_path)
|
||||
send_line(socket, completion_request)
|
||||
read_messages(socket, 1)
|
||||
|
||||
hover_request := fmt.tprintf("{{\"id\":7,\"method\":\"kotlin/hover\",\"params\":{{\"path\":%q,\"line\":1,\"column\":1}}}}\n", diagnostics_path)
|
||||
send_line(socket, hover_request)
|
||||
read_messages(socket, 1)
|
||||
|
||||
definition_request := fmt.tprintf("{{\"id\":8,\"method\":\"kotlin/definition\",\"params\":{{\"path\":%q,\"line\":1,\"column\":5}}}}\n", diagnostics_path)
|
||||
send_line(socket, definition_request)
|
||||
read_messages(socket, 1)
|
||||
|
||||
references_request := fmt.tprintf("{{\"id\":9,\"method\":\"kotlin/references\",\"params\":{{\"path\":%q,\"line\":1,\"column\":5}}}}\n", diagnostics_path)
|
||||
send_line(socket, references_request)
|
||||
read_messages(socket, 1)
|
||||
|
||||
rename_request := fmt.tprintf("{{\"id\":10,\"method\":\"kotlin/rename\",\"params\":{{\"path\":%q,\"line\":1,\"column\":5,\"newName\":\"renamedBroken\"}}}}\n", diagnostics_path)
|
||||
send_line(socket, rename_request)
|
||||
read_messages(socket, 1)
|
||||
}
|
||||
|
||||
run_editor_smoke :: proc(workspace: string) -> Editor {
|
||||
editor := Editor{}
|
||||
path := fmt.tprintf("%s/src/main/kotlin/dev/nativeeditor/daemon/Main.kt", workspace)
|
||||
if !editor_open_file(&editor, path) {
|
||||
return editor
|
||||
}
|
||||
|
||||
fmt.println("editor-visible-before:")
|
||||
editor_print_visible(&editor, 0, 2)
|
||||
editor_replace_active_text(&editor, "fun broken( {")
|
||||
fmt.println("editor-visible-after:")
|
||||
editor_print_visible(&editor, 0, 2)
|
||||
return editor
|
||||
}
|
||||
|
||||
run_buffer_smoke :: proc() {
|
||||
buffer := buffer_make("hello world\nsecond line\n")
|
||||
defer buffer_destroy(&buffer)
|
||||
|
||||
buffer_insert(&buffer, 6, "native ")
|
||||
buffer_delete_range(&buffer, 0, 6)
|
||||
|
||||
cursor := Cursor{}
|
||||
cursor_move_to_line_col(&buffer, &cursor, 1, 6)
|
||||
cursor_insert(&buffer, &cursor, " edited")
|
||||
cursor_backspace(&buffer, &cursor)
|
||||
_ = buffer_undo(&buffer, &cursor)
|
||||
_ = buffer_redo(&buffer, &cursor)
|
||||
cursor_move_vertical(&buffer, &cursor, -1)
|
||||
|
||||
line, col := buffer_offset_to_line_col(&buffer, cursor.offset)
|
||||
second_line := buffer_line_bytes(&buffer, 1)
|
||||
defer delete(second_line)
|
||||
|
||||
text := buffer_bytes(&buffer)
|
||||
defer delete(text)
|
||||
|
||||
fmt.printf("buffer-smoke: %s", string(text[:]))
|
||||
fmt.printf("buffer-lines: %d\n", buffer_line_count(&buffer))
|
||||
fmt.printf("buffer-line-1: %s\n", string(second_line[:]))
|
||||
fmt.printf("buffer-cursor: %d:%d\n", line, col)
|
||||
}
|
||||
|
||||
send_line :: proc(socket: net.TCP_Socket, line: string) {
|
||||
bytes := transmute([]byte)line
|
||||
_, err := net.send_tcp(socket, bytes)
|
||||
if err != nil {
|
||||
fmt.println("send failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
read_messages :: proc(socket: net.TCP_Socket, expected: int) {
|
||||
seen := 0
|
||||
|
||||
for seen < expected {
|
||||
seen += read_some(socket)
|
||||
}
|
||||
}
|
||||
|
||||
read_some :: proc(socket: net.TCP_Socket) -> int {
|
||||
buf: [4096]byte
|
||||
n, err := net.recv_tcp(socket, buf[:])
|
||||
if err != nil {
|
||||
fmt.println("recv failed:", err)
|
||||
return 0
|
||||
}
|
||||
if n == 0 {
|
||||
fmt.println("connection closed")
|
||||
return 0
|
||||
}
|
||||
|
||||
lines := 0
|
||||
for b in buf[:n] {
|
||||
if b == '\n' {
|
||||
lines += 1
|
||||
}
|
||||
}
|
||||
|
||||
fmt.print(string(buf[:n]))
|
||||
return lines
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue