Compare commits
5 Commits
04311187af
...
5275161048
| Author | SHA1 | Date | |
|---|---|---|---|
| 5275161048 | |||
| c4a1c89143 | |||
| 921928e96f | |||
| 33eb774c97 | |||
| 45e2cd1f8f |
@@ -20,7 +20,9 @@
|
||||
],
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
"path": ".",
|
||||
"folder_exclude_patterns": ["*bin*"],
|
||||
"binary_file_patterns": ["*.bin"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
427
src/main.odin
427
src/main.odin
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
- DONE implement movement up and down by paragraph
|
||||
- DONE implement scrolling/viewport
|
||||
- TODO implement deletion by word
|
||||
- DONE implement deletion by word
|
||||
- TODO implement selection
|
||||
- TODO implement duplicate selection/line
|
||||
- TODO implement move selection/line up/down
|
||||
@@ -32,11 +32,24 @@ Viewport :: struct {
|
||||
end: int,
|
||||
}
|
||||
|
||||
Cursor :: struct {
|
||||
Position :: struct {
|
||||
line: int,
|
||||
char: int,
|
||||
}
|
||||
|
||||
Cursor :: distinct Position
|
||||
|
||||
Selection :: struct {
|
||||
active: bool,
|
||||
start: Position,
|
||||
end: Position,
|
||||
}
|
||||
|
||||
Range :: struct {
|
||||
start: int,
|
||||
end: int,
|
||||
}
|
||||
|
||||
window_width := 800
|
||||
window_height := 600
|
||||
font_size: f32 = 20.0
|
||||
@@ -48,6 +61,7 @@ viewport := Viewport {
|
||||
start = 0,
|
||||
end = viewport_size,
|
||||
}
|
||||
selection := Selection{}
|
||||
|
||||
main :: proc() {
|
||||
r.InitWindow(i32(window_width), i32(window_height), "odit")
|
||||
@@ -72,98 +86,37 @@ main :: proc() {
|
||||
for c := r.GetCharPressed(); c != rune(0); c = r.GetCharPressed() {
|
||||
chars, _ := utf8.encode_rune(c)
|
||||
char := chars[0]
|
||||
inject_at(&lines[cursor.line], cursor.char, char)
|
||||
cursor.char += 1
|
||||
// TODO: selection
|
||||
if selection.active {
|
||||
|
||||
} else {
|
||||
inject_at(&lines[cursor.line], cursor.char, char)
|
||||
cursor.char += 1
|
||||
}
|
||||
}
|
||||
|
||||
if repeatable_key_pressed(r.KeyboardKey.RIGHT) {
|
||||
preferred_position := cursor.char + 1
|
||||
if cursor.char == len(current_line()) && cursor.line != len(lines) - 1 {
|
||||
cursor.char = 0
|
||||
move_cursor()
|
||||
|
||||
if repeatable_key_pressed(.ENTER) {
|
||||
// TODO: selection
|
||||
if selection.active {
|
||||
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
inject_at(&lines, cursor.line + 1, bs)
|
||||
resize(current_line(), cursor.char)
|
||||
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) {
|
||||
preferred_position := cursor.char - 1
|
||||
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) {
|
||||
preferred_line := cursor.line - 1
|
||||
|
||||
if r.IsKeyDown(r.KeyboardKey.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()) < cursor.char {
|
||||
cursor.char = len(current_line())
|
||||
cursor.char = 0
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
inject_at(&lines, cursor.line + 1, bs)
|
||||
resize(current_line(), cursor.char)
|
||||
cursor.line += 1
|
||||
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()
|
||||
defer strings.builder_destroy(&builder)
|
||||
for line in lines {
|
||||
@@ -174,32 +127,61 @@ main :: proc() {
|
||||
fmt.println("Wrote file!")
|
||||
}
|
||||
|
||||
skip_backspace: if repeatable_key_pressed(r.KeyboardKey.BACKSPACE) {
|
||||
skip_backspace: if repeatable_key_pressed(.BACKSPACE) {
|
||||
if cursor.line == 0 && cursor.char == 0 do break skip_backspace
|
||||
if cursor.char == 0 {
|
||||
// join lines
|
||||
old_len := len(lines[cursor.line - 1])
|
||||
append(&lines[cursor.line - 1], string(current_line()[:]))
|
||||
line_to_remove := current_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
|
||||
// TODO: selection
|
||||
if selection.active {
|
||||
earliest := selection_earliest()
|
||||
latest := selection_latest()
|
||||
earliest_line := &lines[earliest.line]
|
||||
earliest_selection := get_selection_for_line(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(latest_line[:], latest.line)
|
||||
append(earliest_line, string(latest_line[latest_selection.end:len(latest_line)]))
|
||||
defer delete(latest_line^)
|
||||
ordered_remove(&lines, latest.line)
|
||||
cursor.line -= 1
|
||||
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 i < cursor.line {
|
||||
cursor.line -= 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// delete single char
|
||||
ordered_remove(&lines[cursor.line], cursor.char - 1)
|
||||
cursor.char -= 1
|
||||
cursor.char -= earliest_selection.end - earliest_selection.start
|
||||
}
|
||||
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()[:]))
|
||||
line_to_remove := current_line()^
|
||||
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()[:])
|
||||
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
|
||||
}
|
||||
} else {
|
||||
// delete single char
|
||||
ordered_remove(&lines[cursor.line], cursor.char - 1)
|
||||
cursor.char -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,9 +212,16 @@ main :: proc() {
|
||||
}
|
||||
raw_data(line)[len(line)] = 0
|
||||
|
||||
cstr := strings.unsafe_string_to_cstring(string(line[:]))
|
||||
pos := r.Vector2{0, font_size * f32(line_index)}
|
||||
r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE)
|
||||
if selection.active &&
|
||||
selection_earliest().line <= line_index &&
|
||||
line_index <= selection_latest().line {
|
||||
render_line_with_selection(line[:], line_index, font)
|
||||
} else {
|
||||
// No selection active
|
||||
cstr := strings.unsafe_string_to_cstring(string(line[:]))
|
||||
pos := r.Vector2{0, font_size * f32(line_index)}
|
||||
r.DrawTextEx(font, cstr, pos, font_size, 0, r.WHITE)
|
||||
}
|
||||
}
|
||||
|
||||
render_cursor(&cursor)
|
||||
@@ -288,3 +277,213 @@ find_previous_space :: proc(current_position: int, line: []u8) -> (result: int,
|
||||
|
||||
return result, found
|
||||
}
|
||||
|
||||
handle_selection_start :: proc() {
|
||||
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() {
|
||||
if !selection.active do return
|
||||
selection.end.char = cursor.char
|
||||
selection.end.line = cursor.line
|
||||
}
|
||||
|
||||
render_line_with_selection :: proc(line: []u8, line_index: int, font: r.Font) {
|
||||
earliest := selection_earliest()
|
||||
latest := selection_latest()
|
||||
|
||||
selection_range := get_selection_for_line(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() {
|
||||
if repeatable_key_pressed(.RIGHT) {
|
||||
handle_selection_start()
|
||||
defer handle_selection_end()
|
||||
preferred_position := cursor.char + 1
|
||||
if cursor.char == len(current_line()) && cursor.line != len(lines) - 1 {
|
||||
preferred_position = 0
|
||||
cursor.line += 1
|
||||
}
|
||||
if r.IsKeyDown(.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(.LEFT) {
|
||||
handle_selection_start()
|
||||
defer handle_selection_end()
|
||||
preferred_position := cursor.char - 1
|
||||
if cursor.char == 0 && cursor.line != 0 {
|
||||
cursor.line -= 1
|
||||
preferred_position = len(current_line())
|
||||
}
|
||||
if r.IsKeyDown(.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(.UP) {
|
||||
handle_selection_start()
|
||||
defer handle_selection_end()
|
||||
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()) < cursor.char {
|
||||
cursor.char = len(current_line())
|
||||
}
|
||||
}
|
||||
|
||||
if repeatable_key_pressed(.DOWN) {
|
||||
handle_selection_start()
|
||||
defer handle_selection_end()
|
||||
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()) < cursor.char {
|
||||
cursor.char = len(current_line())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selection_earliest :: proc() -> Position {
|
||||
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() -> Position {
|
||||
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(line: []u8, line_index: int) -> Range {
|
||||
earliest := selection_earliest()
|
||||
latest := selection_latest()
|
||||
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}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user