bl
This commit is contained in:
parent
41adeeb47f
commit
5b65c4c0e4
11 changed files with 4237 additions and 297 deletions
267
client/odin/diff_view.odin
Normal file
267
client/odin/diff_view.odin
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
package main
|
||||
|
||||
import "core:strings"
|
||||
|
||||
// Side-by-side diff viewer. A unified diff is parsed into aligned rows:
|
||||
// context lines appear on both sides, removed/added runs are paired up
|
||||
// line-by-line (with blank filler cells for the unmatched remainder), and
|
||||
// hunk headers become full-width separator rows. A buffer whose `diff`
|
||||
// field is set renders this view instead of its text.
|
||||
|
||||
Diff_Row :: struct {
|
||||
header: bool,
|
||||
header_text: string,
|
||||
left_number: int, // 0 = no line on this side
|
||||
right_number: int,
|
||||
left_text: string,
|
||||
right_text: string,
|
||||
left_kind: u8, // '-' removed, ' ' context, 0 filler
|
||||
right_kind: u8, // '+' added, ' ' context, 0 filler
|
||||
}
|
||||
|
||||
Diff_Document :: struct {
|
||||
rows: [dynamic]Diff_Row,
|
||||
scroll: int,
|
||||
}
|
||||
|
||||
diff_document_destroy :: proc(doc: ^Diff_Document) {
|
||||
for row in doc.rows {
|
||||
delete(row.header_text)
|
||||
delete(row.left_text)
|
||||
delete(row.right_text)
|
||||
}
|
||||
delete(doc.rows)
|
||||
free(doc)
|
||||
}
|
||||
|
||||
diff_parse_leading_int :: proc(s: string) -> int {
|
||||
value := 0
|
||||
for b in transmute([]u8)s {
|
||||
if b < '0' || b > '9' do break
|
||||
value = value * 10 + int(b - '0')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// "@@ -12,5 +14,6 @@ ..." -> (12, 14)
|
||||
diff_parse_hunk_header :: proc(line: string) -> (int, int) {
|
||||
left, right := 1, 1
|
||||
if minus := strings.index_byte(line, '-'); minus >= 0 {
|
||||
left = max_int(diff_parse_leading_int(line[minus + 1:]), 1)
|
||||
}
|
||||
if plus := strings.index_byte(line, '+'); plus >= 0 {
|
||||
right = max_int(diff_parse_leading_int(line[plus + 1:]), 1)
|
||||
}
|
||||
return left, right
|
||||
}
|
||||
|
||||
// Pairs the collected removed/added runs into aligned rows.
|
||||
diff_flush_changes :: proc(doc: ^Diff_Document, removed, added: ^[dynamic]string, left_num, right_num: ^int) {
|
||||
count := max_int(len(removed), len(added))
|
||||
for i in 0 ..< count {
|
||||
row := Diff_Row{}
|
||||
if i < len(removed) {
|
||||
row.left_text = strings.clone(removed[i])
|
||||
row.left_kind = '-'
|
||||
row.left_number = left_num^
|
||||
left_num^ += 1
|
||||
}
|
||||
if i < len(added) {
|
||||
row.right_text = strings.clone(added[i])
|
||||
row.right_kind = '+'
|
||||
row.right_number = right_num^
|
||||
right_num^ += 1
|
||||
}
|
||||
append(&doc.rows, row)
|
||||
}
|
||||
clear(removed)
|
||||
clear(added)
|
||||
}
|
||||
|
||||
diff_document_make :: proc(text: string) -> ^Diff_Document {
|
||||
doc := new(Diff_Document)
|
||||
removed := make([dynamic]string, context.temp_allocator)
|
||||
added := make([dynamic]string, context.temp_allocator)
|
||||
left_num, right_num := 1, 1
|
||||
in_hunk := false
|
||||
|
||||
lines, _ := strings.split_lines(text, context.temp_allocator)
|
||||
for raw_line in lines {
|
||||
line := strings.trim_right(raw_line, "\r")
|
||||
|
||||
if strings.has_prefix(line, "@@") {
|
||||
diff_flush_changes(doc, &removed, &added, &left_num, &right_num)
|
||||
left_num, right_num = diff_parse_hunk_header(line)
|
||||
in_hunk = true
|
||||
append(&doc.rows, Diff_Row{header = true, header_text = strings.clone(line)})
|
||||
continue
|
||||
}
|
||||
if strings.has_prefix(line, "diff --git") {
|
||||
diff_flush_changes(doc, &removed, &added, &left_num, &right_num)
|
||||
in_hunk = false
|
||||
continue
|
||||
}
|
||||
if !in_hunk {
|
||||
// File headers (index, ---, +++, mode changes, binary notes).
|
||||
if len(strings.trim_space(line)) > 0 && len(doc.rows) == 0 && !strings.has_prefix(line, "index ") &&
|
||||
!strings.has_prefix(line, "---") && !strings.has_prefix(line, "+++") &&
|
||||
!strings.has_prefix(line, "old mode") && !strings.has_prefix(line, "new mode") &&
|
||||
!strings.has_prefix(line, "new file") && !strings.has_prefix(line, "deleted file") &&
|
||||
!strings.has_prefix(line, "similarity") && !strings.has_prefix(line, "rename") &&
|
||||
!strings.has_prefix(line, "copy") {
|
||||
// Notes like "Binary files differ" get a visible header row.
|
||||
append(&doc.rows, Diff_Row{header = true, header_text = strings.clone(line)})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(line) == 0 do continue
|
||||
|
||||
switch line[0] {
|
||||
case ' ':
|
||||
diff_flush_changes(doc, &removed, &added, &left_num, &right_num)
|
||||
content := line[1:]
|
||||
append(&doc.rows, Diff_Row{
|
||||
left_number = left_num,
|
||||
right_number = right_num,
|
||||
left_text = strings.clone(content),
|
||||
right_text = strings.clone(content),
|
||||
left_kind = ' ',
|
||||
right_kind = ' ',
|
||||
})
|
||||
left_num += 1
|
||||
right_num += 1
|
||||
case '-':
|
||||
append(&removed, line[1:])
|
||||
case '+':
|
||||
append(&added, line[1:])
|
||||
case '\\':
|
||||
// "\ No newline at end of file"
|
||||
}
|
||||
}
|
||||
diff_flush_changes(doc, &removed, &added, &left_num, &right_num)
|
||||
return doc
|
||||
}
|
||||
|
||||
// Opens (or refreshes) a diff tab for the given unified diff text.
|
||||
editor_open_diff :: proc(editor: ^Editor, name, diff_text: string) {
|
||||
doc := diff_document_make(diff_text)
|
||||
|
||||
for &buffer, index in editor.buffers {
|
||||
if buffer.scratch && buffer.path == name {
|
||||
editor_buffer_destroy(&buffer)
|
||||
buffer = diff_editor_buffer(name, doc)
|
||||
editor.active = index
|
||||
return
|
||||
}
|
||||
}
|
||||
append(&editor.buffers, diff_editor_buffer(name, doc))
|
||||
editor.active = len(editor.buffers) - 1
|
||||
}
|
||||
|
||||
diff_editor_buffer :: proc(name: string, doc: ^Diff_Document) -> Editor_Buffer {
|
||||
return Editor_Buffer{
|
||||
path = strings.clone(name),
|
||||
daemon_path = strings.clone(""),
|
||||
buffer = buffer_make(""),
|
||||
scratch = true,
|
||||
diff = doc,
|
||||
}
|
||||
}
|
||||
|
||||
diff_view_scroll :: proc(active: ^Editor_Buffer, view: ^SDL_View, delta: int) {
|
||||
doc := active.diff
|
||||
visible := visible_editor_lines(view)
|
||||
doc.scroll = clamp_int(doc.scroll + delta, 0, max_int(len(doc.rows) - visible, 0))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rendering
|
||||
|
||||
DIFF_REMOVED_BG :: [3]u8{58, 33, 36}
|
||||
DIFF_ADDED_BG :: [3]u8{34, 51, 38}
|
||||
DIFF_FILLER_BG :: [3]u8{33, 34, 39}
|
||||
DIFF_HEADER_BG :: [3]u8{39, 41, 49}
|
||||
|
||||
render_diff_view_gpu :: proc(gpu: ^GPU_Renderer, view: ^SDL_View, active: ^Editor_Buffer, tasks_panel: ^Gradle_Tasks_Panel) {
|
||||
doc := active.diff
|
||||
sidebar_width := left_sidebar_width(view)
|
||||
right_limit := content_right_edge(gpu.width) - 8
|
||||
if tasks_panel != nil && tasks_panel.open {
|
||||
right_limit = content_right_edge(gpu.width) - right_sidebar_width_view(view, gpu.width) - 8
|
||||
}
|
||||
|
||||
area_x := f32(sidebar_width)
|
||||
area_width := f32(right_limit) - area_x
|
||||
area_bottom := f32(editor_content_bottom(view))
|
||||
half := area_width * 0.5
|
||||
advance := max_f32(gpu.font_advance, 1)
|
||||
gutter_width := 5 * advance + 10
|
||||
columns := max_int(int((half - gutter_width - 16) / advance), 4)
|
||||
|
||||
visible := visible_editor_lines(view)
|
||||
doc.scroll = clamp_int(doc.scroll, 0, max_int(len(doc.rows) - visible, 0))
|
||||
|
||||
y := f32(SDL_EDITOR_TEXT_Y)
|
||||
for offset in 0 ..< visible {
|
||||
index := doc.scroll + offset
|
||||
if index >= len(doc.rows) do break
|
||||
row := doc.rows[index]
|
||||
|
||||
if row.header {
|
||||
gpu_rect(gpu, area_x, y - 3, area_width, SDL_LINE_HEIGHT + 1, DIFF_HEADER_BG[0], DIFF_HEADER_BG[1], DIFF_HEADER_BG[2], 255)
|
||||
gpu_text_limited(gpu, area_x + 12, y, row.header_text, max_int(int((area_width - 24) / advance), 4), 150, 160, 180, 255)
|
||||
} else {
|
||||
render_diff_side_gpu(gpu, area_x, y, half, gutter_width, columns, row.left_number, row.left_text, row.left_kind)
|
||||
render_diff_side_gpu(gpu, area_x + half + 1, y, half - 1, gutter_width, columns, row.right_number, row.right_text, row.right_kind)
|
||||
}
|
||||
y += SDL_LINE_HEIGHT
|
||||
}
|
||||
|
||||
// Center divider on top of the row backgrounds.
|
||||
gpu_rect(gpu, area_x + half, SDL_EDITOR_TOP, 1, area_bottom - SDL_EDITOR_TOP, 52, 54, 62, 255)
|
||||
}
|
||||
|
||||
render_diff_side_gpu :: proc(gpu: ^GPU_Renderer, x, y, width, gutter_width: f32, columns, number: int, text: string, kind: u8) {
|
||||
background: [3]u8
|
||||
has_background := true
|
||||
switch kind {
|
||||
case '-':
|
||||
background = DIFF_REMOVED_BG
|
||||
case '+':
|
||||
background = DIFF_ADDED_BG
|
||||
case 0:
|
||||
background = DIFF_FILLER_BG
|
||||
case:
|
||||
has_background = false
|
||||
}
|
||||
if has_background {
|
||||
gpu_rect(gpu, x, y - 3, width, SDL_LINE_HEIGHT + 1, background[0], background[1], background[2], 255)
|
||||
}
|
||||
if kind == 0 do return
|
||||
|
||||
if number > 0 {
|
||||
gpu_text(gpu, x + 6, y, fmt_diff_number(number), 116, 120, 128, 255)
|
||||
}
|
||||
marker_color := [3]u8{210, 214, 222}
|
||||
if kind == '-' do marker_color = {235, 140, 145}
|
||||
if kind == '+' do marker_color = {160, 210, 140}
|
||||
gpu_text_limited(gpu, x + gutter_width, y, text, columns, marker_color[0], marker_color[1], marker_color[2], 255)
|
||||
}
|
||||
|
||||
fmt_diff_number :: proc(number: int) -> string {
|
||||
buf: [8]u8
|
||||
value := number
|
||||
index := len(buf)
|
||||
for value > 0 && index > 0 {
|
||||
index -= 1
|
||||
buf[index] = '0' + u8(value % 10)
|
||||
value /= 10
|
||||
}
|
||||
out := make([]u8, 4, context.temp_allocator)
|
||||
for i in 0 ..< 4 {
|
||||
out[i] = ' '
|
||||
}
|
||||
digits := len(buf) - index
|
||||
copy(out[max_int(4 - digits, 0):], buf[index:])
|
||||
return string(out)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue