j
This commit is contained in:
parent
5b65c4c0e4
commit
985aa16236
13 changed files with 1381 additions and 241 deletions
283
client/odin/rename_panel.odin
Normal file
283
client/odin/rename_panel.odin
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import json "core:encoding/json"
|
||||
|
||||
Rename_Edit :: struct {
|
||||
path: string,
|
||||
line: int,
|
||||
column: int,
|
||||
old_text: string,
|
||||
new_text: string,
|
||||
label: string,
|
||||
}
|
||||
|
||||
Rename_File_State :: struct {
|
||||
path: string,
|
||||
original_storage: []u8,
|
||||
buffer: Buffer,
|
||||
}
|
||||
|
||||
Rename_Panel :: struct {
|
||||
open: bool,
|
||||
pending_id: int,
|
||||
input: [dynamic]u8,
|
||||
summary: string,
|
||||
edits: [dynamic]Rename_Edit,
|
||||
}
|
||||
|
||||
rename_panel_destroy :: proc(panel: ^Rename_Panel) {
|
||||
delete(panel.input)
|
||||
delete(panel.summary)
|
||||
rename_panel_clear_edits(panel)
|
||||
delete(panel.edits)
|
||||
}
|
||||
|
||||
rename_panel_clear_edits :: proc(panel: ^Rename_Panel) {
|
||||
for edit in panel.edits {
|
||||
delete(edit.path)
|
||||
delete(edit.old_text)
|
||||
delete(edit.new_text)
|
||||
delete(edit.label)
|
||||
}
|
||||
clear(&panel.edits)
|
||||
}
|
||||
|
||||
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")
|
||||
rename_panel_clear_edits(panel)
|
||||
}
|
||||
|
||||
rename_panel_insert :: proc(panel: ^Rename_Panel, text: string) {
|
||||
rename_panel_clear_edits(panel)
|
||||
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) {
|
||||
rename_panel_clear_edits(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 {
|
||||
rename_panel_set_summary(panel, "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[:]))
|
||||
rename_panel_set_summary(panel, "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)
|
||||
rename_edits_destroy(edits[:])
|
||||
delete(edits)
|
||||
}
|
||||
if !ok do return
|
||||
|
||||
panel.pending_id = 0
|
||||
rename_panel_set_summary(panel, summary)
|
||||
rename_panel_clear_edits(panel)
|
||||
for edit in edits {
|
||||
append(&panel.edits, rename_edit_clone(edit))
|
||||
}
|
||||
}
|
||||
|
||||
rename_panel_apply_edits :: proc(panel: ^Rename_Panel, editor: ^Editor, sync: ^Daemon_Sync_State) -> bool {
|
||||
if !panel.open || len(panel.edits) == 0 do return false
|
||||
|
||||
file_states: [dynamic]Rename_File_State
|
||||
defer rename_file_states_destroy(file_states[:])
|
||||
defer delete(file_states)
|
||||
|
||||
for edit in panel.edits {
|
||||
buffer := editor_buffer_for_rename_edit(editor, edit.path)
|
||||
target_buffer: ^Buffer
|
||||
if buffer != nil {
|
||||
if buffer.dirty {
|
||||
rename_panel_set_summary(panel, "Save affected buffers before applying rename")
|
||||
return false
|
||||
}
|
||||
target_buffer = &buffer.buffer
|
||||
} else {
|
||||
file_state := rename_file_state_for_path(&file_states, edit.path)
|
||||
if file_state == nil {
|
||||
rename_panel_set_summary(panel, fmt.tprintf("Could not read rename target: %s", edit.path))
|
||||
return false
|
||||
}
|
||||
target_buffer = &file_state.buffer
|
||||
}
|
||||
|
||||
offset := buffer_line_col_to_offset(target_buffer, max_int(edit.line - 1, 0), max_int(edit.column - 1, 0))
|
||||
existing := buffer_range_bytes(target_buffer, offset, len(edit.old_text))
|
||||
matches := bytes_equal(existing[:], transmute([]u8)edit.old_text)
|
||||
delete(existing)
|
||||
if !matches {
|
||||
rename_panel_set_summary(panel, "Rename preview is stale; request it again")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for index := len(panel.edits) - 1; index >= 0; index -= 1 {
|
||||
edit := panel.edits[index]
|
||||
buffer := editor_buffer_for_rename_edit(editor, edit.path)
|
||||
if buffer != nil {
|
||||
offset := buffer_line_col_to_offset(&buffer.buffer, max_int(edit.line - 1, 0), max_int(edit.column - 1, 0))
|
||||
buffer_replace_range(&buffer.buffer, offset, len(edit.old_text), edit.new_text)
|
||||
buffer.cursor.offset = offset + len(edit.new_text)
|
||||
_, buffer.cursor.wanted_column = buffer_offset_to_line_col(&buffer.buffer, buffer.cursor.offset)
|
||||
editor_clear_selection(buffer)
|
||||
editor_update_dirty(buffer)
|
||||
} else if file_state := rename_file_state_find(file_states[:], edit.path); file_state != nil {
|
||||
offset := buffer_line_col_to_offset(&file_state.buffer, max_int(edit.line - 1, 0), max_int(edit.column - 1, 0))
|
||||
buffer_replace_range(&file_state.buffer, offset, len(edit.old_text), edit.new_text)
|
||||
}
|
||||
}
|
||||
|
||||
for &file_state in file_states {
|
||||
text := buffer_bytes(&file_state.buffer)
|
||||
err := os.write_entire_file(file_state.path, text[:])
|
||||
delete(text)
|
||||
if err != nil {
|
||||
rename_panel_set_summary(panel, fmt.tprintf("Could not write rename target: %s", file_state.path))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
applied := len(panel.edits)
|
||||
rename_panel_clear_edits(panel)
|
||||
panel.open = false
|
||||
editor_set_status(editor, fmt.tprintf("Applied rename preview (%d edits)", applied))
|
||||
daemon_sync_schedule(sync)
|
||||
return true
|
||||
}
|
||||
|
||||
rename_file_state_for_path :: proc(states: ^[dynamic]Rename_File_State, path: string) -> ^Rename_File_State {
|
||||
if existing := rename_file_state_find(states^[:], path); existing != nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
data, err := os.read_entire_file(path, context.allocator)
|
||||
if err != nil || len(data) == 0 {
|
||||
if err == nil do delete(data)
|
||||
return nil
|
||||
}
|
||||
append(states, Rename_File_State{
|
||||
path = strings.clone(path),
|
||||
original_storage = data,
|
||||
buffer = buffer_make(string(data)),
|
||||
})
|
||||
return &states^[len(states^) - 1]
|
||||
}
|
||||
|
||||
rename_file_state_find :: proc(states: []Rename_File_State, path: string) -> ^Rename_File_State {
|
||||
for &state in states {
|
||||
if state.path == path do return &state
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rename_file_states_destroy :: proc(states: []Rename_File_State) {
|
||||
for &state in states {
|
||||
delete(state.path)
|
||||
buffer_destroy(&state.buffer)
|
||||
delete(state.original_storage)
|
||||
}
|
||||
}
|
||||
|
||||
editor_buffer_for_rename_edit :: proc(editor: ^Editor, path: string) -> ^Editor_Buffer {
|
||||
for &buffer in editor.buffers {
|
||||
if buffer.path == path || buffer.daemon_path == path {
|
||||
return &buffer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rename_panel_set_summary :: proc(panel: ^Rename_Panel, message: string) {
|
||||
delete(panel.summary)
|
||||
panel.summary = strings.clone(message)
|
||||
}
|
||||
|
||||
parse_rename_response :: proc(response: string, expected_id: int) -> (string, [dynamic]Rename_Edit, bool) {
|
||||
edits: [dynamic]Rename_Edit
|
||||
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")
|
||||
old_text, _ := json_get_string(item, "oldText")
|
||||
new_text, _ := json_get_string(item, "newText")
|
||||
_, file := os.split_path(path)
|
||||
label := fmt.tprintf("%s:%d:%d", file, line, column)
|
||||
append(&edits, Rename_Edit{
|
||||
path = strings.clone(path),
|
||||
line = line,
|
||||
column = column,
|
||||
old_text = strings.clone(old_text),
|
||||
new_text = strings.clone(new_text),
|
||||
label = strings.clone(label),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
summary := fmt.tprintf("Preview: %s -> %s (%d edits). Press Enter again to apply.", old_name, new_name, len(edits))
|
||||
return strings.clone(summary), edits, true
|
||||
}
|
||||
|
||||
rename_edit_clone :: proc(edit: Rename_Edit) -> Rename_Edit {
|
||||
return Rename_Edit{
|
||||
path = strings.clone(edit.path),
|
||||
line = edit.line,
|
||||
column = edit.column,
|
||||
old_text = strings.clone(edit.old_text),
|
||||
new_text = strings.clone(edit.new_text),
|
||||
label = strings.clone(edit.label),
|
||||
}
|
||||
}
|
||||
|
||||
rename_edits_destroy :: proc(edits: []Rename_Edit) {
|
||||
for edit in edits {
|
||||
delete(edit.path)
|
||||
delete(edit.old_text)
|
||||
delete(edit.new_text)
|
||||
delete(edit.label)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue