j
This commit is contained in:
parent
5b65c4c0e4
commit
985aa16236
13 changed files with 1381 additions and 241 deletions
|
|
@ -64,6 +64,7 @@ Project_Tree :: struct {
|
|||
workspace: string,
|
||||
files: [dynamic]Project_File,
|
||||
expanded: map[string]bool,
|
||||
truncated: bool,
|
||||
}
|
||||
|
||||
// State for the system folder-selection dialog. SDL may invoke the dialog
|
||||
|
|
@ -131,6 +132,16 @@ Hover_Tooltip :: struct {
|
|||
line: int,
|
||||
column: int,
|
||||
contents: string,
|
||||
definition_path: string,
|
||||
definition_line: int,
|
||||
definition_column: int,
|
||||
has_definition: bool,
|
||||
x, y: f32,
|
||||
width, height: f32,
|
||||
selecting: bool,
|
||||
selection_active: bool,
|
||||
selection_anchor: int,
|
||||
selection_cursor: int,
|
||||
// Mouse-dwell hovers anchor to the hovered identifier and dismiss when
|
||||
// the mouse leaves its column span; keyboard hovers anchor to the cursor.
|
||||
from_mouse: bool,
|
||||
|
|
@ -168,14 +179,6 @@ References_Panel :: struct {
|
|||
items: [dynamic]Reference_Item,
|
||||
}
|
||||
|
||||
Rename_Panel :: struct {
|
||||
open: bool,
|
||||
pending_id: int,
|
||||
input: [dynamic]u8,
|
||||
summary: string,
|
||||
edits: [dynamic]string,
|
||||
}
|
||||
|
||||
Search_Panel :: struct {
|
||||
open: bool,
|
||||
input: [dynamic]u8,
|
||||
|
|
@ -195,6 +198,7 @@ Command_Palette :: struct {
|
|||
|
||||
Command_Id :: enum {
|
||||
Open_Folder,
|
||||
New_File,
|
||||
Reload_Workspace,
|
||||
Save,
|
||||
Save_All,
|
||||
|
|
@ -218,6 +222,7 @@ Command_Item :: struct {
|
|||
|
||||
COMMAND_ITEMS := [?]Command_Item{
|
||||
{.Open_Folder, "Open Folder"},
|
||||
{.New_File, "New File"},
|
||||
{.Reload_Workspace, "Reload Workspace"},
|
||||
{.Save, "Save File"},
|
||||
{.Save_All, "Save All"},
|
||||
|
|
@ -287,11 +292,6 @@ Syntax_State :: struct {
|
|||
in_triple_string: bool,
|
||||
}
|
||||
|
||||
Daemon_Sync_State :: struct {
|
||||
pending: bool,
|
||||
last_edit: time.Time,
|
||||
}
|
||||
|
||||
SDL_WINDOW_WIDTH :: 1100
|
||||
SDL_WINDOW_HEIGHT :: 760
|
||||
SDL_TOP_BAR_HEIGHT :: 32
|
||||
|
|
@ -307,7 +307,6 @@ SDL_RIGHT_SIDEBAR_MIN_WIDTH :: 220
|
|||
SDL_RIGHT_SIDEBAR_MAX_WIDTH :: 620
|
||||
SDL_RESIZE_HANDLE_WIDTH :: 6
|
||||
SDL_TOOL_STRIP_WIDTH :: 26
|
||||
SDL_DAEMON_SYNC_DEBOUNCE_MS :: 250
|
||||
SDL_HOVER_DWELL_MS :: 450
|
||||
SDL_GUTTER_WIDTH :: 72
|
||||
SDL_EDITOR_TOP :: SDL_TOP_BAR_HEIGHT + SDL_TAB_BAR_HEIGHT
|
||||
|
|
@ -484,22 +483,6 @@ editor_text_x :: proc(view: ^SDL_View) -> int {
|
|||
return left_sidebar_width(view) + SDL_GUTTER_WIDTH + 20
|
||||
}
|
||||
|
||||
daemon_sync_schedule :: proc(sync: ^Daemon_Sync_State) {
|
||||
sync.pending = true
|
||||
sync.last_edit = time.now()
|
||||
}
|
||||
|
||||
daemon_sync_now :: proc(sync: ^Daemon_Sync_State, daemon: ^Daemon_Client, editor: ^Editor, open: bool) {
|
||||
daemon_sync_active_buffer(daemon, editor, open)
|
||||
sync.pending = false
|
||||
}
|
||||
|
||||
daemon_sync_flush_if_due :: proc(sync: ^Daemon_Sync_State, daemon: ^Daemon_Client, editor: ^Editor) {
|
||||
if !sync.pending || !daemon.connected do return
|
||||
if time.diff(sync.last_edit, time.now()) < SDL_DAEMON_SYNC_DEBOUNCE_MS * time.Millisecond do return
|
||||
daemon_sync_now(sync, daemon, editor, false)
|
||||
}
|
||||
|
||||
daemon_begin_owned_start :: proc(workspace: string, owned_daemon: ^Daemon_Process, port_line: ^[dynamic]u8, editor: ^Editor, start_pending: ^bool) -> bool {
|
||||
daemon_stop_process(owned_daemon)
|
||||
clear(port_line)
|
||||
|
|
@ -658,10 +641,10 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
}
|
||||
defer gradle_tasks_panel_destroy(&tasks_panel)
|
||||
|
||||
owned_daemon := Daemon_Process{}
|
||||
defer daemon_stop_process(&owned_daemon)
|
||||
daemon := Daemon_Client{}
|
||||
defer daemon_close(&daemon)
|
||||
owned_daemon := Daemon_Process{}
|
||||
defer daemon_stop_process(&owned_daemon)
|
||||
daemon_sync_state := Daemon_Sync_State{}
|
||||
daemon_port_line: [dynamic]u8
|
||||
defer delete(daemon_port_line)
|
||||
|
|
@ -784,6 +767,8 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
view.mouse_selecting = false
|
||||
} else if handle_completion_click(&editor, &view, &gpu, &completion, &hover, &references, &rename, &search, &daemon_sync_state, event.button.x, event.button.y) {
|
||||
view.mouse_selecting = false
|
||||
} else if handle_hover_tooltip_click(&hover, &navigation, &editor, &view, &gpu, event.button.x, event.button.y) {
|
||||
view.mouse_selecting = false
|
||||
} else if handle_references_click(&references, &navigation, &editor, &view, event.button.x, event.button.y) {
|
||||
view.mouse_selecting = false
|
||||
} else if handle_diagnostics_click(&diagnostics_panel, &editor, &view, event.button.x, event.button.y) {
|
||||
|
|
@ -797,6 +782,9 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
} else if handle_project_tree_click(&editor, &tree, &view, event.button.x, event.button.y) {
|
||||
daemon_sync_now(&daemon_sync_state, &daemon, &editor, true)
|
||||
} else {
|
||||
if hover.open && len(hover.contents) > 0 {
|
||||
hover.open = false
|
||||
}
|
||||
terminal.focused = false
|
||||
git_status.input_focused = false
|
||||
view.mouse_selecting = handle_editor_text_mouse(&editor, &view, &gpu, event.button.x, event.button.y, false)
|
||||
|
|
@ -810,17 +798,17 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
view.tab_dragging = false
|
||||
view.tab_drag_index = 0
|
||||
view.mouse_selecting = false
|
||||
hover.selecting = false
|
||||
}
|
||||
case .MOUSE_MOTION:
|
||||
view.mouse_x = event.motion.x
|
||||
view.mouse_y = event.motion.y
|
||||
mouse_moved_at = time.now()
|
||||
mouse_dwell_handled = false
|
||||
if hover.open && hover.from_mouse {
|
||||
line, column, over_text := editor_line_col_from_mouse(&editor, &view, &gpu, event.motion.x, event.motion.y)
|
||||
if !over_text || line != hover.line || column < hover.word_start || column >= hover.word_end {
|
||||
hover.open = false
|
||||
}
|
||||
if handle_hover_tooltip_motion(&hover, &gpu, event.motion.x, event.motion.y) {
|
||||
// Keep routing drag selection to the hover text.
|
||||
} else if hover.open && hover.from_mouse && len(hover.contents) > 0 && !hover_tooltip_motion_safe(&hover, &editor, &view, &gpu, event.motion.x, event.motion.y) {
|
||||
hover.open = false
|
||||
}
|
||||
if view.resizing_panel == .Bottom_Panel && (event.motion.state & SDL.BUTTON_LMASK) != {} {
|
||||
handle_bottom_panel_resize_motion(&view, event.motion.y)
|
||||
|
|
@ -889,6 +877,7 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
if daemon.connected {
|
||||
daemon_start_reader(&daemon)
|
||||
daemon_send_workspace_open(&daemon, workspace)
|
||||
editor_set_status(&editor, "Opening Kotlin workspace...")
|
||||
daemon_sync_now(&daemon_sync_state, &daemon, &editor, true)
|
||||
if tasks_panel.open && tasks_panel.pending_id == 0 && len(tasks_panel.items) == 0 {
|
||||
gradle_tasks_panel_request(&tasks_panel, &daemon)
|
||||
|
|
@ -906,6 +895,7 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
} else if !daemon_initialized {
|
||||
daemon_start_reader(&daemon)
|
||||
daemon_send_workspace_open(&daemon, workspace)
|
||||
editor_set_status(&editor, "Opening Kotlin workspace...")
|
||||
daemon_sync_now(&daemon_sync_state, &daemon, &editor, true)
|
||||
if tasks_panel.open && tasks_panel.pending_id == 0 && len(tasks_panel.items) == 0 {
|
||||
gradle_tasks_panel_request(&tasks_panel, &daemon)
|
||||
|
|
@ -914,6 +904,7 @@ run_sdl_editor :: proc(initial_workspace: string, daemon_port: int, file_path: s
|
|||
}
|
||||
|
||||
daemon_sync_flush_if_due(&daemon_sync_state, &daemon, &editor)
|
||||
daemon_apply_workspace_response(&daemon, &editor)
|
||||
daemon_apply_latest_diagnostics(&daemon, &editor)
|
||||
completion_popup_apply_response(&completion, &daemon)
|
||||
hover_tooltip_apply_response(&hover, &daemon)
|
||||
|
|
@ -992,6 +983,7 @@ completion_popup_open :: proc(popup: ^Completion_Popup, editor: ^Editor, daemon:
|
|||
|
||||
hover_tooltip_destroy :: proc(hover: ^Hover_Tooltip) {
|
||||
delete(hover.contents)
|
||||
delete(hover.definition_path)
|
||||
}
|
||||
|
||||
hover_tooltip_open :: proc(hover: ^Hover_Tooltip, editor: ^Editor, daemon: ^Daemon_Client) {
|
||||
|
|
@ -1003,6 +995,7 @@ hover_tooltip_open :: proc(hover: ^Hover_Tooltip, editor: ^Editor, daemon: ^Daem
|
|||
hover.from_mouse = false
|
||||
hover.line = line
|
||||
hover.column = column
|
||||
hover.has_definition = false
|
||||
hover.pending_id = daemon_send_hover(daemon, active.path, line, column)
|
||||
hover_tooltip_set_contents(hover, "Loading hover...")
|
||||
}
|
||||
|
|
@ -1040,6 +1033,7 @@ hover_tooltip_open_at_mouse :: proc(hover: ^Hover_Tooltip, editor: ^Editor, view
|
|||
hover.column = column
|
||||
hover.word_start = word_start
|
||||
hover.word_end = word_end
|
||||
hover.has_definition = false
|
||||
hover.pending_id = daemon_send_hover(daemon, active.path, line, column)
|
||||
// No "loading" placeholder for dwell hovers: the tooltip stays invisible
|
||||
// until real contents arrive, so hovering plain code shows nothing.
|
||||
|
|
@ -1291,15 +1285,6 @@ references_panel_accept :: proc(panel: ^References_Panel, navigation: ^Navigatio
|
|||
panel.open = false
|
||||
}
|
||||
|
||||
rename_panel_destroy :: proc(panel: ^Rename_Panel) {
|
||||
delete(panel.input)
|
||||
delete(panel.summary)
|
||||
for edit in panel.edits {
|
||||
delete(edit)
|
||||
}
|
||||
delete(panel.edits)
|
||||
}
|
||||
|
||||
search_panel_destroy :: proc(panel: ^Search_Panel) {
|
||||
delete(panel.input)
|
||||
delete(panel.message)
|
||||
|
|
@ -1383,6 +1368,8 @@ command_palette_accept :: proc(palette: ^Command_Palette, editor: ^Editor, view:
|
|||
switch item.id {
|
||||
case .Open_Folder:
|
||||
folder_dialog_show(dialog, workspace)
|
||||
case .New_File:
|
||||
project_tree_create_new_file(editor, view, tree, workspace, daemon, sync)
|
||||
case .Close_Tab:
|
||||
if editor_close_active(editor) {
|
||||
scroll_sdl_view(editor, view, 0)
|
||||
|
|
@ -1425,6 +1412,31 @@ command_palette_accept :: proc(palette: ^Command_Palette, editor: ^Editor, view:
|
|||
}
|
||||
}
|
||||
|
||||
project_tree_create_new_file :: proc(editor: ^Editor, view: ^SDL_View, tree: ^Project_Tree, workspace: string, daemon: ^Daemon_Client, sync: ^Daemon_Sync_State) -> bool {
|
||||
for index := 1; index < 1000; index += 1 {
|
||||
name := "untitled.txt" if index == 1 else fmt.tprintf("untitled-%d.txt", index)
|
||||
path := fmt.tprintf("%s/%s", workspace, name)
|
||||
if os.is_file(path) do continue
|
||||
|
||||
err := os.write_entire_file(path, []u8{})
|
||||
if err != nil {
|
||||
editor_set_status(editor, fmt.tprintf("Could not create file: %s", path))
|
||||
return false
|
||||
}
|
||||
if !editor_open_or_focus_file(editor, path) {
|
||||
editor_set_status(editor, fmt.tprintf("Created file but could not open it: %s", path))
|
||||
return false
|
||||
}
|
||||
project_tree_rebuild(tree)
|
||||
scroll_project_tree(tree, view, 0)
|
||||
daemon_sync_now(sync, daemon, editor, true)
|
||||
editor_set_status(editor, fmt.tprintf("Created file: %s", path))
|
||||
return true
|
||||
}
|
||||
editor_set_status(editor, "Could not choose a new file name")
|
||||
return false
|
||||
}
|
||||
|
||||
gradle_panel_toggle :: proc(view: ^SDL_View, tasks_panel: ^Gradle_Tasks_Panel, editor: ^Editor, daemon: ^Daemon_Client, sync: ^Daemon_Sync_State) {
|
||||
if tasks_panel.open {
|
||||
tasks_panel.open = false
|
||||
|
|
@ -1589,12 +1601,12 @@ parse_gradle_run_response :: proc(response: string, expected_id: int) -> (string
|
|||
return "", false
|
||||
}
|
||||
if !message.ok {
|
||||
code, _ := json_get_string(value, "code")
|
||||
error_message, _ := json_get_string(value, "message")
|
||||
error_message := protocol_error_message(value)
|
||||
defer delete(error_message)
|
||||
if len(error_message) > 0 {
|
||||
return strings.clone(fmt.tprintf("Gradle failed: %s", error_message)), true
|
||||
}
|
||||
return strings.clone(fmt.tprintf("Gradle failed: %s", code)), true
|
||||
return strings.clone("Gradle failed"), true
|
||||
}
|
||||
result, has_result := json_object_get(value, "result")
|
||||
task := "task"
|
||||
|
|
@ -1871,6 +1883,7 @@ attempt_open_folder :: proc(
|
|||
// just needs the new root; only start a daemon if none is running.
|
||||
if daemon.connected {
|
||||
daemon_send_workspace_open(daemon, workspace^)
|
||||
editor_set_status(editor, "Opening Kotlin workspace...")
|
||||
daemon_sync_now(sync, daemon, editor, true)
|
||||
} else if owned_enabled {
|
||||
_ = daemon_begin_owned_start(workspace^, owned_daemon, port_line, editor, start_pending)
|
||||
|
|
@ -1879,107 +1892,6 @@ attempt_open_folder :: proc(
|
|||
editor_set_status(editor, fmt.tprintf("Opened folder: %s", workspace^))
|
||||
}
|
||||
|
||||
rename_panel_open :: proc(panel: ^Rename_Panel) {
|
||||
panel.open = true
|
||||
panel.pending_id = 0
|
||||
clear(&panel.input)
|
||||
delete(panel.summary)
|
||||
panel.summary = strings.clone("Enter new name, then Enter for preview")
|
||||
for edit in panel.edits {
|
||||
delete(edit)
|
||||
}
|
||||
clear(&panel.edits)
|
||||
}
|
||||
|
||||
rename_panel_insert :: proc(panel: ^Rename_Panel, text: string) {
|
||||
for b in transmute([]u8)text {
|
||||
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_' {
|
||||
append(&panel.input, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rename_panel_backspace :: proc(panel: ^Rename_Panel) {
|
||||
if len(panel.input) > 0 {
|
||||
resize(&panel.input, len(panel.input) - 1)
|
||||
}
|
||||
}
|
||||
|
||||
rename_panel_request :: proc(panel: ^Rename_Panel, editor: ^Editor, daemon: ^Daemon_Client) {
|
||||
active := editor_active_buffer(editor)
|
||||
if active == nil do return
|
||||
if len(panel.input) == 0 {
|
||||
delete(panel.summary)
|
||||
panel.summary = strings.clone("New name is empty")
|
||||
return
|
||||
}
|
||||
|
||||
line, column := buffer_offset_to_line_col(&active.buffer, active.cursor.offset)
|
||||
panel.pending_id = daemon_send_rename(daemon, active.path, line, column, string(panel.input[:]))
|
||||
delete(panel.summary)
|
||||
panel.summary = strings.clone("Loading rename preview...")
|
||||
}
|
||||
|
||||
rename_panel_apply_response :: proc(panel: ^Rename_Panel, daemon: ^Daemon_Client) {
|
||||
if !panel.open || panel.pending_id == 0 do return
|
||||
|
||||
response := daemon_take_response(daemon, panel.pending_id)
|
||||
defer delete(response)
|
||||
if len(response) == 0 do return
|
||||
|
||||
summary, edits, ok := parse_rename_response(string(response[:]), panel.pending_id)
|
||||
defer {
|
||||
delete(summary)
|
||||
for edit in edits {
|
||||
delete(edit)
|
||||
}
|
||||
delete(edits)
|
||||
}
|
||||
if !ok do return
|
||||
|
||||
panel.pending_id = 0
|
||||
delete(panel.summary)
|
||||
panel.summary = strings.clone(summary)
|
||||
for edit in panel.edits {
|
||||
delete(edit)
|
||||
}
|
||||
clear(&panel.edits)
|
||||
for edit in edits {
|
||||
append(&panel.edits, strings.clone(edit))
|
||||
}
|
||||
}
|
||||
|
||||
parse_rename_response :: proc(response: string, expected_id: int) -> (string, [dynamic]string, bool) {
|
||||
edits: [dynamic]string
|
||||
message, value, ok := parse_protocol_message(response)
|
||||
if !ok do return "", edits, false
|
||||
defer json.destroy_value(value)
|
||||
if message.kind != .Response || message.id != expected_id || !message.ok do return "", edits, false
|
||||
|
||||
result, has_result := json_object_get(value, "result")
|
||||
if !has_result do return "", edits, false
|
||||
old_name, _ := json_get_string(result, "oldName")
|
||||
new_name, _ := json_get_string(result, "newName")
|
||||
edits_value, has_edits := json_object_get(result, "edits")
|
||||
if !has_edits do return "", edits, false
|
||||
|
||||
#partial switch items in edits_value {
|
||||
case json.Array:
|
||||
for item in items {
|
||||
path, has_path := json_get_string(item, "path")
|
||||
if !has_path do continue
|
||||
line, _ := json_get_int(item, "line")
|
||||
column, _ := json_get_int(item, "column")
|
||||
_, file := os.split_path(path)
|
||||
label := fmt.tprintf("%s:%d:%d", file, line, column)
|
||||
append(&edits, strings.clone(label))
|
||||
}
|
||||
}
|
||||
|
||||
summary := fmt.tprintf("Preview: %s -> %s (%d edits)", old_name, new_name, len(edits))
|
||||
return strings.clone(summary), edits, true
|
||||
}
|
||||
|
||||
hover_tooltip_apply_response :: proc(hover: ^Hover_Tooltip, daemon: ^Daemon_Client) {
|
||||
if !hover.open || hover.pending_id == 0 do return
|
||||
|
||||
|
|
@ -1987,8 +1899,11 @@ hover_tooltip_apply_response :: proc(hover: ^Hover_Tooltip, daemon: ^Daemon_Clie
|
|||
defer delete(response)
|
||||
if len(response) == 0 do return
|
||||
|
||||
contents, ok := parse_hover_response(string(response[:]), hover.pending_id)
|
||||
defer delete(contents)
|
||||
contents, definition_path, definition_line, definition_column, has_definition, ok := parse_hover_response(string(response[:]), hover.pending_id)
|
||||
defer {
|
||||
delete(contents)
|
||||
delete(definition_path)
|
||||
}
|
||||
if !ok do return
|
||||
|
||||
hover.pending_id = 0
|
||||
|
|
@ -2001,24 +1916,149 @@ hover_tooltip_apply_response :: proc(hover: ^Hover_Tooltip, daemon: ^Daemon_Clie
|
|||
} else {
|
||||
hover_tooltip_set_contents(hover, contents)
|
||||
}
|
||||
hover_tooltip_set_definition(hover, definition_path, definition_line, definition_column, has_definition)
|
||||
}
|
||||
|
||||
hover_tooltip_set_contents :: proc(hover: ^Hover_Tooltip, contents: string) {
|
||||
delete(hover.contents)
|
||||
hover.contents = strings.clone(contents)
|
||||
hover.selection_active = false
|
||||
hover.selecting = false
|
||||
hover.selection_anchor = 0
|
||||
hover.selection_cursor = 0
|
||||
}
|
||||
|
||||
parse_hover_response :: proc(response: string, expected_id: int) -> (string, bool) {
|
||||
message, value, ok := parse_protocol_message(response)
|
||||
if !ok do return "", false
|
||||
hover_tooltip_set_definition :: proc(hover: ^Hover_Tooltip, path: string, line, column: int, has_definition: bool) {
|
||||
delete(hover.definition_path)
|
||||
hover.definition_path = strings.clone(path)
|
||||
hover.definition_line = line
|
||||
hover.definition_column = column
|
||||
hover.has_definition = has_definition && len(path) > 0
|
||||
}
|
||||
|
||||
parse_hover_response :: proc(response: string, expected_id: int) -> (contents: string, definition_path: string, definition_line: int, definition_column: int, has_definition: bool, ok: bool) {
|
||||
message, value, parsed := parse_protocol_message(response)
|
||||
if !parsed do return
|
||||
defer json.destroy_value(value)
|
||||
if message.kind != .Response || message.id != expected_id || !message.ok do return "", false
|
||||
if message.kind != .Response || message.id != expected_id || !message.ok do return
|
||||
|
||||
result, has_result := json_object_get(value, "result")
|
||||
if !has_result do return "", false
|
||||
contents, has_contents := json_get_string(result, "contents")
|
||||
if !has_contents do return "", true
|
||||
return strings.clone(contents), true
|
||||
if !has_result do return
|
||||
parsed_contents, has_contents := json_get_string(result, "contents")
|
||||
if has_contents {
|
||||
contents = strings.clone(parsed_contents)
|
||||
}
|
||||
if definition, has_def := json_object_get(result, "definition"); has_def {
|
||||
if path, has_path := json_get_string(definition, "path"); has_path {
|
||||
definition_path = strings.clone(path)
|
||||
definition_line, _ = json_get_int(definition, "line")
|
||||
definition_column, _ = json_get_int(definition, "column")
|
||||
has_definition = true
|
||||
}
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
hover_tooltip_hit :: proc(hover: ^Hover_Tooltip, x, y: f32) -> bool {
|
||||
return hover.open && len(hover.contents) > 0 && x >= hover.x && x < hover.x + hover.width && y >= hover.y && y < hover.y + hover.height
|
||||
}
|
||||
|
||||
hover_tooltip_motion_safe :: proc(hover: ^Hover_Tooltip, editor: ^Editor, view: ^SDL_View, gpu: ^GPU_Renderer, x, y: f32) -> bool {
|
||||
if hover_tooltip_hit(hover, x, y) do return true
|
||||
active := editor_active_buffer(editor)
|
||||
if active == nil do return false
|
||||
if hover.line < 0 || hover.line >= buffer_line_count(&active.buffer) do return false
|
||||
|
||||
word_x1 := editor_column_pixel_x_gpu(gpu, view, &active.buffer, hover.line, hover.word_start)
|
||||
word_x2 := editor_column_pixel_x_gpu(gpu, view, &active.buffer, hover.line, hover.word_end)
|
||||
word_y := f32(SDL_EDITOR_TEXT_Y + (hover.line - view.first_line) * SDL_LINE_HEIGHT)
|
||||
if x >= word_x1 && x < word_x2 && y >= word_y - 3 && y < word_y + SDL_LINE_HEIGHT {
|
||||
return true
|
||||
}
|
||||
|
||||
// Let the pointer travel from the source word to the tooltip without the
|
||||
// dwell hover disappearing in the gap between them.
|
||||
left := min_f32(word_x1, hover.x) - 10
|
||||
right := max_f32(word_x2, hover.x + hover.width) + 10
|
||||
top := min_f32(word_y - 3, hover.y) - 10
|
||||
bottom := max_f32(word_y + SDL_LINE_HEIGHT, hover.y + hover.height) + 10
|
||||
return x >= left && x < right && y >= top && y < bottom
|
||||
}
|
||||
|
||||
hover_tooltip_action_hit :: proc(hover: ^Hover_Tooltip, x, y: f32) -> bool {
|
||||
if !hover.has_definition do return false
|
||||
action_y := hover.y + hover.height - 25
|
||||
return x >= hover.x + 8 && x < hover.x + 142 && y >= action_y && y < action_y + 18
|
||||
}
|
||||
|
||||
hover_visible_lines :: proc(hover: ^Hover_Tooltip) -> []string {
|
||||
all_lines, _ := strings.split_lines(hover.contents, context.temp_allocator)
|
||||
line_count := min_int(len(all_lines), 12)
|
||||
lines := all_lines[:line_count]
|
||||
for len(lines) > 0 && len(strings.trim_space(lines[len(lines) - 1])) == 0 {
|
||||
lines = lines[:len(lines) - 1]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
hover_line_start_offset :: proc(hover: ^Hover_Tooltip, line_index: int) -> int {
|
||||
offset := 0
|
||||
current := 0
|
||||
for i := 0; i < len(hover.contents) && current < line_index; i += 1 {
|
||||
offset += 1
|
||||
if hover.contents[i] == '\n' {
|
||||
current += 1
|
||||
}
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
hover_text_offset_at :: proc(hover: ^Hover_Tooltip, gpu: ^GPU_Renderer, x, y: f32) -> int {
|
||||
lines := hover_visible_lines(hover)
|
||||
if len(lines) == 0 do return 0
|
||||
line_index := clamp_int(int((y - (hover.y + 10)) / 16), 0, len(lines) - 1)
|
||||
line := lines[line_index]
|
||||
rel_x := max_f32(x - (hover.x + 10), 0)
|
||||
column := int((rel_x + max_f32(gpu.font_advance, 1) * 0.5) / max_f32(gpu.font_advance, 1))
|
||||
column = clamp_int(column, 0, len(line))
|
||||
return min_int(hover_line_start_offset(hover, line_index) + column, len(hover.contents))
|
||||
}
|
||||
|
||||
hover_tooltip_copy_selection :: proc(hover: ^Hover_Tooltip) -> bool {
|
||||
if !hover.selection_active do return false
|
||||
start := min_int(hover.selection_anchor, hover.selection_cursor)
|
||||
end := max_int(hover.selection_anchor, hover.selection_cursor)
|
||||
if start == end do return false
|
||||
c_text, err := strings.clone_to_cstring(hover.contents[start:end], context.temp_allocator)
|
||||
if err != nil do return false
|
||||
return SDL.SetClipboardText(c_text)
|
||||
}
|
||||
|
||||
handle_hover_tooltip_click :: proc(hover: ^Hover_Tooltip, navigation: ^Navigation_History, editor: ^Editor, view: ^SDL_View, gpu: ^GPU_Renderer, x, y: f32) -> bool {
|
||||
if !hover_tooltip_hit(hover, x, y) do return false
|
||||
if hover_tooltip_action_hit(hover, x, y) {
|
||||
active := editor_active_buffer(editor)
|
||||
if active != nil {
|
||||
navigation_push(&navigation.back, active.path, active.cursor.offset)
|
||||
navigation_clear_stack(&navigation.forward)
|
||||
}
|
||||
_ = editor_jump_current_tab_to_location(editor, view, hover.definition_path, hover.definition_line, hover.definition_column)
|
||||
hover.open = false
|
||||
return true
|
||||
}
|
||||
offset := hover_text_offset_at(hover, gpu, x, y)
|
||||
hover.selecting = true
|
||||
hover.selection_active = true
|
||||
hover.selection_anchor = offset
|
||||
hover.selection_cursor = offset
|
||||
return true
|
||||
}
|
||||
|
||||
handle_hover_tooltip_motion :: proc(hover: ^Hover_Tooltip, gpu: ^GPU_Renderer, x, y: f32) -> bool {
|
||||
if !hover.selecting do return false
|
||||
hover.selection_cursor = hover_text_offset_at(hover, gpu, x, y)
|
||||
return true
|
||||
}
|
||||
|
||||
completion_popup_apply_response :: proc(popup: ^Completion_Popup, daemon: ^Daemon_Client) {
|
||||
|
|
@ -2160,6 +2200,7 @@ project_tree_clear_files :: proc(tree: ^Project_Tree) {
|
|||
|
||||
project_tree_rebuild :: proc(tree: ^Project_Tree) {
|
||||
project_tree_clear_files(tree)
|
||||
tree.truncated = false
|
||||
project_tree_append_dir(tree, tree.workspace, 0)
|
||||
}
|
||||
|
||||
|
|
@ -2194,12 +2235,16 @@ workspace_reload :: proc(editor: ^Editor, view: ^SDL_View, tree: ^Project_Tree,
|
|||
scroll_project_tree(tree, view, 0)
|
||||
editor_clear_all_diagnostics(editor)
|
||||
daemon_send_workspace_open(daemon, workspace)
|
||||
editor_set_status(editor, "Opening Kotlin workspace...")
|
||||
daemon_sync_now(sync, daemon, editor, true)
|
||||
editor_set_status(editor, "Workspace reloaded")
|
||||
}
|
||||
|
||||
project_tree_append_dir :: proc(tree: ^Project_Tree, dir: string, depth: int) {
|
||||
if depth > 12 || len(tree.files) >= 5000 do return
|
||||
if depth > 12 do return
|
||||
if len(tree.files) >= 5000 {
|
||||
tree.truncated = true
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := os.read_all_directory_by_path(dir, context.allocator)
|
||||
if err != nil do return
|
||||
|
|
@ -2213,7 +2258,10 @@ project_tree_append_dir :: proc(tree: ^Project_Tree, dir: string, depth: int) {
|
|||
})
|
||||
|
||||
for entry in entries {
|
||||
if len(tree.files) >= 5000 do return
|
||||
if len(tree.files) >= 5000 {
|
||||
tree.truncated = true
|
||||
return
|
||||
}
|
||||
if project_tree_skip(entry.name) do continue
|
||||
|
||||
path := fmt.tprintf("%s/%s", dir, entry.name)
|
||||
|
|
@ -3011,6 +3059,10 @@ handle_sdl_key :: proc(editor: ^Editor, view: ^SDL_View, tree: ^Project_Tree, wo
|
|||
active := editor_active_buffer(editor)
|
||||
if active == nil do return false
|
||||
|
||||
if key == SDL.K_C && (mod & SDL.KMOD_CTRL) != SDL.KMOD_NONE && hover_tooltip_copy_selection(hover) {
|
||||
return false
|
||||
}
|
||||
|
||||
if command_palette.open {
|
||||
switch key {
|
||||
case SDL.K_ESCAPE:
|
||||
|
|
@ -3052,8 +3104,12 @@ handle_sdl_key :: proc(editor: ^Editor, view: ^SDL_View, tree: ^Project_Tree, wo
|
|||
rename_panel_backspace(rename)
|
||||
return false
|
||||
case SDL.K_RETURN:
|
||||
daemon_sync_now(sync, daemon, editor, false)
|
||||
rename_panel_request(rename, editor, daemon)
|
||||
if len(rename.edits) > 0 {
|
||||
_ = rename_panel_apply_edits(rename, editor, sync)
|
||||
} else {
|
||||
daemon_sync_now(sync, daemon, editor, false)
|
||||
rename_panel_request(rename, editor, daemon)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -3352,7 +3408,8 @@ handle_sdl_key :: proc(editor: ^Editor, view: ^SDL_View, tree: ^Project_Tree, wo
|
|||
|
||||
switch key {
|
||||
case SDL.K_ESCAPE:
|
||||
// Window close is handled by the window manager; escape currently only clears intent.
|
||||
hover.open = false
|
||||
return false
|
||||
case SDL.K_RETURN:
|
||||
editor_insert_newline_auto_indent(active)
|
||||
close_stale_edit_overlays(completion, hover, references, rename)
|
||||
|
|
@ -4096,7 +4153,7 @@ render_rename_panel :: proc(renderer: ^SDL.Renderer, panel: ^Rename_Panel) {
|
|||
item_y := y + 68
|
||||
for edit, index in panel.edits {
|
||||
if index >= 8 do break
|
||||
label := fmt.ctprintf("%s", edit)
|
||||
label := fmt.ctprintf("%s", edit.label)
|
||||
_ = SDL.RenderDebugText(renderer, x + 10, item_y, label)
|
||||
item_y += 16
|
||||
}
|
||||
|
|
@ -4122,7 +4179,7 @@ render_rename_panel_gpu :: proc(gpu: ^GPU_Renderer, panel: ^Rename_Panel) {
|
|||
item_y := y + 68
|
||||
for edit, index in panel.edits {
|
||||
if index >= 8 do break
|
||||
gpu_text_limited(gpu, x + 10, item_y, edit, 56, 220, 225, 235, 255)
|
||||
gpu_text_limited(gpu, x + 10, item_y, edit.label, 56, 220, 225, 235, 255)
|
||||
item_y += 16
|
||||
}
|
||||
}
|
||||
|
|
@ -4597,12 +4654,7 @@ render_hover_tooltip_gpu :: proc(gpu: ^GPU_Renderer, editor: ^Editor, view: ^SDL
|
|||
// Hover contents can span several lines (signatures, docs), so the box
|
||||
// is sized to the visible lines and each line is drawn separately —
|
||||
// gpu_text would otherwise continue multi-line text below the box.
|
||||
all_lines, _ := strings.split_lines(hover.contents, context.temp_allocator)
|
||||
line_count := min_int(len(all_lines), 12)
|
||||
lines := all_lines[:line_count]
|
||||
for len(lines) > 0 && len(strings.trim_space(lines[len(lines) - 1])) == 0 {
|
||||
lines = lines[:len(lines) - 1]
|
||||
}
|
||||
lines := hover_visible_lines(hover)
|
||||
if len(lines) == 0 do return
|
||||
|
||||
max_line_width := 0
|
||||
|
|
@ -4610,7 +4662,8 @@ render_hover_tooltip_gpu :: proc(gpu: ^GPU_Renderer, editor: ^Editor, view: ^SDL
|
|||
max_line_width = max_int(max_line_width, gpu_font_text_width(gpu, line))
|
||||
}
|
||||
width := f32(max_int(180, min_int(560, max_line_width + 20)))
|
||||
height := f32(18 + len(lines) * 16)
|
||||
action_height := 26 if hover.has_definition else 0
|
||||
height := f32(18 + len(lines) * 16 + action_height)
|
||||
|
||||
anchor_line := min_int(hover.line, buffer_line_count(&active.buffer) - 1)
|
||||
anchor_col := hover.word_start if hover.from_mouse else hover.column
|
||||
|
|
@ -4618,15 +4671,42 @@ render_hover_tooltip_gpu :: proc(gpu: ^GPU_Renderer, editor: ^Editor, view: ^SDL
|
|||
y := f32(SDL_EDITOR_TEXT_Y + (anchor_line - view.first_line + 2) * SDL_LINE_HEIGHT)
|
||||
x = max_f32(min_f32(x, f32(content_right_edge(gpu.width)) - width - 4), 0)
|
||||
y = max_f32(min_f32(y, f32(editor_content_bottom(view)) - height - 4), SDL_EDITOR_TOP)
|
||||
hover.x = x
|
||||
hover.y = y
|
||||
hover.width = width
|
||||
hover.height = height
|
||||
|
||||
gpu_rect(gpu, x, y, width, height, 30, 34, 45, 255)
|
||||
gpu_rect_outline(gpu, x, y, width, height, 100, 125, 165, 255)
|
||||
max_columns := max_int(int((width - 20) / max_f32(gpu.font_advance, 1)), 1)
|
||||
line_y := y + 10
|
||||
selection_start := min_int(hover.selection_anchor, hover.selection_cursor)
|
||||
selection_end := max_int(hover.selection_anchor, hover.selection_cursor)
|
||||
content_offset := 0
|
||||
syntax_state := Syntax_State{}
|
||||
for line in lines {
|
||||
gpu_text_limited(gpu, x + 10, line_y, line, max_columns, 220, 225, 235, 255)
|
||||
visible_len := min_int(len(line), max_columns)
|
||||
if hover.selection_active && selection_start != selection_end {
|
||||
line_start := content_offset
|
||||
line_end := content_offset + visible_len
|
||||
if selection_end > line_start && selection_start < line_end {
|
||||
start_col := clamp_int(selection_start - line_start, 0, visible_len)
|
||||
end_col := clamp_int(selection_end - line_start, start_col, visible_len)
|
||||
if end_col > start_col {
|
||||
gpu_rect(gpu, x + 10 + f32(start_col) * gpu.font_advance, line_y - 2, f32(end_col - start_col) * gpu.font_advance, 16, 62, 83, 125, 190)
|
||||
}
|
||||
}
|
||||
}
|
||||
syntax_state = render_syntax_line_gpu(gpu, x + 10, line_y, line, max_columns, syntax_state)
|
||||
content_offset += len(line) + 1
|
||||
line_y += 16
|
||||
}
|
||||
if hover.has_definition {
|
||||
action_y := y + height - 25
|
||||
gpu_rect(gpu, x + 8, action_y, 134, 18, 43, 54, 72, 255)
|
||||
gpu_rect_outline(gpu, x + 8, action_y, 134, 18, 77, 155, 230, 255)
|
||||
gpu_text(gpu, x + 16, action_y + 4, "Go to definition", 160, 205, 255, 255)
|
||||
}
|
||||
}
|
||||
|
||||
render_completion_popup :: proc(renderer: ^SDL.Renderer, editor: ^Editor, view: ^SDL_View, popup: ^Completion_Popup) {
|
||||
|
|
@ -4808,7 +4888,8 @@ render_project_tree :: proc(renderer: ^SDL.Renderer, tree: ^Project_Tree, view:
|
|||
_ = SDL.RenderFillRect(renderer, &sidebar)
|
||||
|
||||
_ = SDL.SetRenderDrawColor(renderer, 145, 165, 205, 255)
|
||||
_ = SDL.RenderDebugText(renderer, 12, 8, "Project")
|
||||
title := "Project (truncated)" if tree.truncated else "Project"
|
||||
_ = SDL.RenderDebugText(renderer, 12, 8, fmt.ctprintf("%s", title))
|
||||
|
||||
y: f32 = 28
|
||||
end := min_int(view.tree_first + visible_tree_rows(view), len(tree.files))
|
||||
|
|
@ -4841,7 +4922,8 @@ render_project_tree_gpu :: proc(gpu: ^GPU_Renderer, tree: ^Project_Tree, view: ^
|
|||
if !view.explorer_visible || view.left_tab != .Files do return
|
||||
left := f32(content_left_edge())
|
||||
sidebar_width := left_sidebar_width(view)
|
||||
gpu_text(gpu, left + 14, SDL_TREE_HEADER_Y, "EXPLORER", 150, 154, 162, 255)
|
||||
title := "EXPLORER (TRUNCATED)" if tree.truncated else "EXPLORER"
|
||||
gpu_text(gpu, left + 14, SDL_TREE_HEADER_Y, title, 150, 154, 162, 255)
|
||||
gpu_rect(gpu, left + 14, SDL_TREE_HEADER_Y + 18, f32(sidebar_width) - left - 28, 1, 52, 54, 60, 255)
|
||||
|
||||
hovered_index, has_hover := project_tree_row_at(tree, view, view.mouse_x, view.mouse_y)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue