diff --git a/Makefile b/Makefile index 44c9c44..4547d4f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ all: editor -editor: src/*.odin rg +editor: src/*.odin rg odin_highlighter odin build src/ -out:bin/editor -lld +odin_highlighter: + odin build plugins/odin_highlighter/src/ -build-mode:dll -no-entry-point -out:bin/odin_highlighter + rg: cargo b --manifest-path=lib-rg/Cargo.toml diff --git a/plugins/odin_highlighter/src/plugin.odin b/plugins/odin_highlighter/src/plugin.odin new file mode 100644 index 0000000..e7fad83 --- /dev/null +++ b/plugins/odin_highlighter/src/plugin.odin @@ -0,0 +1,260 @@ +// The default syntax highlighter plugin for Odin +package odin_highlighter; + +import "core:runtime" +import "core:fmt" + +import p "../../../src/plugin" + +Plugin :: p.Plugin; +Iterator :: p.Iterator; +BufferIter :: p.BufferIter; +BufferIndex :: p.BufferIndex; + +@export +OnInitialize :: proc "c" (plugin: Plugin) { + context = runtime.default_context(); + fmt.println("Hello from the Odin Highlighter Plugin!"); + + it := plugin.iter.get_current_buffer_iterator(plugin.state); + + fmt.println("Look I have an iterator!", it); +} + +@export +OnExit :: proc "c" () { + context = runtime.default_context(); + fmt.println("Goodbye from the Odin Highlighter Plugin!"); +} + +@export +OnDraw :: proc "c" (plugin: Plugin) { + context = runtime.default_context(); + + color_buffer(plugin); +} + +iterate_buffer :: proc(state: rawptr, iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { + result := iter_funcs.iterate_buffer(state, it); + + return result.char, it.cursor.index, result.should_stop; +} + +iterate_buffer_reverse :: proc(state: rawptr, iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { + result := iter_funcs.iterate_buffer_reverse(state, it); + + return result.char, it.cursor.index, result.should_stop; +} + +iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr) { + plugin.iter.iterate_buffer_until(plugin.state, it, until_proc); +} + +is_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) { + keywords := []string { + "using", + "transmute", + "cast", + "distinct", + "opaque", + "where", + "struct", + "enum", + "union", + "bit_field", + "bit_set", + "if", + "when", + "else", + "do", + "for", + "switch", + "case", + "continue", + "break", + "size_of", + "offset_of", + "type_info_of", + "typeid_of", + "type_of", + "align_of", + "or_return", + "or_else", + "inline", + "no_inline", + "string", + "cstring", + "bool", + "b8", + "b16", + "b32", + "b64", + "rune", + "any", + "rawptr", + "f16", + "f32", + "f64", + "f16le", + "f16be", + "f32le", + "f32be", + "f64le", + "f64be", + "u8", + "u16", + "u32", + "u64", + "u128", + "u16le", + "u32le", + "u64le", + "u128le", + "u16be", + "u32be", + "u64be", + "u128be", + "uint", + "uintptr", + "i8", + "i16", + "i32", + "i64", + "i128", + "i16le", + "i32le", + "i64le", + "i128le", + "i16be", + "i32be", + "i64be", + "i128be", + "int", + "complex", + "complex32", + "complex64", + "complex128", + "quaternion", + "quaternion64", + "quaternion128", + "quaternion256", + "matrix", + "typeid", + "true", + "false", + "nil", + "dynamic", + "map", + "proc", + "in", + "notin", + "not_in", + "import", + "export", + "foreign", + "const", + "package", + "return", + "defer", + }; + + for keyword in keywords { + it := start; + keyword_index := 0; + + for character in iterate_buffer(plugin.state, plugin.iter, &it) { + if character != keyword[keyword_index] { + break; + } + + keyword_index += 1; + if keyword_index >= len(keyword)-1 && it == end { + if plugin.iter.get_char_at_iter(plugin.state, &it) == keyword[keyword_index] { + matches = true; + } + + break; + } else if keyword_index >= len(keyword)-1 { + break; + } else if it == end { + break; + } + } + + if matches { + break; + } + } + + return; +} + +color_buffer :: proc(plugin: Plugin) { + start_it := plugin.iter.get_current_buffer_iterator(plugin.state); + it := plugin.iter.get_current_buffer_iterator(plugin.state); + + buffer := plugin.buffer.get_buffer_info(plugin.state); + + for character in iterate_buffer(plugin.state, plugin.iter, &it) { + if it.cursor.line > buffer.glyph_buffer_height && (it.cursor.line - buffer.top_line) > buffer.glyph_buffer_height { + break; + } + + if character == '/' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_buffer_reverse(plugin.state, plugin.iter, &start_it); + + character, _, succ := iterate_buffer(plugin.state, plugin.iter, &it); + if !succ { break; } + + if character == '/' { + iterate_buffer_until(plugin, &it, plugin.iter.until_line_break); + plugin.buffer.color_char_at(plugin.state, start_it.cursor, it.cursor, 9); + } else if character == '*' { + // TODO: block comments + } + } else if character == '\'' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_buffer_reverse(plugin.state, plugin.iter, &start_it); + + // jump into the quoted text + iterate_buffer_until(plugin, &it, plugin.iter.until_single_quote); + plugin.buffer.color_char_at(plugin.state, start_it.cursor, it.cursor, 12); + + iterate_buffer(plugin.state, plugin.iter, &it); + } else if character == '"' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_buffer_reverse(plugin.state, plugin.iter, &start_it); + + // jump into the quoted text + iterate_buffer_until(plugin, &it, plugin.iter.until_double_quote); + plugin.buffer.color_char_at(plugin.state, start_it.cursor, it.cursor, 12); + + iterate_buffer(plugin.state, plugin.iter, &it); + } else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' { + start_it = it; + // need to go back one character because `it` is on the next character + iterate_buffer_reverse(plugin.state, plugin.iter, &start_it); + it = start_it; + + iterate_buffer_until(plugin, &it, plugin.iter.until_end_of_word); + + if is_keyword(plugin, start_it, it) { + plugin.buffer.color_char_at(plugin.state, start_it.cursor, it.cursor, 13); + } + //else { + // break; + //} + // else if character, _, cond := iterate_peek(&it, iterate_file_buffer); cond { + // if character == '(' { + // color_character(buffer, start_it.cursor, it.cursor, .Green); + // } + // } + + iterate_buffer(plugin.state, plugin.iter, &it); + } + } +} diff --git a/src/core/core.odin b/src/core/core.odin index d8bf620..c1c41ee 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -1,8 +1,11 @@ package core +import "core:runtime" import "core:fmt" import "vendor:raylib" +import "../plugin" + Mode :: enum { Normal, Insert, @@ -39,6 +42,8 @@ close_window_and_free :: proc(state: ^State) { } State :: struct { + ctx: runtime.Context, + mode: Mode, should_close: bool, screen_height: int, @@ -59,6 +64,8 @@ State :: struct { input_map: InputMap, current_input_map: ^InputMap, + + plugins: [dynamic]plugin.Interface, } EditorAction :: proc(state: ^State); diff --git a/src/core/error.odin b/src/core/error.odin index f65d6f4..e8dfe14 100644 --- a/src/core/error.odin +++ b/src/core/error.odin @@ -5,6 +5,7 @@ import "core:runtime" ErrorType :: enum { None, FileIOError, + PluginLoadError, } Error :: struct { diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 91eeea3..1645aac 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -916,7 +916,13 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) { draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, font: raylib.Font, show_line_numbers: bool = true) { update_glyph_buffer(buffer); - color_buffer(buffer); + for plugin in state.plugins { + if plugin.on_initialize != nil { + plugin.on_draw(plugin.plugin); + } + } + //color_buffer(buffer); + //update_glyph_buffer(buffer); padding := 0; if show_line_numbers { diff --git a/src/main.odin b/src/main.odin index ebfdaa3..6291397 100644 --- a/src/main.odin +++ b/src/main.odin @@ -13,6 +13,7 @@ import "vendor:raylib" import "core" import "theme" import "ui" +import "plugin" State :: core.State; FileBuffer :: core.FileBuffer; @@ -185,14 +186,221 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) { register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap)); } +load_plugins :: proc(state: ^State) -> core.Error { + if loaded_plugin, succ := plugin.try_load_plugin("bin/odin_highlighter.dylib"); succ { + loaded_plugin.plugin = plugin.Plugin { + state = cast(rawptr)state, + iter = plugin.Iterator { + get_current_buffer_iterator = proc "c" (state: rawptr) -> plugin.BufferIter { + state := cast(^State)state; + context = state.ctx; + + it := core.new_file_buffer_iter(&state.buffers[state.current_buffer]); + + // TODO: make this into a function + return plugin.BufferIter { + cursor = plugin.Cursor { + col = it.cursor.col, + line = it.cursor.line, + index = plugin.BufferIndex { + slice_index = it.cursor.index.slice_index, + content_index = it.cursor.index.content_index, + } + }, + buffer = cast(rawptr)it.buffer, + hit_end = it.hit_end, + } + }, + get_char_at_iter = proc "c" (state: rawptr, it: ^plugin.BufferIter) -> u8 { + state := cast(^State)state; + context = state.ctx; + + internal_it := core.FileBufferIter { + cursor = core.Cursor { + col = it.cursor.col, + line = it.cursor.line, + index = core.FileBufferIndex { + slice_index = it.cursor.index.slice_index, + content_index = it.cursor.index.content_index, + } + }, + buffer = cast(^core.FileBuffer)it.buffer, + hit_end = it.hit_end, + } + + return core.get_character_at_iter(internal_it); + }, + iterate_buffer = proc "c" (state: rawptr, it: ^plugin.BufferIter) -> plugin.IterateResult { + state := cast(^State)state; + context = state.ctx; + + // TODO: make this into a function + internal_it := core.FileBufferIter { + cursor = core.Cursor { + col = it.cursor.col, + line = it.cursor.line, + index = core.FileBufferIndex { + slice_index = it.cursor.index.slice_index, + content_index = it.cursor.index.content_index, + } + }, + buffer = cast(^core.FileBuffer)it.buffer, + hit_end = it.hit_end, + } + + char, _, cond := core.iterate_file_buffer(&internal_it); + + it^ = plugin.BufferIter { + cursor = plugin.Cursor { + col = internal_it.cursor.col, + line = internal_it.cursor.line, + index = plugin.BufferIndex { + slice_index = internal_it.cursor.index.slice_index, + content_index = internal_it.cursor.index.content_index, + } + }, + buffer = cast(rawptr)internal_it.buffer, + hit_end = internal_it.hit_end, + }; + + return plugin.IterateResult { + char = char, + should_stop = cond, + }; + }, + iterate_buffer_reverse = proc "c" (state: rawptr, it: ^plugin.BufferIter) -> plugin.IterateResult { + state := cast(^State)state; + context = state.ctx; + + // TODO: make this into a function + internal_it := core.FileBufferIter { + cursor = core.Cursor { + col = it.cursor.col, + line = it.cursor.line, + index = core.FileBufferIndex { + slice_index = it.cursor.index.slice_index, + content_index = it.cursor.index.content_index, + } + }, + buffer = cast(^core.FileBuffer)it.buffer, + hit_end = it.hit_end, + } + + char, _, cond := core.iterate_file_buffer_reverse(&internal_it); + + it^ = plugin.BufferIter { + cursor = plugin.Cursor { + col = internal_it.cursor.col, + line = internal_it.cursor.line, + index = plugin.BufferIndex { + slice_index = internal_it.cursor.index.slice_index, + content_index = internal_it.cursor.index.content_index, + } + }, + buffer = cast(rawptr)internal_it.buffer, + hit_end = internal_it.hit_end, + }; + + return plugin.IterateResult { + char = char, + should_stop = cond, + }; + }, + iterate_buffer_until = proc "c" (state: rawptr, it: ^plugin.BufferIter, until_proc: rawptr) { + state := cast(^State)state; + context = state.ctx; + + // TODO: make this into a function + internal_it := core.FileBufferIter { + cursor = core.Cursor { + col = it.cursor.col, + line = it.cursor.line, + index = core.FileBufferIndex { + slice_index = it.cursor.index.slice_index, + content_index = it.cursor.index.content_index, + } + }, + buffer = cast(^core.FileBuffer)it.buffer, + hit_end = it.hit_end, + } + + core.iterate_file_buffer_until(&internal_it, transmute(core.UntilProc)until_proc); + + it^ = plugin.BufferIter { + cursor = plugin.Cursor { + col = internal_it.cursor.col, + line = internal_it.cursor.line, + index = plugin.BufferIndex { + slice_index = internal_it.cursor.index.slice_index, + content_index = internal_it.cursor.index.content_index, + } + }, + buffer = cast(rawptr)internal_it.buffer, + hit_end = internal_it.hit_end, + }; + }, + until_line_break = transmute(rawptr)core.until_line_break, + until_single_quote = transmute(rawptr)core.until_single_quote, + until_double_quote = transmute(rawptr)core.until_double_quote, + until_end_of_word = transmute(rawptr)core.until_end_of_word, + }, + buffer = plugin.Buffer { + get_buffer_info = proc "c" (state: rawptr) -> plugin.BufferInfo { + state := cast(^State)state; + context = state.ctx; + + buffer := &state.buffers[state.current_buffer]; + + return plugin.BufferInfo { + glyph_buffer_width = buffer.glyph_buffer_width, + glyph_buffer_height = buffer.glyph_buffer_height, + top_line = buffer.top_line, + }; + }, + color_char_at = proc "c" (state: rawptr, start_cursor: plugin.Cursor, end_cursor: plugin.Cursor, palette_index: i32) { + state := cast(^State)state; + context = state.ctx; + + buffer := &state.buffers[state.current_buffer]; + + start_cursor := core.Cursor { + col = start_cursor.col, + line = start_cursor.line, + index = core.FileBufferIndex { + slice_index = start_cursor.index.slice_index, + content_index = start_cursor.index.content_index, + } + }; + end_cursor := core.Cursor { + col = end_cursor.col, + line = end_cursor.line, + index = core.FileBufferIndex { + slice_index = end_cursor.index.slice_index, + content_index = end_cursor.index.content_index, + } + }; + + core.color_character(buffer, start_cursor, end_cursor, cast(theme.PaletteColor)palette_index); + } + } + }; + + append(&state.plugins, loaded_plugin); + fmt.println("Loaded Odin Highlighter plugin"); + return core.no_error(); + } + + return core.make_error(.PluginLoadError, fmt.aprintf("failed to load Odin Highligher plugin")); +} + main :: proc() { state := State { source_font_width = 8, source_font_height = 16, input_map = core.new_input_map(), window = nil, - directory = os.get_current_directory(), + plugins = make([dynamic]plugin.Interface), }; state.current_input_map = &state.input_map; register_default_input_actions(&state.input_map); @@ -216,6 +424,17 @@ main :: proc() { runtime.append(&buffer_items, item); } + // Load plugins + if err := load_plugins(&state); err.type != .None { + fmt.println(err.msg); + } + + for plugin in state.plugins { + if plugin.on_initialize != nil { + plugin.on_initialize(plugin.plugin); + } + } + raylib.InitWindow(640, 480, "odin_editor - [back to basics]"); raylib.SetWindowState({ .WINDOW_RESIZABLE, .VSYNC_HINT }); raylib.SetTargetFPS(60); @@ -248,6 +467,14 @@ main :: proc() { defer raylib.EndDrawing(); raylib.ClearBackground(theme.get_palette_raylib_color(.Background)); + + // TODO: be more granular in /what/ is being draw by the plugin + for plugin in state.plugins { + if plugin.on_initialize != nil { + //plugin.on_draw(plugin.plugin); + } + } + core.draw_file_buffer(&state, buffer, 32, state.source_font_height, state.font); ui.draw_menu_bar(&state, &menu_bar_state, 0, 0, i32(state.screen_width), i32(state.screen_height), state.source_font_height); @@ -393,4 +620,10 @@ main :: proc() { ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height); } + + for plugin in state.plugins { + if plugin.on_exit != nil { + plugin.on_exit(); + } + } } diff --git a/src/plugin/plugin.odin b/src/plugin/plugin.odin new file mode 100644 index 0000000..58998c8 --- /dev/null +++ b/src/plugin/plugin.odin @@ -0,0 +1,97 @@ +package plugin; + +import "core:intrinsics" +import "core:dynlib" +import "core:fmt" + +OnInitializeProc :: proc "c" (plugin: Plugin); +OnExitProc :: proc "c" (/* probably needs some state eventually */); +OnDrawProc :: proc "c" (plugin: Plugin); +Interface :: struct { + on_initialize: OnInitializeProc, + on_exit: OnExitProc, + on_draw: OnDrawProc, + + plugin: Plugin, +} + +BufferIndex :: struct { + slice_index: int, + content_index: int, +} + +Cursor :: struct { + col: int, + line: int, + index: BufferIndex, +} + +BufferIter :: struct { + cursor: Cursor, + buffer: rawptr, + hit_end: bool, +} + +IterateResult :: struct { + char: u8, + should_stop: bool, +} + +BufferInfo :: struct { + glyph_buffer_width: int, + glyph_buffer_height: int, + top_line: int, +} + +Buffer :: struct { + get_buffer_info: proc "c" (state: rawptr) -> BufferInfo, + color_char_at: proc "c" (buffer: rawptr, start_cursor: Cursor, end_cursor: Cursor, palette_index: i32), +} + +Iterator :: struct { + get_current_buffer_iterator: proc "c" (state: rawptr) -> BufferIter, + get_char_at_iter: proc "c" (state: rawptr, it: ^BufferIter) -> u8, + + iterate_buffer: proc "c" (state: rawptr, it: ^BufferIter) -> IterateResult, + iterate_buffer_reverse: proc "c" (state: rawptr, it: ^BufferIter) -> IterateResult, + iterate_buffer_until: proc "c" (state: rawptr, it: ^BufferIter, until_proc: rawptr), + iterate_buffer_until_reverse: proc "c" (state: rawptr, it: ^BufferIter, until_proc: rawptr), + iterate_buffer_peek: proc "c" (state: rawptr, it: ^BufferIter, iter_proc: rawptr) -> IterateResult, + + until_line_break: rawptr, + until_single_quote: rawptr, + until_double_quote: rawptr, + until_end_of_word: rawptr, +} + +Plugin :: struct { + state: rawptr, + iter: Iterator, + buffer: Buffer, +} + +load_proc_address :: proc(lib_path: string, library: dynlib.Library, symbol: string, $ProcType: typeid) -> ProcType + where intrinsics.type_is_proc(ProcType) +{ + if address, found := dynlib.symbol_address(library, symbol); found { + fmt.println("The symbol", symbol, "was found at the address", address); + return transmute(ProcType)address; + } else { + fmt.println("Could not find symbol", symbol, "in library", lib_path); + } + + return nil; +} + +try_load_plugin :: proc(lib_path: string) -> (plugin: Interface, success: bool) { + library, ok := dynlib.load_library(lib_path) + if !ok { + return {}, false; + } + + return Interface { + on_initialize = load_proc_address(lib_path, library, "OnInitialize", OnInitializeProc), + on_exit = load_proc_address(lib_path, library, "OnExit", OnExitProc), + on_draw = load_proc_address(lib_path, library, "OnDraw", OnDrawProc), + }, true; +}