Compare commits
10 Commits
04311187af
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f7fa920bd | |||
| cfa6e37b85 | |||
| 83a2de0386 | |||
| b2b4ffe1bd | |||
| f58b5e88a7 | |||
| 5275161048 | |||
| c4a1c89143 | |||
| 921928e96f | |||
| 33eb774c97 | |||
| 45e2cd1f8f |
@@ -12,7 +12,7 @@
|
|||||||
],
|
],
|
||||||
"build_systems": [
|
"build_systems": [
|
||||||
{
|
{
|
||||||
"cmd": ["odin", "build", "src", "-debug"],
|
"cmd": ["odin", "build", "src", "-debug", "-o:none"],
|
||||||
"name": "odit",
|
"name": "odit",
|
||||||
"selector": "source.odin",
|
"selector": "source.odin",
|
||||||
"working_dir": "${project_path}"
|
"working_dir": "${project_path}"
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
],
|
],
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"path": "."
|
"path": ".",
|
||||||
}
|
"folder_exclude_patterns": ["*bin*"],
|
||||||
|
"binary_file_patterns": ["*.bin"]
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
530
src/main.odin
530
src/main.odin
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
- DONE implement movement up and down by paragraph
|
- DONE implement movement up and down by paragraph
|
||||||
- DONE implement scrolling/viewport
|
- DONE implement scrolling/viewport
|
||||||
- TODO implement deletion by word
|
- DONE implement deletion by word
|
||||||
- TODO implement selection
|
- DONE implement selection
|
||||||
- TODO implement duplicate selection/line
|
- TODO implement duplicate selection/line
|
||||||
- TODO implement move selection/line up/down
|
- TODO implement move selection/line up/down
|
||||||
- TODO implement copy/cut/paste
|
- TODO implement copy/cut/paste
|
||||||
@@ -16,6 +16,7 @@ package main
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:slice"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import utf8 "core:unicode/utf8"
|
import utf8 "core:unicode/utf8"
|
||||||
import r "vendor:raylib"
|
import r "vendor:raylib"
|
||||||
@@ -32,38 +33,67 @@ Viewport :: struct {
|
|||||||
end: int,
|
end: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor :: struct {
|
Position :: struct {
|
||||||
line: int,
|
line: int,
|
||||||
char: int,
|
char: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
window_width := 800
|
Cursor :: distinct Position
|
||||||
window_height := 600
|
|
||||||
font_size: f32 = 20.0
|
Selection :: struct {
|
||||||
text_height: f32
|
active: bool,
|
||||||
cursor: Cursor
|
start: Position,
|
||||||
lines: [dynamic][dynamic]u8
|
end: Position,
|
||||||
viewport_size := window_height / int(font_size)
|
|
||||||
viewport := Viewport {
|
|
||||||
start = 0,
|
|
||||||
end = viewport_size,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Range :: struct {
|
||||||
|
start: int,
|
||||||
|
end: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor :: struct {
|
||||||
|
window_width: i32,
|
||||||
|
window_height: i32,
|
||||||
|
font_size: f32,
|
||||||
|
text_height: f32,
|
||||||
|
cursor: Cursor,
|
||||||
|
lines: [dynamic][dynamic]u8,
|
||||||
|
viewport: Viewport,
|
||||||
|
viewport_size: int,
|
||||||
|
selection: Selection,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_WINDOW_WIDTH :: 800
|
||||||
|
DEFAULT_WINDOW_HEIGHT :: 600
|
||||||
|
DEFAULT_FONT_SIZE :: 20
|
||||||
|
DEFAULT_VIEWPORT_SIZE :: DEFAULT_WINDOW_HEIGHT / DEFAULT_FONT_SIZE
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
r.InitWindow(i32(window_width), i32(window_height), "odit")
|
editor := Editor {
|
||||||
|
window_width = DEFAULT_WINDOW_WIDTH,
|
||||||
|
window_height = DEFAULT_WINDOW_HEIGHT,
|
||||||
|
font_size = DEFAULT_FONT_SIZE,
|
||||||
|
viewport_size = DEFAULT_WINDOW_HEIGHT / DEFAULT_FONT_SIZE,
|
||||||
|
viewport = Viewport{start = 0, end = DEFAULT_VIEWPORT_SIZE},
|
||||||
|
selection = Selection{},
|
||||||
|
text_height = {},
|
||||||
|
cursor = {},
|
||||||
|
lines = {},
|
||||||
|
}
|
||||||
|
r.InitWindow(editor.window_width, editor.window_height, "odit")
|
||||||
r.SetTargetFPS(60)
|
r.SetTargetFPS(60)
|
||||||
default_font := r.GetFontDefault()
|
default_font := r.GetFontDefault()
|
||||||
font := r.LoadFont("/Users/grant/Library/Fonts/BerkeleyMono-Regular.otf")
|
font := r.LoadFont("/Users/grant/Library/Fonts/BerkeleyMono-Regular.otf")
|
||||||
r.GuiSetFont(font)
|
r.GuiSetFont(font)
|
||||||
two_lines_size := r.MeasureTextEx(font, "foo\nbar", font_size, 0.0)
|
two_lines_size := r.MeasureTextEx(font, "foo\nbar", editor.font_size, 0.0)
|
||||||
text_height = two_lines_size[1] / 2
|
editor.text_height = two_lines_size[1] / 2
|
||||||
|
|
||||||
content, _success := read_file("src/main.odin")
|
content, _success := read_file("src/main.odin")
|
||||||
|
|
||||||
for line in strings.split_lines_iterator(&content) {
|
for line in strings.split_lines_iterator(&content) {
|
||||||
bs := make([dynamic]u8, 0, len(line) == 0 ? 1 : len(line) * 2)
|
bs := make([dynamic]u8, 0, len(line) == 0 ? 1 : len(line) * 2)
|
||||||
append(&bs, line)
|
append(&bs, line)
|
||||||
append(&lines, bs)
|
append(&editor.lines, bs)
|
||||||
}
|
}
|
||||||
|
|
||||||
text: string
|
text: string
|
||||||
@@ -72,101 +102,42 @@ main :: proc() {
|
|||||||
for c := r.GetCharPressed(); c != rune(0); c = r.GetCharPressed() {
|
for c := r.GetCharPressed(); c != rune(0); c = r.GetCharPressed() {
|
||||||
chars, _ := utf8.encode_rune(c)
|
chars, _ := utf8.encode_rune(c)
|
||||||
char := chars[0]
|
char := chars[0]
|
||||||
inject_at(&lines[cursor.line], cursor.char, char)
|
// TODO: selection
|
||||||
cursor.char += 1
|
if editor.selection.active {
|
||||||
|
delete_selection(&editor)
|
||||||
|
editor.selection.active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if repeatable_key_pressed(r.KeyboardKey.RIGHT) {
|
inject_at(&editor.lines[editor.cursor.line], editor.cursor.char, char)
|
||||||
preferred_position := cursor.char + 1
|
editor.cursor.char += 1
|
||||||
if cursor.char == len(current_line()) && cursor.line != len(lines) - 1 {
|
|
||||||
cursor.char = 0
|
|
||||||
cursor.line += 1
|
|
||||||
}
|
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_ALT) && cursor.char + 1 < len(current_line()) {
|
|
||||||
seen_space := false
|
|
||||||
for c, c_index in current_line()[cursor.char + 1:] {
|
|
||||||
if seen_space && !is_whitespace(c) {
|
|
||||||
preferred_position = cursor.char + c_index
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if is_whitespace(c) {
|
|
||||||
seen_space = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !seen_space {
|
|
||||||
preferred_position = len(current_line())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor.char = min(preferred_position, len(current_line()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if repeatable_key_pressed(r.KeyboardKey.LEFT) {
|
move_cursor(&editor)
|
||||||
preferred_position := cursor.char - 1
|
handle_backspace(&editor)
|
||||||
if cursor.char == 0 && cursor.line != 0 {
|
|
||||||
cursor.line -= 1
|
|
||||||
cursor.char = len(current_line())
|
|
||||||
}
|
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_ALT) && 0 < cursor.char {
|
|
||||||
found := false
|
|
||||||
preferred_position, found = find_previous_space(cursor.char, current_line()[:])
|
|
||||||
}
|
|
||||||
cursor.char = max(preferred_position, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if repeatable_key_pressed(r.KeyboardKey.UP) {
|
if repeatable_key_pressed(.ENTER) {
|
||||||
preferred_line := cursor.line - 1
|
// TODO: selection
|
||||||
|
if editor.selection.active {
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_ALT) && 0 < cursor.line {
|
delete_selection(&editor)
|
||||||
#reverse for l, l_index in lines[:cursor.line] {
|
editor.selection.active = false
|
||||||
if len(l) == 0 || all_whitespace(l[:]) {
|
|
||||||
preferred_line = l_index
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
new_line_cap := len(current_line(&editor)) - editor.cursor.char
|
||||||
}
|
bs := make([dynamic]u8, 0, len(current_line(&editor)) == 0 ? 1 : new_line_cap * 2)
|
||||||
|
if editor.cursor.char < len(current_line(&editor)) {
|
||||||
cursor.line = max(0, preferred_line)
|
for c, i in current_line(&editor)[editor.cursor.char:] {
|
||||||
if len(current_line()) < cursor.char {
|
|
||||||
cursor.char = len(current_line())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repeatable_key_pressed(r.KeyboardKey.DOWN) {
|
|
||||||
preferred_line := cursor.line + 1
|
|
||||||
|
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_ALT) && cursor.line + 1 < len(lines) {
|
|
||||||
for l, l_index in lines[cursor.line + 1:] {
|
|
||||||
if len(l) == 0 || all_whitespace(l[:]) {
|
|
||||||
preferred_line = cursor.line + l_index + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.line = min(preferred_line, len(lines) - 1)
|
|
||||||
if len(current_line()) < cursor.char {
|
|
||||||
cursor.char = len(current_line())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repeatable_key_pressed(r.KeyboardKey.ENTER) {
|
|
||||||
new_line_cap := len(current_line()) - cursor.char
|
|
||||||
bs := make([dynamic]u8, 0, len(current_line()) == 0 ? 1 : new_line_cap * 2)
|
|
||||||
if cursor.char < len(current_line()) {
|
|
||||||
for c, i in current_line()[cursor.char:] {
|
|
||||||
append(&bs, c)
|
append(&bs, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inject_at(&lines, cursor.line + 1, bs)
|
inject_at(&editor.lines, editor.cursor.line + 1, bs)
|
||||||
resize(current_line(), cursor.char)
|
resize(current_line(&editor), editor.cursor.char)
|
||||||
cursor.line += 1
|
editor.cursor.line += 1
|
||||||
cursor.char = 0
|
editor.cursor.char = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_SUPER) && r.IsKeyPressed(r.KeyboardKey.S) {
|
if r.IsKeyDown(.LEFT_SUPER) && r.IsKeyPressed(.S) {
|
||||||
builder := strings.builder_make()
|
builder := strings.builder_make()
|
||||||
defer strings.builder_destroy(&builder)
|
defer strings.builder_destroy(&builder)
|
||||||
for line in lines {
|
for line in editor.lines {
|
||||||
strings.write_bytes(&builder, line[:])
|
strings.write_bytes(&builder, line[:])
|
||||||
strings.write_byte(&builder, '\n')
|
strings.write_byte(&builder, '\n')
|
||||||
}
|
}
|
||||||
@@ -174,54 +145,40 @@ main :: proc() {
|
|||||||
fmt.println("Wrote file!")
|
fmt.println("Wrote file!")
|
||||||
}
|
}
|
||||||
|
|
||||||
skip_backspace: if repeatable_key_pressed(r.KeyboardKey.BACKSPACE) {
|
if r.IsKeyDown(.LEFT_SUPER) && r.IsKeyDown(.LEFT_SHIFT) && r.IsKeyPressed(.D) {
|
||||||
if cursor.line == 0 && cursor.char == 0 do break skip_backspace
|
if editor.selection.active {
|
||||||
if cursor.char == 0 {
|
earliest := selection_earliest(&editor)
|
||||||
// join lines
|
latest := selection_latest(&editor)
|
||||||
old_len := len(lines[cursor.line - 1])
|
#reverse for line, index in editor.lines[earliest.line:latest.line + 1] {
|
||||||
append(&lines[cursor.line - 1], string(current_line()[:]))
|
new_line, _ := slice.clone_to_dynamic(line[:])
|
||||||
line_to_remove := current_line()^
|
inject_at(&editor.lines, latest.line + 1, new_line)
|
||||||
defer delete(line_to_remove)
|
|
||||||
ordered_remove(&lines, cursor.line)
|
|
||||||
cursor.line -= 1
|
|
||||||
cursor.char = old_len
|
|
||||||
} else {
|
|
||||||
if r.IsKeyDown(r.KeyboardKey.LEFT_ALT) {
|
|
||||||
// delete by word
|
|
||||||
delete_to, found := find_previous_space(cursor.char, current_line()[:])
|
|
||||||
if found {
|
|
||||||
remove_range(current_line(), delete_to, cursor.char)
|
|
||||||
cursor.char = delete_to
|
|
||||||
} else {
|
|
||||||
remove_range(current_line(), 0, cursor.char)
|
|
||||||
cursor.char = 0
|
|
||||||
}
|
}
|
||||||
|
// NOTE: should we move the selection as well?
|
||||||
} else {
|
} else {
|
||||||
// delete single char
|
new_line, _ := slice.clone_to_dynamic(current_line(&editor)[:])
|
||||||
ordered_remove(&lines[cursor.line], cursor.char - 1)
|
inject_at(&editor.lines, editor.cursor.line + 1, new_line)
|
||||||
cursor.char -= 1
|
editor.cursor.line += 1
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor_padding := 3
|
cursor_padding := 3
|
||||||
// move viewport up
|
// move viewport up
|
||||||
if cursor.line < viewport.start + cursor_padding {
|
if editor.cursor.line < editor.viewport.start + cursor_padding {
|
||||||
diff := (cursor.line - viewport.start) - cursor_padding
|
diff := (editor.cursor.line - editor.viewport.start) - cursor_padding
|
||||||
viewport.start = min(max(0, viewport.start + diff), len(lines) - viewport_size)
|
editor.viewport.start = min(max(0, editor.viewport.start + diff), len(editor.lines) - editor.viewport_size)
|
||||||
viewport.end = min(max(viewport_size, viewport.end + diff), len(lines))
|
editor.viewport.end = min(max(editor.viewport_size, editor.viewport.end + diff), len(editor.lines))
|
||||||
}
|
}
|
||||||
// move viewport down
|
// move viewport down
|
||||||
if viewport.end - cursor_padding < cursor.line {
|
if editor.viewport.end - cursor_padding < editor.cursor.line {
|
||||||
diff := cursor.line - (viewport.end - cursor_padding)
|
diff := editor.cursor.line - (editor.viewport.end - cursor_padding)
|
||||||
viewport.start = min(max(0, viewport.start + diff), len(lines) - viewport_size)
|
editor.viewport.start = min(max(0, editor.viewport.start + diff), len(editor.lines) - editor.viewport_size)
|
||||||
viewport.end = min(max(viewport_size, viewport.end + diff), len(lines))
|
editor.viewport.end = min(max(editor.viewport_size, editor.viewport.end + diff), len(editor.lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
r.BeginDrawing()
|
r.BeginDrawing()
|
||||||
r.ClearBackground(r.BLACK)
|
r.ClearBackground(r.BLACK)
|
||||||
|
|
||||||
for &line, line_index in lines[viewport.start:viewport.end] {
|
for &line, line_index in editor.lines[editor.viewport.start:editor.viewport.end] {
|
||||||
if cap(line) == 0 {
|
if cap(line) == 0 {
|
||||||
reserve(&line, 1)
|
reserve(&line, 1)
|
||||||
}
|
}
|
||||||
@@ -230,16 +187,23 @@ main :: proc() {
|
|||||||
}
|
}
|
||||||
raw_data(line)[len(line)] = 0
|
raw_data(line)[len(line)] = 0
|
||||||
|
|
||||||
|
if editor.selection.active &&
|
||||||
|
selection_earliest(&editor).line <= line_index &&
|
||||||
|
line_index <= selection_latest(&editor).line {
|
||||||
|
render_line_with_selection(&editor, line[:], line_index, font)
|
||||||
|
} else {
|
||||||
|
// No selection active
|
||||||
cstr := strings.unsafe_string_to_cstring(string(line[:]))
|
cstr := strings.unsafe_string_to_cstring(string(line[:]))
|
||||||
pos := r.Vector2{0, font_size * f32(line_index)}
|
pos := r.Vector2{0, editor.font_size * f32(line_index)}
|
||||||
r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE)
|
r.DrawTextEx(font, cstr, pos, editor.font_size, 0, r.WHITE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_cursor(&cursor)
|
render_cursor(&editor)
|
||||||
|
|
||||||
fps_width := r.MeasureText("60", default_font.baseSize)
|
fps_width := r.MeasureText("60", default_font.baseSize)
|
||||||
|
|
||||||
r.DrawFPS(i32(window_width) - (fps_width + 20), 10)
|
r.DrawFPS(editor.window_width - (fps_width + 20), 10)
|
||||||
|
|
||||||
r.EndDrawing()
|
r.EndDrawing()
|
||||||
|
|
||||||
@@ -248,15 +212,16 @@ main :: proc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_cursor :: proc(cursor: ^Cursor) {
|
render_cursor :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
x := cursor.char * int(font_size / 2)
|
x := cursor.char * int(font_size / 2)
|
||||||
y := i32(font_size) * i32(cursor.line - viewport.start)
|
y := i32(font_size) * i32(cursor.line - viewport.start)
|
||||||
r.DrawLine(i32(x), y, i32(x), y + i32(font_size), r.WHITE)
|
r.DrawLine(i32(x), y, i32(x), y + i32(font_size), r.WHITE)
|
||||||
r.DrawLine(i32(x) + 1, y, i32(x) + 1, y + i32(font_size), r.WHITE)
|
r.DrawLine(i32(x) + 1, y, i32(x) + 1, y + i32(font_size), r.WHITE)
|
||||||
}
|
}
|
||||||
|
|
||||||
current_line :: proc() -> ^[dynamic]u8 {
|
current_line :: proc(editor: ^Editor) -> ^[dynamic]u8 {
|
||||||
return &lines[cursor.line]
|
return &editor.lines[editor.cursor.line]
|
||||||
}
|
}
|
||||||
|
|
||||||
is_whitespace :: proc(c: u8) -> bool {
|
is_whitespace :: proc(c: u8) -> bool {
|
||||||
@@ -288,3 +253,288 @@ find_previous_space :: proc(current_position: int, line: []u8) -> (result: int,
|
|||||||
|
|
||||||
return result, found
|
return result, found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle_selection_start :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
|
was_active := selection.active
|
||||||
|
if r.IsKeyDown(.LEFT_SHIFT) {
|
||||||
|
selection.active = true
|
||||||
|
} else {
|
||||||
|
selection = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if selection.active && was_active do return
|
||||||
|
selection.start.line = cursor.line
|
||||||
|
selection.start.char = cursor.char
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_selection_end :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
|
if !selection.active do return
|
||||||
|
selection.end.char = cursor.char
|
||||||
|
selection.end.line = cursor.line
|
||||||
|
}
|
||||||
|
|
||||||
|
render_line_with_selection :: proc(editor: ^Editor, line: []u8, line_index: int, font: r.Font) {
|
||||||
|
using editor
|
||||||
|
earliest := selection_earliest(editor)
|
||||||
|
latest := selection_latest(editor)
|
||||||
|
|
||||||
|
selection_range := get_selection_for_line(editor, line, line_index)
|
||||||
|
|
||||||
|
prefix := line[:selection_range.start]
|
||||||
|
suffix := line[selection_range.end:] if selection_range.end < len(line) else []u8{}
|
||||||
|
// need to copy these into separate buffers for zero value
|
||||||
|
x: f32 = 0.0
|
||||||
|
if len(prefix) != 0 {
|
||||||
|
cstr := strings.clone_to_cstring(string(prefix), context.temp_allocator)
|
||||||
|
measurements := r.MeasureTextEx(font, cstr, font_size, 0)
|
||||||
|
pos := r.Vector2{x, font_size * f32(line_index)}
|
||||||
|
r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE)
|
||||||
|
x += measurements.x
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_str: string
|
||||||
|
if selection_range.end < len(line) {
|
||||||
|
selected_str = string(line[selection_range.start:selection_range.end])
|
||||||
|
} else {
|
||||||
|
selected_str = string(line[selection_range.start:])
|
||||||
|
}
|
||||||
|
cstr :=
|
||||||
|
strings.clone_to_cstring(selected_str, context.temp_allocator) if len(suffix) != 0 else strings.unsafe_string_to_cstring(selected_str)
|
||||||
|
measurements := r.MeasureTextEx(font, cstr, font_size, 0)
|
||||||
|
pos := r.Vector2{x, font_size * f32(line_index)}
|
||||||
|
r.DrawRectangle(i32(pos.x), i32(pos.y), i32(measurements.x), i32(measurements.y), r.WHITE)
|
||||||
|
r.DrawTextEx(font, cstr, pos, font_size, 0, r.BLACK)
|
||||||
|
x += measurements.x
|
||||||
|
|
||||||
|
if len(suffix) != 0 {
|
||||||
|
cstr := strings.unsafe_string_to_cstring(string(suffix))
|
||||||
|
pos := r.Vector2{x, font_size * f32(line_index)}
|
||||||
|
r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move_cursor :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
|
if repeatable_key_pressed(.RIGHT) {
|
||||||
|
handle_selection_start(editor)
|
||||||
|
defer handle_selection_end(editor)
|
||||||
|
preferred_position := cursor.char + 1
|
||||||
|
if cursor.char == len(current_line(editor)) && cursor.line != len(lines) - 1 {
|
||||||
|
preferred_position = 0
|
||||||
|
cursor.line += 1
|
||||||
|
}
|
||||||
|
if r.IsKeyDown(.LEFT_ALT) && cursor.char + 1 < len(current_line(editor)) {
|
||||||
|
seen_space := false
|
||||||
|
for c, c_index in current_line(editor)[cursor.char + 1:] {
|
||||||
|
if seen_space && !is_whitespace(c) {
|
||||||
|
preferred_position = cursor.char + c_index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if is_whitespace(c) {
|
||||||
|
seen_space = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !seen_space {
|
||||||
|
preferred_position = len(current_line(editor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.char = min(preferred_position, len(current_line(editor)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if repeatable_key_pressed(.LEFT) {
|
||||||
|
handle_selection_start(editor)
|
||||||
|
defer handle_selection_end(editor)
|
||||||
|
preferred_position := cursor.char - 1
|
||||||
|
if cursor.char == 0 && cursor.line != 0 {
|
||||||
|
cursor.line -= 1
|
||||||
|
preferred_position = len(current_line(editor))
|
||||||
|
}
|
||||||
|
if r.IsKeyDown(.LEFT_ALT) && 0 < cursor.char {
|
||||||
|
found := false
|
||||||
|
preferred_position, found = find_previous_space(cursor.char, current_line(editor)[:])
|
||||||
|
}
|
||||||
|
cursor.char = max(preferred_position, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if repeatable_key_pressed(.UP) {
|
||||||
|
handle_selection_start(editor)
|
||||||
|
defer handle_selection_end(editor)
|
||||||
|
preferred_line := cursor.line - 1
|
||||||
|
|
||||||
|
if r.IsKeyDown(.LEFT_ALT) && 0 < cursor.line {
|
||||||
|
#reverse for l, l_index in lines[:cursor.line] {
|
||||||
|
if len(l) == 0 || all_whitespace(l[:]) {
|
||||||
|
preferred_line = l_index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.line = max(0, preferred_line)
|
||||||
|
if len(current_line(editor)) < cursor.char {
|
||||||
|
cursor.char = len(current_line(editor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if repeatable_key_pressed(.DOWN) {
|
||||||
|
handle_selection_start(editor)
|
||||||
|
defer handle_selection_end(editor)
|
||||||
|
preferred_line := cursor.line + 1
|
||||||
|
|
||||||
|
if r.IsKeyDown(.LEFT_ALT) && cursor.line + 1 < len(lines) {
|
||||||
|
for l, l_index in lines[cursor.line + 1:] {
|
||||||
|
if len(l) == 0 || all_whitespace(l[:]) {
|
||||||
|
preferred_line = cursor.line + l_index + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.line = min(preferred_line, len(lines) - 1)
|
||||||
|
if len(current_line(editor)) < cursor.char {
|
||||||
|
cursor.char = len(current_line(editor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection_earliest :: proc(editor: ^Editor) -> Position {
|
||||||
|
using editor
|
||||||
|
earliest: Position
|
||||||
|
latest: Position
|
||||||
|
if selection.start.line < selection.end.line {
|
||||||
|
// Started highlighting on a previous line, selecting downwards
|
||||||
|
earliest = selection.start
|
||||||
|
latest = selection.end
|
||||||
|
} else if selection.start.line > selection.end.line {
|
||||||
|
// Started highlighting on a later line, selecting upwards
|
||||||
|
earliest = selection.end
|
||||||
|
latest = selection.start
|
||||||
|
} else if selection.start.char < selection.end.char {
|
||||||
|
// Selection on one line, selecting rightwards
|
||||||
|
earliest = selection.start
|
||||||
|
latest = selection.end
|
||||||
|
} else {
|
||||||
|
// Selection on one line, selecting leftwards
|
||||||
|
earliest = selection.end
|
||||||
|
latest = selection.start
|
||||||
|
}
|
||||||
|
|
||||||
|
return earliest
|
||||||
|
}
|
||||||
|
|
||||||
|
selection_latest :: proc(editor: ^Editor) -> Position {
|
||||||
|
using editor
|
||||||
|
earliest: Position
|
||||||
|
latest: Position
|
||||||
|
if selection.start.line < selection.end.line {
|
||||||
|
// Started highlighting on a previous line, selecting downwards
|
||||||
|
earliest = selection.start
|
||||||
|
latest = selection.end
|
||||||
|
} else if selection.start.line > selection.end.line {
|
||||||
|
// Started highlighting on a later line, selecting upwards
|
||||||
|
earliest = selection.end
|
||||||
|
latest = selection.start
|
||||||
|
} else if selection.start.char < selection.end.char {
|
||||||
|
// Selection on one line, selecting rightwards
|
||||||
|
earliest = selection.start
|
||||||
|
latest = selection.end
|
||||||
|
} else {
|
||||||
|
// Selection on one line, selecting leftwards
|
||||||
|
earliest = selection.end
|
||||||
|
latest = selection.start
|
||||||
|
}
|
||||||
|
|
||||||
|
return latest
|
||||||
|
}
|
||||||
|
|
||||||
|
get_selection_for_line :: proc(editor: ^Editor, line: []u8, line_index: int) -> Range {
|
||||||
|
using editor
|
||||||
|
earliest := selection_earliest(editor)
|
||||||
|
latest := selection_latest(editor)
|
||||||
|
selection_begin: int
|
||||||
|
selection_end: int
|
||||||
|
if line_index == earliest.line && line_index == latest.line {
|
||||||
|
selection_begin = min(selection.start.char, selection.end.char)
|
||||||
|
selection_end = max(selection.start.char, selection.end.char)
|
||||||
|
} else if line_index == earliest.line {
|
||||||
|
selection_begin = earliest.char
|
||||||
|
selection_end = len(line)
|
||||||
|
} else if line_index == latest.line {
|
||||||
|
selection_begin = 0
|
||||||
|
selection_end = latest.char
|
||||||
|
} else if earliest.line < line_index && line_index < latest.line {
|
||||||
|
selection_begin = 0
|
||||||
|
selection_end = len(line)
|
||||||
|
}
|
||||||
|
return Range{start = selection_begin, end = selection_end}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_backspace :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
|
skip_backspace: if repeatable_key_pressed(.BACKSPACE) {
|
||||||
|
if cursor.line == 0 && cursor.char == 0 do break skip_backspace
|
||||||
|
// TODO: selection
|
||||||
|
if selection.active {
|
||||||
|
delete_selection(editor)
|
||||||
|
selection.active = false
|
||||||
|
} else if cursor.char == 0 {
|
||||||
|
// join lines
|
||||||
|
old_len := len(lines[cursor.line - 1])
|
||||||
|
append(&lines[cursor.line - 1], string(current_line(editor)[:]))
|
||||||
|
line_to_remove := current_line(editor)^
|
||||||
|
defer delete(line_to_remove)
|
||||||
|
ordered_remove(&lines, cursor.line)
|
||||||
|
cursor.line -= 1
|
||||||
|
cursor.char = old_len
|
||||||
|
} else if r.IsKeyDown(.LEFT_ALT) {
|
||||||
|
// delete by word
|
||||||
|
delete_to, found := find_previous_space(cursor.char, current_line(editor)[:])
|
||||||
|
if found {
|
||||||
|
remove_range(current_line(editor), delete_to, cursor.char)
|
||||||
|
cursor.char = delete_to
|
||||||
|
} else {
|
||||||
|
remove_range(current_line(editor), 0, cursor.char)
|
||||||
|
cursor.char = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// delete single char
|
||||||
|
ordered_remove(&lines[cursor.line], cursor.char - 1)
|
||||||
|
cursor.char -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_selection :: proc(editor: ^Editor) {
|
||||||
|
using editor
|
||||||
|
earliest := selection_earliest(editor)
|
||||||
|
latest := selection_latest(editor)
|
||||||
|
earliest_line := &lines[earliest.line]
|
||||||
|
earliest_selection := get_selection_for_line(editor, earliest_line[:], earliest.line)
|
||||||
|
remove_range(earliest_line, earliest_selection.start, earliest_selection.end)
|
||||||
|
if latest.line != earliest.line {
|
||||||
|
latest_line := lines[latest.line]
|
||||||
|
latest_selection := get_selection_for_line(editor, latest_line[:], latest.line)
|
||||||
|
append(earliest_line, string(latest_line[latest_selection.end:len(latest_line)]))
|
||||||
|
ordered_remove(&lines, latest.line)
|
||||||
|
delete(latest_line)
|
||||||
|
if selection.start.line < selection.end.line {
|
||||||
|
cursor.line -= 1
|
||||||
|
if selection.start.char < selection.end.char {
|
||||||
|
cursor.char -= selection.end.char - selection.start.char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := earliest.line + 1; i < latest.line; i += 1 {
|
||||||
|
line_to_remove := lines[i]
|
||||||
|
defer delete(line_to_remove)
|
||||||
|
ordered_remove(&lines, i)
|
||||||
|
// NOTE(grant): Should we do this here?
|
||||||
|
if selection.start.line < selection.end.line {
|
||||||
|
cursor.line -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cursor.char -= earliest_selection.end - earliest_selection.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user