Switch to c++
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,7 +7,10 @@
|
||||
|
||||
.vscode
|
||||
|
||||
tests/tmp_*
|
||||
tests/*
|
||||
!tests/.keep
|
||||
|
||||
builds/*
|
||||
!builds/.keep
|
||||
|
||||
src/cpp/__tmp__
|
||||
|
||||
80
Makefile
Normal file
80
Makefile
Normal file
@@ -0,0 +1,80 @@
|
||||
SRC_DIR := src
|
||||
BIN_DIR := bin
|
||||
OBJ_DIR := build
|
||||
|
||||
TARGET_DEBUG := $(BIN_DIR)/crib-dbg
|
||||
TARGET_RELEASE := $(BIN_DIR)/crib
|
||||
|
||||
CXX_DEBUG := g++
|
||||
CXX_RELEASE := clang++
|
||||
|
||||
CFLAGS_DEBUG := -std=c++20 -Wall -Wextra -O0 -g -fno-inline -gsplit-dwarf
|
||||
CFLAGS_RELEASE := -std=c++20 -O3 -march=native -flto=thin \
|
||||
-fno-exceptions -fno-rtti -fstrict-aliasing -ffast-math -funroll-loops \
|
||||
-fomit-frame-pointer -DNDEBUG -s \
|
||||
-mllvm -inline-threshold=10000 \
|
||||
-mllvm -vectorize-loops \
|
||||
-mllvm -force-vector-width=8 \
|
||||
-mllvm -unroll-threshold=500000
|
||||
|
||||
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
|
||||
|
||||
UNICODE_OBJ_DEBUG := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/debug/unicode_width/%.o,$(UNICODE_SRC))
|
||||
UNICODE_OBJ_RELEASE := $(patsubst libs/unicode_width/%.c,$(OBJ_DIR)/release/unicode_width/%.o,$(UNICODE_SRC))
|
||||
|
||||
LIBS := \
|
||||
libs/libgrapheme/libgrapheme.a \
|
||||
libs/tree-sitter/libtree-sitter.a \
|
||||
libs/tree-sitter-ruby/libtree-sitter-ruby.a \
|
||||
-lpcre2-8
|
||||
|
||||
|
||||
SRC := $(wildcard $(SRC_DIR)/*.cc)
|
||||
OBJ_DEBUG := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/debug/%.o,$(SRC))
|
||||
OBJ_RELEASE := $(patsubst $(SRC_DIR)/%.cc,$(OBJ_DIR)/release/%.o,$(SRC))
|
||||
|
||||
DEP_DEBUG := $(OBJ_DEBUG:.o=.d)
|
||||
DEP_RELEASE := $(OBJ_RELEASE:.o=.d)
|
||||
|
||||
.PHONY: all test release clean
|
||||
|
||||
all: debug
|
||||
|
||||
test: $(TARGET_DEBUG)
|
||||
|
||||
release: $(TARGET_RELEASE)
|
||||
|
||||
$(TARGET_DEBUG): $(OBJ_DEBUG) $(UNICODE_OBJ_DEBUG)
|
||||
mkdir -p $(BIN_DIR)
|
||||
$(CXX_DEBUG) $(CFLAGS_DEBUG) -o $@ $^ $(LIBS)
|
||||
|
||||
$(TARGET_RELEASE): $(OBJ_RELEASE) $(UNICODE_OBJ_RELEASE)
|
||||
mkdir -p $(BIN_DIR)
|
||||
$(CXX_RELEASE) $(CFLAGS_RELEASE) -o $@ $^ $(LIBS)
|
||||
|
||||
# Pattern rules for object files + dependency generation
|
||||
$(OBJ_DIR)/debug/%.o: $(SRC_DIR)/%.cc
|
||||
mkdir -p $(dir $@)
|
||||
$(CXX_DEBUG) $(CFLAGS_DEBUG) -MMD -MP -c $< -o $@
|
||||
|
||||
$(OBJ_DIR)/release/%.o: $(SRC_DIR)/%.cc
|
||||
mkdir -p $(dir $@)
|
||||
$(CXX_RELEASE) $(CFLAGS_RELEASE) -MMD -MP -c $< -o $@
|
||||
|
||||
$(OBJ_DIR)/debug/unicode_width/%.o: libs/unicode_width/%.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CXX_DEBUG) $(CFLAGS_DEBUG) -MMD -MP -c $< -o $@
|
||||
|
||||
$(OBJ_DIR)/release/unicode_width/%.o: libs/unicode_width/%.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CXX_RELEASE) $(CFLAGS_RELEASE) -MMD -MP -c $< -o $@
|
||||
|
||||
# Include deps if they exist
|
||||
DEP_DEBUG += $(UNICODE_OBJ_DEBUG:.o=.d)
|
||||
DEP_RELEASE += $(UNICODE_OBJ_RELEASE:.o=.d)
|
||||
|
||||
-include $(DEP_DEBUG)
|
||||
-include $(DEP_RELEASE)
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJ_DIR) $(BIN_DIR)
|
||||
54
__old__/crib.rb
Executable file
54
__old__/crib.rb
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "ffi"
|
||||
require "zlib"
|
||||
|
||||
require_relative "../src/ruby/utils"
|
||||
require_relative "../src/ruby/mod"
|
||||
require_relative "../src/ruby/ts_rb"
|
||||
require_relative "../src/ruby/fm"
|
||||
require_relative "../src/ruby/editor"
|
||||
require_relative "../src/ruby/ide"
|
||||
|
||||
$rows, $cols = C.start_screen
|
||||
$running = true
|
||||
$event_queue = Queue.new
|
||||
$folder = Dir.new File.dirname(ARGV[0] || Dir.pwd)
|
||||
$threads = []
|
||||
|
||||
at_exit do
|
||||
IDE.close
|
||||
C.end_screen
|
||||
puts "Exiting crib.rb"
|
||||
end
|
||||
|
||||
IDE.start
|
||||
|
||||
$threads << Thread.new do
|
||||
loop do
|
||||
sleep 1.0 / 64
|
||||
break unless $running
|
||||
IDE.handle_event $event_queue.pop timeout: 0 until $event_queue.empty?
|
||||
IDE.render
|
||||
C.render
|
||||
end
|
||||
end
|
||||
|
||||
$threads << Thread.new do
|
||||
loop do
|
||||
sleep 1.0 / 64
|
||||
break unless $running
|
||||
IDE.work!
|
||||
end
|
||||
end
|
||||
|
||||
$threads << Thread.new do
|
||||
loop do
|
||||
break unless $running
|
||||
event = C.read_key # read_key is blocking
|
||||
$running = false if KEY_TYPE[event[:key_type]] == :char && event[:c] == ctrl_key('q')
|
||||
$event_queue << event
|
||||
end
|
||||
end
|
||||
|
||||
$threads.each &:join
|
||||
329
__old__/editor.rb
Normal file
329
__old__/editor.rb
Normal file
@@ -0,0 +1,329 @@
|
||||
class Editor
|
||||
attr_accessor :text, :cursor, :selection_start, :filename
|
||||
|
||||
def initialize(filename, x, y, width, height)
|
||||
contents = File.exist?(filename) ? File.read(filename) : "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
|
||||
@text = contents.grapheme_clusters
|
||||
@cursor = @text.size
|
||||
@selection_start = 0
|
||||
@scroll = 0
|
||||
@x = x
|
||||
@y = y
|
||||
@width = width
|
||||
@height = height
|
||||
@filename = filename
|
||||
@folds = []
|
||||
@highlights = Array.new(@text.size)
|
||||
@highlight_generation = 0
|
||||
@tree = nil
|
||||
@num_width = (@text.count("\n") + 1).to_s.size + 3
|
||||
|
||||
@parser = C.ts_parser_new
|
||||
@lang_ts = C.tree_sitter_ruby
|
||||
C.ts_parser_set_language @parser, @lang_ts
|
||||
@cached_tree = nil
|
||||
C.load_query "/home/syed/main/crib/grammar/ruby.scm"
|
||||
|
||||
highlight!
|
||||
|
||||
rebuild_folded_lines!
|
||||
end
|
||||
|
||||
def save!
|
||||
File.write @filename, @text.join
|
||||
end
|
||||
|
||||
def highlight!
|
||||
gen = @highlight_generation
|
||||
src = @text.join
|
||||
text_bytes = []
|
||||
byte_offset = 0
|
||||
@text.each do |cluster|
|
||||
text_bytes << byte_offset
|
||||
byte_offset += cluster.bytesize
|
||||
end
|
||||
text_bytes << byte_offset
|
||||
line_map = []
|
||||
line = 0
|
||||
@text.each_with_index do |ch, i|
|
||||
line_map[i] = line
|
||||
line += 1 if ch == "\n"
|
||||
end
|
||||
# TODO: add ts_tree_edit calls in erase/insert
|
||||
count_ptr = FFI::MemoryPointer.new :uint32
|
||||
@cached_tree = C.ts_parser_parse_string @parser, nil, src, src.bytesize
|
||||
root = C.ts_tree_root_node @cached_tree
|
||||
ptr = C.ts_collect_tokens root, count_ptr, @lang_ts, src
|
||||
count = count_ptr.read_uint32
|
||||
ints = ptr.read_array_of_uint32 count
|
||||
C.free_tokens ptr
|
||||
count_ptr.free
|
||||
C.ts_tree_delete @cached_tree
|
||||
return if gen != @highlight_generation
|
||||
@highlight_generation = 0
|
||||
@highlights = Array.new(@text.size)
|
||||
done_folds = []
|
||||
ints.each_slice(3).map do |start_byte, end_byte, sym_i|
|
||||
start_cluster = byte_to_cluster start_byte, text_bytes
|
||||
end_cluster = byte_to_cluster end_byte - 1, text_bytes
|
||||
next if start_cluster.nil? || end_cluster.nil?
|
||||
sym = TS_SYMBOL_MAP[sym_i]
|
||||
if sym == "@fold"
|
||||
start_row = line_map[start_cluster]
|
||||
end_row = line_map[end_cluster]
|
||||
next if start_row == end_row
|
||||
hash_value = Zlib.crc32 @text[start_cluster...end_cluster].join
|
||||
done_folds << hash_value
|
||||
if (f = @folds.find { |f| f.crc32 == hash_value })
|
||||
f.start = start_row
|
||||
f.end = end_row
|
||||
else
|
||||
@folds << Fold.new(start_row, end_row, false, hash_value)
|
||||
end
|
||||
next
|
||||
end
|
||||
hl = TS_RUBY[sym]
|
||||
next unless hl
|
||||
(start_cluster..end_cluster).each do |i|
|
||||
@highlights[i] = hl if @highlights[i].nil? || @highlights[i].priority < hl.priority
|
||||
end
|
||||
end
|
||||
@folds.select! { |f| done_folds.include? f.crc32 }
|
||||
rebuild_folded_lines!
|
||||
end
|
||||
|
||||
def byte_to_cluster(byte, byte_starts)
|
||||
left = 0
|
||||
right = byte_starts.size - 2
|
||||
while left <= right
|
||||
mid = (left + right) / 2
|
||||
if byte_starts[mid] <= byte && byte < byte_starts[mid + 1]
|
||||
return mid
|
||||
elsif byte < byte_starts[mid]
|
||||
right = mid - 1
|
||||
else
|
||||
left = mid + 1
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def erase(x0, x1)
|
||||
return if x0 < 0 || x1 < x0
|
||||
lines_deleted = @text[x0...x1].count "\n"
|
||||
@text[x0...x1] = []
|
||||
@highlights[x0...x1] = []
|
||||
@highlight_generation += 1
|
||||
@folds.map! do |f|
|
||||
if f.start >= @text[...x0].count("\n")
|
||||
f.start -= lines_deleted
|
||||
f.end -= lines_deleted
|
||||
end
|
||||
f
|
||||
end
|
||||
rebuild_folded_lines!
|
||||
@num_width = (@text.count("\n") + 1).to_s.size + 3
|
||||
end
|
||||
|
||||
def insert(x, str)
|
||||
@text.insert x, *str.grapheme_clusters
|
||||
@highlights.insert x, *Array.new(str.grapheme_clusters.size)
|
||||
lines_added = str.count "\n"
|
||||
@folds.map! do |f|
|
||||
if f.start >= @text[...x].count("\n")
|
||||
f.start += lines_added
|
||||
f.end += lines_added
|
||||
end
|
||||
f
|
||||
end
|
||||
rebuild_folded_lines!
|
||||
@highlight_generation += 1
|
||||
@num_width = (@text.count("\n") + 1).to_s.size + 3
|
||||
end
|
||||
|
||||
def move_up!
|
||||
precursor_text = @text[...@cursor]
|
||||
last_newline = precursor_text.rindex "\n"
|
||||
return unless last_newline
|
||||
@preferred_col ||= @cursor - last_newline
|
||||
prev_start = precursor_text[...last_newline].rindex("\n") || -1
|
||||
@cursor = [
|
||||
prev_start + @preferred_col,
|
||||
@text[prev_start + 1..].index("\n")&.+(prev_start + 1) || @text.size
|
||||
].min
|
||||
end
|
||||
|
||||
def move_down!
|
||||
next_newline = @text[@cursor..].index("\n")&.+(@cursor)
|
||||
return unless next_newline
|
||||
@preferred_col ||= (@cursor - (@text[...@cursor].rindex("\n") || -1)).abs
|
||||
@cursor = [
|
||||
next_newline + @preferred_col,
|
||||
@text[next_newline + 1..].index("\n")&.+(next_newline + 1) || @text.size
|
||||
].min
|
||||
end
|
||||
|
||||
def move_left!
|
||||
@cursor = [@cursor - 1, 0].max
|
||||
@preferred_col = nil
|
||||
end
|
||||
|
||||
def move_right!
|
||||
@cursor = [@cursor + 1, @text.size].min
|
||||
@preferred_col = nil
|
||||
end
|
||||
|
||||
def cursor_valid?
|
||||
!@folded_lines.include? @text[0...@cursor].count("\n")
|
||||
end
|
||||
|
||||
def handle_event(event)
|
||||
return if event.nil?
|
||||
case KEY_TYPE[event[:key_type]]
|
||||
when :char
|
||||
ch = event[:c].chr
|
||||
ch = "\n" if ch == "\r"
|
||||
if ch == "'"
|
||||
cr = @text[0...@cursor].count("\n")
|
||||
@folds.find { |f| f.active = !f.active if f.start <= cr && f.end >= cr }
|
||||
rebuild_folded_lines!
|
||||
elsif ch == ctrl_key('s').chr
|
||||
save!
|
||||
elsif ch =~ /[[:print:]]|\n/
|
||||
insert @cursor, ch
|
||||
@cursor += 1
|
||||
elsif ch == "\b" || ch == "\x7f"
|
||||
erase @cursor - 1, @cursor
|
||||
@cursor = [@cursor - 1, 0].max
|
||||
end
|
||||
@preferred_col = nil
|
||||
when :special
|
||||
return unless MODIFIER[event[:special_modifier]] == :none
|
||||
case SPECIAL_KEY[event[:special_key]]
|
||||
when :up
|
||||
move_up!
|
||||
move_up! until cursor_valid?
|
||||
when :down
|
||||
move_down!
|
||||
move_down! until cursor_valid?
|
||||
when :left
|
||||
move_left!
|
||||
move_left! until cursor_valid?
|
||||
when :right
|
||||
move_right!
|
||||
move_right! until cursor_valid?
|
||||
when :delete
|
||||
erase @cursor, @cursor + 1
|
||||
@cursor = [@cursor, @text.size].min
|
||||
@preferred_col = nil
|
||||
end
|
||||
end
|
||||
move_right! until cursor_valid?
|
||||
adjust_scroll!
|
||||
end
|
||||
|
||||
def adjust_scroll!
|
||||
cr = cursor_row_visual
|
||||
start = @scroll
|
||||
last = @scroll + @height - 1
|
||||
if cr < start
|
||||
@scroll = cr
|
||||
elsif cr > last
|
||||
@scroll = cr - @height + 1
|
||||
end
|
||||
end
|
||||
|
||||
def cursor_row_visual
|
||||
row = col = 0
|
||||
nc = 0
|
||||
@text.each_with_index do |ch, i|
|
||||
return row if i == @cursor
|
||||
if @folded_lines.include? nc
|
||||
nc += 1 if ch == "\n"
|
||||
next
|
||||
end
|
||||
if ch == "\n"
|
||||
row += 1
|
||||
nc += 1
|
||||
col = 0
|
||||
next
|
||||
end
|
||||
w = C.display_width(ch)
|
||||
if col + w > @width - @num_width
|
||||
row += 1
|
||||
col = 0
|
||||
end
|
||||
col += w
|
||||
end
|
||||
row
|
||||
end
|
||||
|
||||
def rebuild_folded_lines!
|
||||
@folded_lines = Set.new
|
||||
@folds.each do |f|
|
||||
(f.start + 1..f.end).each { |line_num| @folded_lines.add(line_num) } if f.active
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
(0...@height).each { |h| (0...@width).each { |w|
|
||||
C.update @y + h, @x + w, ' ', 0x000000, 0x000000, 0
|
||||
} }
|
||||
row = col = nc = 0
|
||||
cursor_row = cursor_col = nil
|
||||
@text.each_with_index do |ch, i|
|
||||
if @folded_lines.include? nc
|
||||
nc += 1 if ch == "\n"
|
||||
next
|
||||
end
|
||||
if i == @cursor
|
||||
cursor_row = row
|
||||
cursor_col = col
|
||||
end
|
||||
if col == 0
|
||||
color = 0x666666
|
||||
color = 0xFFFFFF if cursor_row == row
|
||||
num = (nc + 1).to_s.rjust @num_width - 1, '👪👩💻👨🏿🚀🏳️🌈🔥🛠️🌍🥶✅❌💡⏰🚀'#ddasasdasda
|
||||
num.each_char.with_index do |c, j|
|
||||
C.update @y + row - @scroll, @x + j, c, color, 0x000000, 0
|
||||
end
|
||||
if (fold = @folds.find { |f| nc == f.start })
|
||||
icon = fold.active ? "" : "" # "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : "" "" : ""
|
||||
C.update @y + row - @scroll, @x, icon, 0xFF0000, 0x000000, 0
|
||||
end
|
||||
end
|
||||
if ch == "\n"
|
||||
row += 1
|
||||
nc += 1
|
||||
col = 0
|
||||
next
|
||||
end
|
||||
w = C.display_width(ch)
|
||||
if col + w > @width - @num_width
|
||||
row += 1
|
||||
col = 0
|
||||
end
|
||||
col += w
|
||||
next if row < @scroll
|
||||
break if row - @scroll >= @height
|
||||
fg = 0xFFFFFF
|
||||
bg = 0x000000
|
||||
fl = 0
|
||||
if (h = @highlights[i])
|
||||
fg = h.color_fg
|
||||
bg = h.color_bg
|
||||
fl = h.flags
|
||||
end
|
||||
C.update @y + row - @scroll, @x + @num_width + col - w, ch, fg, bg, fl
|
||||
end
|
||||
if @text[-1] == "\n" || @text.size == 0
|
||||
num = (nc + 1).to_s.rjust @num_width - 1, ' '
|
||||
color = 0x666666
|
||||
color = 0xFFFFFF if (cursor_row || row) == row
|
||||
num.each_char.with_index do |c, j|
|
||||
C.update @y + row - @scroll, @x + j, c, color, 0x000000, 0
|
||||
end
|
||||
end
|
||||
C.set_cursor @y - @scroll + (cursor_row || row), @x + @num_width + (cursor_col || col), 1
|
||||
end
|
||||
end
|
||||
30
__old__/fm.rb
Normal file
30
__old__/fm.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
module FileManager
|
||||
@hidden = true
|
||||
|
||||
module_function
|
||||
|
||||
def start(x, y, width, height)
|
||||
@hidden = false
|
||||
@x = x
|
||||
@y = y
|
||||
@width = width
|
||||
@height = height
|
||||
end
|
||||
|
||||
def toggle!
|
||||
@hidden = !@hidden
|
||||
end
|
||||
|
||||
def render
|
||||
return if @hidden
|
||||
(0...@height).each { |h| (0...@width).each { |w|
|
||||
C.update @y + h, @x + w, ' ', 0x000000, 0x000000, 0
|
||||
} }
|
||||
files = $folder.children
|
||||
files.each_with_index do |f, i|
|
||||
f.each_char.with_index do |c, j|
|
||||
C.update @y + i, @x + j, c, 0xFFFFFF, 0x000000, 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
__old__/ide.rb
Normal file
36
__old__/ide.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
module IDE
|
||||
FM_WIDTH = 20
|
||||
|
||||
module_function
|
||||
|
||||
def start
|
||||
@editors = {}
|
||||
@u_c = 0
|
||||
editor = Editor.new (ARGV[0] || ""), FM_WIDTH, 0, $cols - FM_WIDTH, $rows
|
||||
@editors[editor.filename || "untitled #{@u_c += 1}"] = editor
|
||||
@selected_editor = @editors.keys.first
|
||||
@focus = :editor
|
||||
FileManager.start 0, 0, FM_WIDTH, $rows
|
||||
end
|
||||
|
||||
def handle_event(event)
|
||||
if @focus == :editor
|
||||
@editors[@selected_editor].handle_event event if @editors.key? @selected_editor
|
||||
elsif @focus == :file_manager
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
@editors[@selected_editor].render if @editors.key? @selected_editor
|
||||
FileManager.render
|
||||
end
|
||||
|
||||
def work!
|
||||
@editors[@selected_editor].highlight! if @editors.key? @selected_editor
|
||||
end
|
||||
|
||||
def close
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
126
__old__/mod.rb
Normal file
126
__old__/mod.rb
Normal file
@@ -0,0 +1,126 @@
|
||||
module C
|
||||
extend FFI::Library
|
||||
ffi_lib File.join(__dir__, "../../libs/tree-sitter/libtree-sitter.so")
|
||||
|
||||
# TSTree and TSParser are opaque pointers
|
||||
typedef :pointer, :tstree
|
||||
typedef :pointer, :tsparser
|
||||
|
||||
# Define TSNode struct
|
||||
class TSNode < FFI::Struct
|
||||
layout :context, [:uint32, 4],
|
||||
:id, :pointer,
|
||||
:tree, :tstree
|
||||
end
|
||||
|
||||
class TSPoint < FFI::Struct
|
||||
layout :row, :uint32,
|
||||
:column, :uint32
|
||||
end
|
||||
|
||||
class TSInputEdit < FFI::Struct
|
||||
layout :start_byte, :uint32,
|
||||
:old_end_byte, :uint32,
|
||||
:new_end_byte, :uint32,
|
||||
:start_point, TSPoint.by_value,
|
||||
:old_end_point, TSPoint.by_value,
|
||||
:new_end_point, TSPoint.by_value
|
||||
end
|
||||
|
||||
class TSTreeCursor < FFI::Struct
|
||||
layout :tree, :pointer,
|
||||
:id, :pointer,
|
||||
:context, [:uint32, 3]
|
||||
end
|
||||
|
||||
attach_function :ts_parser_new, [], :tsparser
|
||||
attach_function :ts_parser_set_language, [:tsparser, :pointer], :bool
|
||||
attach_function :ts_parser_parse_string, [:tsparser, :tstree, :string, :size_t], :tstree, blocking: true
|
||||
attach_function :ts_tree_delete, [:tstree], :void
|
||||
|
||||
attach_function :ts_node_start_byte, [TSNode.by_value], :uint32
|
||||
attach_function :ts_node_end_byte, [TSNode.by_value], :uint32
|
||||
attach_function :ts_node_symbol, [TSNode.by_value], :uint16
|
||||
|
||||
attach_function :ts_tree_edit, [:tstree, TSInputEdit.by_ref], :void
|
||||
|
||||
attach_function :ts_tree_root_node, [:tstree], TSNode.by_value, blocking: true
|
||||
|
||||
attach_function :ts_node_child_count, [TSNode.by_value], :uint32
|
||||
attach_function :ts_node_child, [TSNode.by_value, :uint32], TSNode.by_value
|
||||
|
||||
|
||||
|
||||
# Ruby grammar
|
||||
ffi_lib File.join(__dir__, "../../libs/tree-sitter-ruby/libtree-sitter-ruby.so")
|
||||
attach_function :tree_sitter_ruby, [], :pointer
|
||||
|
||||
|
||||
ffi_lib File.join(__dir__, "../../builds/C-crib.so")
|
||||
|
||||
class KeyEvent < FFI::Struct
|
||||
layout :key_type, :uint8,
|
||||
:c, :char,
|
||||
:special_key, :uint8,
|
||||
:special_modifier, :uint8,
|
||||
:mouse_x, :uint8,
|
||||
:mouse_y, :uint8,
|
||||
:mouse_button, :uint8,
|
||||
:mouse_state, :uint8,
|
||||
:mouse_direction, :uint8,
|
||||
:mouse_modifier, :uint8
|
||||
|
||||
def to_s
|
||||
case KEY_TYPE[self[:key_type]]
|
||||
when :char
|
||||
"#<KeyEvent char=#{self[:c].inspect}>"
|
||||
when :special
|
||||
"#<KeyEvent special key=#{SPECIAL_KEY[self[:special_key]]} mod=#{MODIFIER[self[:special_modifier]]}>"
|
||||
when :mouse
|
||||
"#<KeyEvent mouse x=#{self[:mouse_x]} y=#{self[:mouse_y]} " \
|
||||
"btn=#{MOUSE_BUTTON[self[:mouse_button]]} state=#{MOUSE_STATE[self[:mouse_state]]} " \
|
||||
"scroll_dir=#{SCROLL_DIR[self[:mouse_direction]]} mod=#{MODIFIER[self[:mouse_modifier]]}>"
|
||||
else
|
||||
"#<KeyEvent type=#{self[:key_type]} unknown=true>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Coords < FFI::Struct
|
||||
layout :x, :int,
|
||||
:y, :int
|
||||
|
||||
def to_ary
|
||||
[self[:x], self[:y]]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<Coords x=#{self[:x]} y=#{self[:y]}>"
|
||||
end
|
||||
end
|
||||
|
||||
class Size < FFI::Struct
|
||||
layout :rows, :int,
|
||||
:cols, :int
|
||||
|
||||
def to_ary
|
||||
[self[:rows], self[:cols]]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<Size rows=#{self[:rows]} cols=#{self[:cols]}>"
|
||||
end
|
||||
end
|
||||
|
||||
attach_function :load_query, [:string], :void
|
||||
attach_function :ts_collect_tokens, [TSNode.by_value, :pointer, :pointer, :string], :pointer, blocking: true
|
||||
attach_function :free_tokens, [:pointer], :void, blocking: true
|
||||
attach_function :start_screen, [], Size.by_value
|
||||
attach_function :end_screen, [], :void
|
||||
attach_function :update, [:int, :int, :string, :uint32, :uint32, :uint8], :void
|
||||
attach_function :render, [], :void, blocking: true
|
||||
attach_function :set_cursor, [:int, :int, :int], :void
|
||||
attach_function :read_key, [], KeyEvent.by_value, blocking: true
|
||||
attach_function :get_size, [], Coords.by_value
|
||||
attach_function :display_width, [:string], :int, blocking: true
|
||||
end
|
||||
57
__old__/ts_rb.rb
Normal file
57
__old__/ts_rb.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
query = File.read "/home/syed/main/crib/grammar/ruby.scm"
|
||||
|
||||
raw = query.scan(/@[a-zA-Z0-9_.]+/)
|
||||
|
||||
seen = {}
|
||||
ordered = []
|
||||
|
||||
raw.each do |c|
|
||||
next if seen[c]
|
||||
seen[c] = true
|
||||
ordered << c
|
||||
end
|
||||
|
||||
TS_SYMBOL_MAP = ordered.freeze
|
||||
|
||||
spawn "echo \"#{TS_SYMBOL_MAP.sort.join "\n"}\" > /tmp/gg"
|
||||
|
||||
TS_RUBY = {
|
||||
"@string.special.symbol" => Highlight.new(0xbd9ae6, 0x000000, CF_NONE, 2),
|
||||
"@comment" => Highlight.new(0xAAAAAA, 0x000000, CF_ITALIC, 1),
|
||||
"@boolean.true" => Highlight.new(0x51eeba, 0x000000, CF_NONE, 1),
|
||||
"@boolean.false" => Highlight.new(0xee513a, 0x000000, CF_NONE, 1),
|
||||
"@constant.nil" => Highlight.new(0xee8757, 0x000000, CF_NONE, 1),
|
||||
"@constant" => Highlight.new(0xebda8c, 0x000000, CF_NONE, 1),
|
||||
"@number" => Highlight.new(0xebda8c, 0x000000, CF_NONE, 2),
|
||||
"@number.float" => Highlight.new(0xebda8c, 0x000000, CF_NONE, 2),
|
||||
"@constant.builtin" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 2),
|
||||
"@punctuation.bracket" => Highlight.new(0xbd9ae6, 0x000000, CF_NONE, 1),
|
||||
"@operator.ligature" => Highlight.new(0xffffff, 0x000000, CF_ITALIC, 1),
|
||||
"@operator" => Highlight.new(0xffffff, 0x000000, CF_NONE, 1),
|
||||
"@punctuation.delimiter" => Highlight.new(0xbd9ae6, 0x000000, CF_NONE, 1),
|
||||
"@punctuation.special" => Highlight.new(0xe6a24c, 0x000000, CF_NONE, 1),
|
||||
"@function" => Highlight.new(0xaad84c, 0x000000, CF_NONE, 1),
|
||||
"@function.builtin" => Highlight.new(0xaad84c, 0xFF0000, CF_NONE, 1),
|
||||
"@keyword.import" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@function.call" => Highlight.new(0xff5689, 0x000000, CF_NONE, 1),
|
||||
|
||||
"@keyword" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.conditional" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.control" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.directive" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.exception" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.function" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.operator" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.repeat" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.return" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@keyword.type" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@label" => Highlight.new(0xfbb152, 0x000000, CF_NONE, 1),
|
||||
"@string" => Highlight.new(0xaad84c, 0x000000, CF_NONE, 1),
|
||||
"@string.escape" => Highlight.new(0xe6a24c, 0x000000, CF_NONE, 2),
|
||||
"@string.regexp" => Highlight.new(0xe6a24c, 0x000000, CF_NONE, 2),
|
||||
"@type" => Highlight.new(0xaad84c, 0x000000, CF_NONE, 1),
|
||||
"@variable" => Highlight.new(0xffffff, 0x000000, CF_NONE, 1),
|
||||
"@variable.builtin" => Highlight.new(0xffffff, 0x000000, CF_NONE, 1),
|
||||
"@variable.member" => Highlight.new(0xffffff, 0x000000, CF_NONE, 1),
|
||||
"@variable.parameter" => Highlight.new(0xffffff, 0x000000, CF_NONE, 1),
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
Fold = Struct.new(:start, :end, :active, :crc32)
|
||||
Highlight = Struct.new(:color_fg, :color_bg, :flags, :priority)
|
||||
Token = Struct.new(:start, :end, :sym)
|
||||
|
||||
CF_NONE = 0b00000000
|
||||
CF_ITALIC = 0b00000001
|
||||
CF_BOLD = 0b00000010
|
||||
CF_UNDERLINE = 0b00000100
|
||||
|
||||
def ctrl_key(k)
|
||||
k.ord & 0x1F
|
||||
end
|
||||
@@ -6,8 +15,9 @@ end
|
||||
KEY_TYPE = {
|
||||
0 => :char,
|
||||
1 => :special,
|
||||
2 => :mouse
|
||||
}
|
||||
2 => :mouse,
|
||||
3 => :none
|
||||
}.freeze
|
||||
|
||||
# Special keys
|
||||
SPECIAL_KEY = {
|
||||
@@ -16,10 +26,10 @@ SPECIAL_KEY = {
|
||||
2 => :left,
|
||||
3 => :right,
|
||||
4 => :delete
|
||||
}
|
||||
}.freeze
|
||||
|
||||
# Control key
|
||||
KEY_ESC = "\x1b"
|
||||
KEY_ESC = "\x1b".freeze
|
||||
|
||||
# Mouse states
|
||||
MOUSE_STATE = {
|
||||
@@ -27,7 +37,7 @@ MOUSE_STATE = {
|
||||
1 => :release,
|
||||
2 => :drag,
|
||||
3 => :scroll
|
||||
}
|
||||
}.freeze
|
||||
|
||||
# Mouse buttons
|
||||
MOUSE_BUTTON = {
|
||||
@@ -36,7 +46,7 @@ MOUSE_BUTTON = {
|
||||
2 => :right,
|
||||
3 => :scroll,
|
||||
4 => :none
|
||||
}
|
||||
}.freeze
|
||||
|
||||
# Scroll directions
|
||||
SCROLL_DIR = {
|
||||
@@ -45,12 +55,13 @@ SCROLL_DIR = {
|
||||
2 => :left,
|
||||
3 => :right,
|
||||
4 => :none
|
||||
}
|
||||
}.freeze
|
||||
|
||||
# Modifiers
|
||||
MODIFIER = {
|
||||
0 => :none,
|
||||
1 => :alt,
|
||||
2 => :cntrl,
|
||||
3 => :cntrl_alt,
|
||||
4 => :shift
|
||||
}
|
||||
}.freeze
|
||||
BIN
bin/crib-dbg
Executable file
BIN
bin/crib-dbg
Executable file
Binary file not shown.
14
build/debug/editor.d
Normal file
14
build/debug/editor.d
Normal file
@@ -0,0 +1,14 @@
|
||||
build/debug/editor.o: src/editor.cc src/../libs/libgrapheme/grapheme.h \
|
||||
src/../include/editor.h \
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/./rope.h src/../include/./ui.h src/../include/./utils.h \
|
||||
src/../include/ts.h \
|
||||
src/../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../include/editor.h:
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/./rope.h:
|
||||
src/../include/./ui.h:
|
||||
src/../include/./utils.h:
|
||||
src/../include/ts.h:
|
||||
src/../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h:
|
||||
BIN
build/debug/editor.dwo
Normal file
BIN
build/debug/editor.dwo
Normal file
Binary file not shown.
2
build/debug/input.d
Normal file
2
build/debug/input.d
Normal file
@@ -0,0 +1,2 @@
|
||||
build/debug/input.o: src/input.cc src/../include/ui.h
|
||||
src/../include/ui.h:
|
||||
BIN
build/debug/input.dwo
Normal file
BIN
build/debug/input.dwo
Normal file
Binary file not shown.
12
build/debug/main.d
Normal file
12
build/debug/main.d
Normal file
@@ -0,0 +1,12 @@
|
||||
build/debug/main.o: src/main.cc src/../include/editor.h \
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/./rope.h src/../include/./ui.h src/../include/./utils.h \
|
||||
src/../include/ts.h \
|
||||
src/../libs/tree-sitter/lib/include/tree_sitter/api.h
|
||||
src/../include/editor.h:
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/./rope.h:
|
||||
src/../include/./ui.h:
|
||||
src/../include/./utils.h:
|
||||
src/../include/ts.h:
|
||||
src/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
BIN
build/debug/main.dwo
Normal file
BIN
build/debug/main.dwo
Normal file
Binary file not shown.
6
build/debug/renderer.d
Normal file
6
build/debug/renderer.d
Normal file
@@ -0,0 +1,6 @@
|
||||
build/debug/renderer.o: src/renderer.cc \
|
||||
src/../libs/libgrapheme/grapheme.h \
|
||||
src/../libs/unicode_width/unicode_width.h src/../include/ui.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../libs/unicode_width/unicode_width.h:
|
||||
src/../include/ui.h:
|
||||
BIN
build/debug/renderer.dwo
Normal file
BIN
build/debug/renderer.dwo
Normal file
Binary file not shown.
2
build/debug/rope.d
Normal file
2
build/debug/rope.d
Normal file
@@ -0,0 +1,2 @@
|
||||
build/debug/rope.o: src/rope.cc src/../include/rope.h
|
||||
src/../include/rope.h:
|
||||
BIN
build/debug/rope.dwo
Normal file
BIN
build/debug/rope.dwo
Normal file
Binary file not shown.
11
build/debug/ts.d
Normal file
11
build/debug/ts.d
Normal file
@@ -0,0 +1,11 @@
|
||||
build/debug/ts.o: src/ts.cc src/../include/ts.h src/../include/./editor.h \
|
||||
src/../include/./../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/././rope.h src/../include/././ui.h \
|
||||
src/../include/././utils.h src/../include/rope.h
|
||||
src/../include/ts.h:
|
||||
src/../include/./editor.h:
|
||||
src/../include/./../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/././rope.h:
|
||||
src/../include/././ui.h:
|
||||
src/../include/././utils.h:
|
||||
src/../include/rope.h:
|
||||
BIN
build/debug/ts.dwo
Normal file
BIN
build/debug/ts.dwo
Normal file
Binary file not shown.
3
build/debug/unicode_width/unicode_width.d
Normal file
3
build/debug/unicode_width/unicode_width.d
Normal file
@@ -0,0 +1,3 @@
|
||||
build/debug/unicode_width/unicode_width.o: \
|
||||
libs/unicode_width/unicode_width.c libs/unicode_width/unicode_width.h
|
||||
libs/unicode_width/unicode_width.h:
|
||||
BIN
build/debug/unicode_width/unicode_width.dwo
Normal file
BIN
build/debug/unicode_width/unicode_width.dwo
Normal file
Binary file not shown.
4
build/debug/utils.d
Normal file
4
build/debug/utils.d
Normal file
@@ -0,0 +1,4 @@
|
||||
build/debug/utils.o: src/utils.cc src/../libs/libgrapheme/grapheme.h \
|
||||
src/../include/utils.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../include/utils.h:
|
||||
BIN
build/debug/utils.dwo
Normal file
BIN
build/debug/utils.dwo
Normal file
Binary file not shown.
15
build/release/editor.d
Normal file
15
build/release/editor.d
Normal file
@@ -0,0 +1,15 @@
|
||||
build/release/editor.o: src/editor.cc src/../libs/libgrapheme/grapheme.h \
|
||||
src/../include/editor.h \
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/./rope.h src/../include/./ui.h src/../include/./utils.h \
|
||||
src/../include/ts.h src/../include/./editor.h \
|
||||
src/../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../include/editor.h:
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/./rope.h:
|
||||
src/../include/./ui.h:
|
||||
src/../include/./utils.h:
|
||||
src/../include/ts.h:
|
||||
src/../include/./editor.h:
|
||||
src/../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h:
|
||||
2
build/release/input.d
Normal file
2
build/release/input.d
Normal file
@@ -0,0 +1,2 @@
|
||||
build/release/input.o: src/input.cc src/../include/ui.h
|
||||
src/../include/ui.h:
|
||||
14
build/release/main.d
Normal file
14
build/release/main.d
Normal file
@@ -0,0 +1,14 @@
|
||||
build/release/main.o: src/main.cc src/../include/editor.h \
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/./rope.h src/../include/./ui.h src/../include/./utils.h \
|
||||
src/../include/ts.h src/../include/./editor.h src/../include/ui.h \
|
||||
src/../libs/tree-sitter/lib/include/tree_sitter/api.h
|
||||
src/../include/editor.h:
|
||||
src/../include/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/./rope.h:
|
||||
src/../include/./ui.h:
|
||||
src/../include/./utils.h:
|
||||
src/../include/ts.h:
|
||||
src/../include/./editor.h:
|
||||
src/../include/ui.h:
|
||||
src/../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
6
build/release/renderer.d
Normal file
6
build/release/renderer.d
Normal file
@@ -0,0 +1,6 @@
|
||||
build/release/renderer.o: src/renderer.cc \
|
||||
src/../libs/libgrapheme/grapheme.h \
|
||||
src/../libs/unicode_width/unicode_width.h src/../include/ui.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../libs/unicode_width/unicode_width.h:
|
||||
src/../include/ui.h:
|
||||
2
build/release/rope.d
Normal file
2
build/release/rope.d
Normal file
@@ -0,0 +1,2 @@
|
||||
build/release/rope.o: src/rope.cc src/../include/rope.h
|
||||
src/../include/rope.h:
|
||||
14
build/release/ts.d
Normal file
14
build/release/ts.d
Normal file
@@ -0,0 +1,14 @@
|
||||
build/release/ts.o: src/ts.cc src/../include/ts.h \
|
||||
src/../include/./editor.h \
|
||||
src/../include/./../libs/tree-sitter/lib/include/tree_sitter/api.h \
|
||||
src/../include/././rope.h src/../include/././ui.h \
|
||||
src/../include/././utils.h src/../include/editor.h \
|
||||
src/../include/rope.h
|
||||
src/../include/ts.h:
|
||||
src/../include/./editor.h:
|
||||
src/../include/./../libs/tree-sitter/lib/include/tree_sitter/api.h:
|
||||
src/../include/././rope.h:
|
||||
src/../include/././ui.h:
|
||||
src/../include/././utils.h:
|
||||
src/../include/editor.h:
|
||||
src/../include/rope.h:
|
||||
3
build/release/unicode_width/unicode_width.d
Normal file
3
build/release/unicode_width/unicode_width.d
Normal file
@@ -0,0 +1,3 @@
|
||||
build/release/unicode_width/unicode_width.o: \
|
||||
libs/unicode_width/unicode_width.c libs/unicode_width/unicode_width.h
|
||||
libs/unicode_width/unicode_width.h:
|
||||
4
build/release/utils.d
Normal file
4
build/release/utils.d
Normal file
@@ -0,0 +1,4 @@
|
||||
build/release/utils.o: src/utils.cc src/../libs/libgrapheme/grapheme.h \
|
||||
src/../include/utils.h
|
||||
src/../libs/libgrapheme/grapheme.h:
|
||||
src/../include/utils.h:
|
||||
11
compile.sh
11
compile.sh
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
||||
|
||||
mkdir -p "$DIR/builds"
|
||||
|
||||
g++ -O2 -std=c++20 -shared -fPIC -Wall -Wextra \
|
||||
-o "$DIR/builds/C-crib.so" $DIR/src/cpp/*.cpp
|
||||
strip "$DIR/builds/C-crib.so"
|
||||
53
crib.rb
53
crib.rb
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require_relative "./src/ruby/mod"
|
||||
|
||||
# C.start_screen
|
||||
#
|
||||
# at_exit do
|
||||
# C.end_screen
|
||||
# puts "bye"
|
||||
# end
|
||||
#
|
||||
#
|
||||
# class Tester
|
||||
# @current_row = 0
|
||||
#
|
||||
# class << self
|
||||
# def debug_print(text)
|
||||
# text.each_char.with_index do |ch, i|
|
||||
# C.update(@current_row, i, ch, 0xFFFFFE, 0x000001, 0)
|
||||
# end
|
||||
# @current_row += 1
|
||||
# end
|
||||
#
|
||||
# def reset
|
||||
# @current_row = 0
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# render_thread = Thread.new do
|
||||
# loop do
|
||||
# sleep(1.0/20)
|
||||
# C.render
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# loop do
|
||||
# sleep 0.001
|
||||
# event = C.read_key
|
||||
# break if event[:key_type] == 0 && event[:c] == 'q'.ord
|
||||
# Tester.reset if event[:key_type] == 0 && event[:c] == 'r'.ord
|
||||
# Tester.debug_print(C.get_size.to_s) if event[:key_type] == 0 && event[:c] == 's'.ord
|
||||
# Tester.debug_print(event.to_s)
|
||||
# end
|
||||
#
|
||||
# render_thread.kill
|
||||
# render_thread.join
|
||||
|
||||
require_relative("./src/ruby/editor.rb")
|
||||
|
||||
test = Buffer.new("hello world\nwow")
|
||||
|
||||
pp test.render_text(0, 0, 10, 10)
|
||||
346
grammar/ruby.scm
Normal file
346
grammar/ruby.scm
Normal file
@@ -0,0 +1,346 @@
|
||||
[
|
||||
(method)
|
||||
(singleton_method)
|
||||
(class)
|
||||
(module)
|
||||
(if)
|
||||
(else)
|
||||
(case)
|
||||
(when)
|
||||
(in)
|
||||
(do_block)
|
||||
(singleton_class)
|
||||
(heredoc_content)
|
||||
(lambda)
|
||||
] @fold
|
||||
|
||||
;; #ffffff #000000 0 0 0 1
|
||||
[
|
||||
(identifier)
|
||||
(global_variable)
|
||||
] @variable
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"alias"
|
||||
"begin"
|
||||
"do"
|
||||
"end"
|
||||
"ensure"
|
||||
"module"
|
||||
"rescue"
|
||||
"then"
|
||||
] @keyword
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
"class" @keyword.type
|
||||
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"return"
|
||||
"yield"
|
||||
] @keyword.return
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"and"
|
||||
"or"
|
||||
"in"
|
||||
"not"
|
||||
] @keyword.operator
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"def"
|
||||
"undef"
|
||||
] @keyword.function
|
||||
|
||||
(method
|
||||
"end" @keyword.function)
|
||||
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"case"
|
||||
"else"
|
||||
"elsif"
|
||||
"if"
|
||||
"unless"
|
||||
"when"
|
||||
"then"
|
||||
] @keyword.conditional
|
||||
|
||||
(if
|
||||
"end" @keyword.conditional)
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"for"
|
||||
"until"
|
||||
"while"
|
||||
"break"
|
||||
"redo"
|
||||
"retry"
|
||||
"next"
|
||||
] @keyword.repeat
|
||||
|
||||
;; #ebda8c #000000 0 0 0 1
|
||||
(constant) @constant
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
"rescue"
|
||||
"ensure"
|
||||
] @keyword.exception
|
||||
|
||||
;; #aad84c #000000 0 0 0 1
|
||||
"defined?" @function
|
||||
|
||||
;; #aad84c #000000 0 0 0 1
|
||||
(call
|
||||
receiver: (constant)? @type
|
||||
method: [
|
||||
(identifier)
|
||||
(constant)
|
||||
;; #ff5689 #000000 0 0 0 1
|
||||
] @function.call)
|
||||
|
||||
(alias
|
||||
(identifier) @function)
|
||||
|
||||
(setter
|
||||
(identifier) @function)
|
||||
|
||||
(method
|
||||
name: [
|
||||
(identifier) @function
|
||||
(constant) @type
|
||||
])
|
||||
|
||||
(singleton_method
|
||||
name: [
|
||||
(identifier) @function
|
||||
(constant) @type
|
||||
])
|
||||
|
||||
(class
|
||||
name: (constant) @type)
|
||||
|
||||
(module
|
||||
name: (constant) @type)
|
||||
|
||||
(superclass
|
||||
(constant) @type)
|
||||
|
||||
;; #ffffff #000000 0 0 0 1
|
||||
[
|
||||
(class_variable)
|
||||
(instance_variable)
|
||||
] @variable.member
|
||||
|
||||
((identifier) @keyword.modifier
|
||||
(#match? @keyword.modifier "^(private|protected|public)$" ))
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
(program
|
||||
(call
|
||||
(identifier) @keyword.import)
|
||||
(#match? @keyword.import "^(require|require_relative|load)$"))
|
||||
|
||||
;; #fbb152 #000000 0 0 0 2
|
||||
((identifier) @constant.builtin
|
||||
(#match? @constant.builtin "^(__callee__|__dir__|__id__|__method__|__send__|__ENCODING__|__FILE__|__LINE__)$" ))
|
||||
|
||||
;; #aad84c #000000 0 0 0 1
|
||||
((identifier) @function.builtin
|
||||
(#match? @function.builtin "^(attr_reader|attr_writer|attr_accessor|module_function)$" ))
|
||||
|
||||
((call
|
||||
!receiver
|
||||
method: (identifier) @function.builtin)
|
||||
(#match? @function.builtin "^(include|extend|prepend|refine|using)"))
|
||||
|
||||
((identifier) @keyword.exception
|
||||
(#match? @keyword.exception "^(raise|fail|catch|throw)" ))
|
||||
|
||||
;; #ffffff #000000 0 0 0 1
|
||||
[
|
||||
(self)
|
||||
(super)
|
||||
] @variable.builtin
|
||||
|
||||
;; #ffffff #000000 0 0 0 1
|
||||
(method_parameters
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(lambda_parameters
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(block_parameters
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(splat_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(hash_splat_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(optional_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(destructured_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(block_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
(keyword_parameter
|
||||
(identifier) @variable.parameter)
|
||||
|
||||
;; #aad84c #000000 0 0 0 1
|
||||
[
|
||||
(string_content)
|
||||
(heredoc_content)
|
||||
"\""
|
||||
"`"
|
||||
] @string
|
||||
|
||||
;; #fbb152 #000000 0 0 0 1
|
||||
[
|
||||
(heredoc_beginning)
|
||||
(heredoc_end)
|
||||
] @label
|
||||
|
||||
;; #bd9ae6 #000000 0 0 0 2
|
||||
[
|
||||
(bare_symbol)
|
||||
(simple_symbol)
|
||||
(delimited_symbol)
|
||||
(hash_key_symbol)
|
||||
] @string.special.symbol
|
||||
|
||||
;; #e6a24c #000000 0 0 0 2
|
||||
(regex
|
||||
(string_content) @string.regexp)
|
||||
|
||||
;; #e6a24c #000000 0 0 0 2
|
||||
(escape_sequence) @string.escape
|
||||
|
||||
;; #ebda8c #000000 0 0 0 2
|
||||
(integer) @number
|
||||
|
||||
;; #ebda8c #000000 0 0 0 2
|
||||
(float) @number.float
|
||||
|
||||
;; #51eeba #000000 0 0 0 1
|
||||
(true) @boolean.true
|
||||
|
||||
;; #ee513a #000000 0 0 0 1
|
||||
(false) @boolean.false
|
||||
|
||||
;; #ee8757 #000000 0 0 0 1
|
||||
(nil) @constant.nil
|
||||
|
||||
;; #AAAAAA #000000 0 1 0 1
|
||||
(comment) @comment
|
||||
|
||||
(program
|
||||
(comment)+ @comment.documentation
|
||||
(class))
|
||||
|
||||
(module
|
||||
(comment)+ @comment.documentation
|
||||
(body_statement
|
||||
(class)))
|
||||
|
||||
(class
|
||||
(comment)+ @comment.documentation
|
||||
(body_statement
|
||||
(method)))
|
||||
|
||||
(body_statement
|
||||
(comment)+ @comment.documentation
|
||||
(method))
|
||||
|
||||
;; #ffffff #000000 0 0 0 1
|
||||
[
|
||||
"!"
|
||||
"="
|
||||
">>"
|
||||
"<<"
|
||||
">"
|
||||
"<"
|
||||
"**"
|
||||
"*"
|
||||
"/"
|
||||
"%"
|
||||
"+"
|
||||
"-"
|
||||
"&"
|
||||
"|"
|
||||
"^"
|
||||
"%="
|
||||
"+="
|
||||
"-="
|
||||
"*="
|
||||
"/="
|
||||
"=~"
|
||||
"!~"
|
||||
"?"
|
||||
":"
|
||||
] @operator
|
||||
|
||||
;; #ffffff #000000 0 1 0 1
|
||||
[
|
||||
"=="
|
||||
"==="
|
||||
"<=>"
|
||||
"=>"
|
||||
"->"
|
||||
">="
|
||||
"<="
|
||||
"||"
|
||||
"||="
|
||||
"&&="
|
||||
"&&"
|
||||
"!="
|
||||
".."
|
||||
"..."
|
||||
] @operator.ligature
|
||||
|
||||
;; #bd9ae6 #000000 0 0 0 1
|
||||
[
|
||||
","
|
||||
";"
|
||||
"."
|
||||
"&."
|
||||
"::"
|
||||
] @punctuation.delimiter
|
||||
|
||||
;; #bd9ae6 #000000 0 0 0 1
|
||||
|
||||
(regex
|
||||
"/" @punctuation.bracket)
|
||||
|
||||
(pair
|
||||
":" @punctuation.delimiter)
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"%w("
|
||||
"%i("
|
||||
] @punctuation.bracket
|
||||
|
||||
(block_parameters
|
||||
"|" @punctuation.bracket)
|
||||
|
||||
;; #e6a24c #000000 0 0 0 1
|
||||
(interpolation
|
||||
"#{" @punctuation.special
|
||||
"}" @punctuation.special)
|
||||
111
include/editor.h
Normal file
111
include/editor.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
#include "../libs/tree-sitter/lib/include/tree_sitter/api.h"
|
||||
#include "./rope.h"
|
||||
#include "./ui.h"
|
||||
#include "./utils.h"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <shared_mutex>
|
||||
|
||||
struct Highlight {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
uint32_t flags;
|
||||
uint8_t priority;
|
||||
};
|
||||
|
||||
struct Span {
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
Highlight *hl;
|
||||
|
||||
bool operator<(const Span &other) const { return start < other.start; }
|
||||
};
|
||||
|
||||
struct SpanCursor {
|
||||
const std::vector<Span> &spans;
|
||||
size_t index = 0;
|
||||
std::vector<Span *> active;
|
||||
|
||||
SpanCursor(const std::vector<Span> &s) : spans(s) {}
|
||||
Highlight *get_highlight(uint32_t byte_offset) {
|
||||
for (int i = active.size() - 1; i >= 0; i--)
|
||||
if (active[i]->end <= byte_offset)
|
||||
active.erase(active.begin() + i);
|
||||
while (index < spans.size() && spans[index].start <= byte_offset) {
|
||||
if (spans[index].end > byte_offset)
|
||||
active.push_back(const_cast<Span *>(&spans[index]));
|
||||
index++;
|
||||
}
|
||||
Highlight *best = nullptr;
|
||||
int max_prio = -1;
|
||||
for (auto *s : active) {
|
||||
if (s->hl->priority > max_prio) {
|
||||
max_prio = s->hl->priority;
|
||||
best = s->hl;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
void sync(uint32_t byte_offset) {
|
||||
active.clear();
|
||||
size_t left = 0, right = spans.size();
|
||||
while (left < right) {
|
||||
size_t mid = (left + right) / 2;
|
||||
if (spans[mid].start <= byte_offset)
|
||||
left = mid + 1;
|
||||
else
|
||||
right = mid;
|
||||
}
|
||||
index = left;
|
||||
while (left > 0) {
|
||||
left--;
|
||||
if (spans[left].end > byte_offset)
|
||||
active.push_back(const_cast<Span *>(&spans[left]));
|
||||
else if (byte_offset - spans[left].end > 1000)
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Editor {
|
||||
const char *filename; // Filename of the editor
|
||||
Knot *root; // A rope
|
||||
std::shared_mutex knot_mtx; // A mutex
|
||||
std::shared_mutex span_mtx; // A mutex
|
||||
Coord cursor; // position of the cursor
|
||||
uint32_t cursor_preffered; // preffered visual column
|
||||
Coord selection; // position of the selection
|
||||
bool selection_active; // true if there is a selection
|
||||
Coord position; // Position of the editor
|
||||
Coord size; // Size of the editor
|
||||
Coord scroll; // Position of the scroll
|
||||
TSTree *tree; // Tree-sitter tree
|
||||
TSParser *parser; // Tree-sitter parser
|
||||
TSQuery *query; // Tree-sitter query
|
||||
const TSLanguage *language; // Tree-sitter language
|
||||
Queue<TSInputEdit> edit_queue; // Tree-sitter edit queue
|
||||
std::vector<Highlight> query_map; // Tree-sitter query map
|
||||
int *folded; // folded lines indexed by line number - cached form of
|
||||
// folded_node
|
||||
std::vector<Span> spans;
|
||||
std::map<uint32_t, bool> folded_node; // maps content hash to fold state
|
||||
// - built by tree-sitter
|
||||
};
|
||||
|
||||
typedef struct TSLoad {
|
||||
Editor *editor;
|
||||
char *prev = nullptr;
|
||||
} TSLoad;
|
||||
|
||||
Editor *new_editor(const char *filename, Coord position, Coord size);
|
||||
void free_editor(Editor *editor);
|
||||
void render_editor(Editor *editor);
|
||||
void fold(Editor *editor, uint32_t start_line, uint32_t end_line);
|
||||
void scroll_up(Editor *editor, uint32_t lines);
|
||||
void scroll_down(Editor *editor, uint32_t lines);
|
||||
void cursor_up(Editor *editor, uint32_t number);
|
||||
void cursor_down(Editor *editor, uint32_t number);
|
||||
void cursor_left(Editor *editor, uint32_t number);
|
||||
void cursor_right(Editor *editor, uint32_t number);
|
||||
void ensure_scroll(Editor *editor);
|
||||
157
include/rope.h
Normal file
157
include/rope.h
Normal file
@@ -0,0 +1,157 @@
|
||||
#ifndef ROPE_HPP
|
||||
#define ROPE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#define MIN_CHUNK_SIZE 64 // 64 Bytes
|
||||
#define MAX_CHUNK_SIZE 1024 * 8 // 8192 Bytes (8 KiB)
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define DEPTH(n) ((n) ? (n)->depth : 0)
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#define PCRE_WORKSPACE_SIZE 512
|
||||
|
||||
// Rope node definition
|
||||
typedef struct Knot {
|
||||
Knot *left;
|
||||
Knot *right;
|
||||
uint8_t depth;
|
||||
uint32_t chunk_size;
|
||||
uint32_t line_count;
|
||||
uint32_t char_count;
|
||||
char data[];
|
||||
} Knot;
|
||||
|
||||
typedef struct LineIterator {
|
||||
Knot *node;
|
||||
uint8_t top;
|
||||
uint32_t offset;
|
||||
uint32_t line;
|
||||
Knot *stack[64];
|
||||
} LineIterator;
|
||||
|
||||
typedef struct LeafIterator {
|
||||
Knot *node;
|
||||
uint8_t top;
|
||||
uint32_t offset;
|
||||
Knot *stack[64];
|
||||
} LeafIterator;
|
||||
|
||||
typedef struct ByteIterator {
|
||||
LeafIterator *it;
|
||||
uint32_t offset_l;
|
||||
uint32_t offset_g;
|
||||
uint32_t char_count;
|
||||
char *data;
|
||||
} ByteIterator;
|
||||
|
||||
// Rope operations
|
||||
|
||||
// Takes lengt of string to be converted
|
||||
// to rope and returns a suitable chunk size
|
||||
// but rope should work with any positive chunk size
|
||||
uint32_t optimal_chunk_size(uint64_t length);
|
||||
|
||||
// Takes a string (no need for null termination) and returns a rope
|
||||
// len is the length of the string, and chunk size is the size of each chunk
|
||||
// load does not free or consume the string.
|
||||
// and the str can be freed after load has been run.
|
||||
Knot *load(char *str, uint32_t len, uint32_t chunk_size);
|
||||
|
||||
// Balances the rope and returns the root
|
||||
// n is no longer valid / do not free
|
||||
// As rope is balanced by other functions
|
||||
// this is not to be used directly
|
||||
Knot *balance(Knot *n);
|
||||
|
||||
// Concatenates two ropes and returns the joined root
|
||||
// Balances the ropes too, if needed
|
||||
// left and right are no longer valid / do not free
|
||||
// ! left and right should have the same chunk size !
|
||||
Knot *concat(Knot *left, Knot *right);
|
||||
|
||||
// Used to insert text into the rope
|
||||
// node (the rope being inserted into) is no longer valid after call
|
||||
// instead use return value as the new node
|
||||
// offset is the position of the insertion relative to the start of the rope
|
||||
// str is the string to be inserted (no need for null termination)
|
||||
// len is the length of the string
|
||||
Knot *insert(Knot *node, uint32_t offset, char *str, uint32_t len);
|
||||
|
||||
// Similar to insert but for deletion
|
||||
// node (the rope being deleted from) is no longer valid after call
|
||||
// instead use return value as the new node
|
||||
// offset is the position of the deletion relative to the start of the rope
|
||||
// len is the length of the deletion
|
||||
Knot *erase(Knot *node, uint32_t offset, uint32_t len);
|
||||
|
||||
// Used to read a string from the rope
|
||||
// root is the rope to be read from
|
||||
// offset is the position of the read relative to the start of the rope
|
||||
// len is the length of the read
|
||||
// returns a null terminated string, should be freed by the caller
|
||||
char *read(Knot *root, uint32_t offset, uint32_t len);
|
||||
|
||||
// Used to split the rope into left and right ropes
|
||||
// node is the rope to be split (it is no longer valid after call / do not free)
|
||||
// offset is the position of the split relative to the start of the rope
|
||||
// left and right are pointers set to the root of that side of the split
|
||||
void split(Knot *node, uint32_t offset, Knot **left, Knot **right);
|
||||
|
||||
// Used to convert a byte offset to a line number that contains that byte
|
||||
uint32_t byte_to_line(Knot *node, uint32_t offset);
|
||||
|
||||
// Used to convert a line number to a byte offset (start of the line)
|
||||
// also sets out_len to the length of the line
|
||||
uint32_t line_to_byte(Knot *node, uint32_t line, uint32_t *out_len);
|
||||
|
||||
// Used to start a line iterator from the start_line number
|
||||
// root is the root of the rope
|
||||
// returned iterator must be freed after iteration is done
|
||||
LineIterator *begin_l_iter(Knot *root, uint32_t start_line);
|
||||
|
||||
// Each subsequent call returns the next line as a null terminated string
|
||||
// `it` is the iterator returned from begin_l_iter
|
||||
// After getting the necessary lines free the iterator (no need to go upto the
|
||||
// end) returns null if there are no more lines All return strings `must` be
|
||||
// freed by the caller
|
||||
char *next_line(LineIterator *it);
|
||||
|
||||
// Used to start an iterator over leaf data
|
||||
// root is the root of the rope
|
||||
// the caller must free the iterator after use
|
||||
LeafIterator *begin_k_iter(Knot *root);
|
||||
|
||||
// Returns the next leaf data as a null terminated string
|
||||
// `it` is the iterator returned from begin_k_iter
|
||||
// ! Strings returned must never be freed by the caller !
|
||||
// to mutate the string a copy must be made
|
||||
char *next_leaf(LeafIterator *it);
|
||||
|
||||
// Used to start an iterator over byte data (one byte at a time)
|
||||
// Uses leaf iterator internally
|
||||
// root is the root of the rope, the caller must free the iterator after use
|
||||
ByteIterator *begin_b_iter(Knot *root);
|
||||
|
||||
// Returns the next byte from the iterator
|
||||
// Returns '\0' if there are no more bytes left
|
||||
// `it` is the iterator returned from begin_b_iter
|
||||
char next_byte(ByteIterator *it);
|
||||
|
||||
// Used to search for a pattern in the rope
|
||||
// Pattern is a null terminated string representing a regular expression (DFA
|
||||
// compliant) I.e some forms of backtracking etc. are not supported
|
||||
// root is the root of the rope to be searched
|
||||
// Returns a vector of pairs of start and length offsets (in bytes)
|
||||
std::vector<std::pair<size_t, size_t>> search_rope(Knot *root,
|
||||
const char *pattern);
|
||||
|
||||
// Helper function to free the rope
|
||||
// root is the root of the rope
|
||||
// the root is no longer valid after call
|
||||
// This must be called only once when the rope is no longer needed
|
||||
void free_rope(Knot *root);
|
||||
|
||||
#endif // ROPE_HPP
|
||||
7
include/ts.h
Normal file
7
include/ts.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "./editor.h"
|
||||
|
||||
#define HEX(s) (static_cast<uint32_t>(std::stoul(s, nullptr, 16)))
|
||||
|
||||
TSQuery *load_query(const char *query_path, Editor *editor);
|
||||
void ts_collect_spans(Editor *editor);
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
@@ -41,6 +43,10 @@
|
||||
#define CNTRL_ALT 3
|
||||
#define SHIFT 4
|
||||
|
||||
const char VS16_BYTE_A = '\xEF';
|
||||
const char VS16_BYTE_B = '\xB8';
|
||||
const char VS16_BYTE_C = '\x8F';
|
||||
|
||||
enum CellFlags : uint8_t {
|
||||
CF_NONE = 0,
|
||||
CF_ITALIC = 1 << 0,
|
||||
@@ -49,12 +55,17 @@ enum CellFlags : uint8_t {
|
||||
};
|
||||
|
||||
struct ScreenCell {
|
||||
std::string utf8; // empty => no content
|
||||
std::string utf8 = std::string(""); // empty => no content
|
||||
uint32_t fg = 0;
|
||||
uint32_t bg = 0;
|
||||
uint8_t flags = CF_NONE;
|
||||
};
|
||||
|
||||
struct Coord {
|
||||
uint32_t row;
|
||||
uint32_t col;
|
||||
};
|
||||
|
||||
struct KeyEvent {
|
||||
uint8_t key_type;
|
||||
|
||||
@@ -71,32 +82,25 @@ struct KeyEvent {
|
||||
uint8_t mouse_modifier;
|
||||
};
|
||||
|
||||
extern int rows, cols;
|
||||
extern uint32_t rows, cols;
|
||||
extern std::vector<ScreenCell> screen; // size rows*cols
|
||||
extern std::vector<ScreenCell> old_screen;
|
||||
extern std::mutex screen_mutex;
|
||||
extern std::atomic<bool> running;
|
||||
|
||||
struct coords {
|
||||
int row;
|
||||
int col;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void get_terminal_size();
|
||||
void die(const char *s);
|
||||
void enable_raw_mode();
|
||||
void disable_raw_mode();
|
||||
void start_screen();
|
||||
Coord start_screen();
|
||||
void end_screen();
|
||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||
uint8_t flags);
|
||||
void set_cursor(int row, int col, bool show_cursor_param);
|
||||
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||
uint32_t bg, uint8_t flags);
|
||||
void set_cursor(int row, int col, int show_cursor_param);
|
||||
void render();
|
||||
coords get_size();
|
||||
|
||||
int real_width(std::string str);
|
||||
Coord get_size();
|
||||
|
||||
int read_input(char *buf, size_t buflen);
|
||||
KeyEvent read_key_nonblock();
|
||||
KeyEvent read_key();
|
||||
}
|
||||
|
||||
int display_width(const char *str);
|
||||
32
include/utils.h
Normal file
32
include/utils.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
template <typename T> struct Queue {
|
||||
std::queue<T> q;
|
||||
std::mutex m;
|
||||
|
||||
void push(T val) {
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
q.push(val);
|
||||
}
|
||||
bool pop(T &val) {
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
if (q.empty())
|
||||
return false;
|
||||
val = q.front();
|
||||
q.pop();
|
||||
return true;
|
||||
}
|
||||
bool empty() {
|
||||
std::lock_guard<std::mutex> lock(m);
|
||||
return q.empty();
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t grapheme_strlen(const char *s);
|
||||
uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit);
|
||||
uint32_t get_bytes_from_visual_col(const char *line,
|
||||
uint32_t target_visual_col);
|
||||
void log(const char *fmt, ...);
|
||||
std::string get_exe_dir();
|
||||
508
src/editor.cc
Normal file
508
src/editor.cc
Normal file
@@ -0,0 +1,508 @@
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
}
|
||||
#include "../include/editor.h"
|
||||
#include "../include/ts.h"
|
||||
#include "../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
char *load_file(const char *path, uint32_t *out_len) {
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open())
|
||||
return nullptr;
|
||||
std::streamsize len = file.tellg();
|
||||
if (len < 0 || (std::uint32_t)len > 0xFFFFFFFF)
|
||||
return nullptr;
|
||||
file.seekg(0, std::ios::beg);
|
||||
char *buf = (char *)malloc(static_cast<std::uint32_t>(len));
|
||||
if (!buf)
|
||||
return nullptr;
|
||||
if (file.read(buf, len)) {
|
||||
*out_len = static_cast<uint32_t>(len);
|
||||
return buf;
|
||||
} else {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Editor *new_editor(const char *filename, Coord position, Coord size) {
|
||||
Editor *editor = new Editor();
|
||||
if (!editor)
|
||||
return nullptr;
|
||||
uint32_t len = 0;
|
||||
char *str = load_file(filename, &len);
|
||||
if (!str)
|
||||
return nullptr;
|
||||
editor->filename = filename;
|
||||
editor->position = position;
|
||||
editor->size = size;
|
||||
editor->tree = nullptr;
|
||||
editor->cursor = {0, 0};
|
||||
editor->selection_active = false;
|
||||
editor->selection = {0, 0};
|
||||
editor->scroll = {0, 0};
|
||||
editor->root = load(str, len, optimal_chunk_size(len));
|
||||
editor->folded = (int *)calloc(editor->root->line_count + 2, sizeof(int));
|
||||
std::string query = get_exe_dir() + "/../grammar/ruby.scm";
|
||||
if (!(len > (1024 * 1024))) {
|
||||
editor->parser = ts_parser_new();
|
||||
editor->language = tree_sitter_ruby();
|
||||
ts_parser_set_language(editor->parser, editor->language);
|
||||
editor->query = load_query(query.c_str(), editor);
|
||||
}
|
||||
free(str);
|
||||
return editor;
|
||||
}
|
||||
|
||||
void free_editor(Editor *editor) {
|
||||
if (editor->folded)
|
||||
free(editor->folded);
|
||||
ts_parser_delete(editor->parser);
|
||||
if (editor->tree)
|
||||
ts_tree_delete(editor->tree);
|
||||
if (editor->query)
|
||||
ts_query_delete(editor->query);
|
||||
free_rope(editor->root);
|
||||
delete editor;
|
||||
}
|
||||
|
||||
void scroll_up(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
uint32_t count = 0;
|
||||
uint32_t visible_lines_checked = 0;
|
||||
int32_t current_check_row = editor->scroll.row;
|
||||
while (visible_lines_checked < number + 1 && current_check_row >= 0) {
|
||||
if (editor->folded[current_check_row] != 1)
|
||||
visible_lines_checked++;
|
||||
count++;
|
||||
current_check_row--;
|
||||
}
|
||||
if (current_check_row < 0)
|
||||
count = editor->scroll.row;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row - count + 1);
|
||||
std::vector<std::pair<uint32_t, uint32_t>> stack;
|
||||
stack.reserve(count);
|
||||
uint32_t lines_iterated = 0;
|
||||
uint32_t start_row = editor->scroll.row - count + 1;
|
||||
while (lines_iterated < count) {
|
||||
char *line_content = next_line(it);
|
||||
uint32_t current_idx = start_row + lines_iterated;
|
||||
int fold_state = editor->folded[current_idx];
|
||||
if (fold_state == 2) {
|
||||
stack.push_back({1, current_idx});
|
||||
} else if (fold_state == 0) {
|
||||
uint32_t len =
|
||||
(line_content != nullptr) ? grapheme_strlen(line_content) : 0;
|
||||
stack.push_back({len, current_idx});
|
||||
}
|
||||
if (line_content)
|
||||
free(line_content);
|
||||
lines_iterated++;
|
||||
}
|
||||
uint32_t ln = 0;
|
||||
uint32_t wrap_limit = editor->size.col;
|
||||
for (int i = stack.size() - 1; i >= 0; i--) {
|
||||
uint32_t len = stack[i].first;
|
||||
uint32_t row_idx = stack[i].second;
|
||||
uint32_t segments =
|
||||
(wrap_limit > 0 && len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1;
|
||||
if (len == 0)
|
||||
segments = 1;
|
||||
for (int seg = (row_idx == editor->scroll.row)
|
||||
? editor->scroll.col / wrap_limit
|
||||
: segments - 1;
|
||||
seg >= 0; seg--) {
|
||||
ln++;
|
||||
if (ln == number + 1) {
|
||||
editor->scroll.row = row_idx;
|
||||
editor->scroll.col = seg * wrap_limit;
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ln < number + 1) {
|
||||
editor->scroll.row = 0;
|
||||
editor->scroll.col = 0;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
|
||||
void scroll_down(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row);
|
||||
uint32_t current_row = editor->scroll.row;
|
||||
uint32_t lines_moved = 0;
|
||||
uint32_t wrap_limit = editor->size.col;
|
||||
if (wrap_limit == 0)
|
||||
wrap_limit = 1;
|
||||
while (lines_moved < number) {
|
||||
char *line_content = next_line(it);
|
||||
if (line_content == nullptr)
|
||||
break;
|
||||
int fold_state = editor->folded[current_row];
|
||||
if (fold_state == 1) {
|
||||
free(line_content);
|
||||
current_row++;
|
||||
continue;
|
||||
}
|
||||
uint32_t segments = 1;
|
||||
if (fold_state == 2) {
|
||||
segments = 1;
|
||||
} else {
|
||||
uint32_t len = grapheme_strlen(line_content);
|
||||
segments = (len > 0) ? (len + wrap_limit - 1) / wrap_limit : 1;
|
||||
}
|
||||
uint32_t start_seg = (current_row == editor->scroll.row)
|
||||
? (editor->scroll.col / wrap_limit)
|
||||
: 0;
|
||||
for (uint32_t seg = start_seg; seg < segments; seg++) {
|
||||
if (current_row == editor->scroll.row && seg == start_seg)
|
||||
continue;
|
||||
lines_moved++;
|
||||
if (lines_moved == number) {
|
||||
editor->scroll.row = current_row;
|
||||
editor->scroll.col = seg * wrap_limit;
|
||||
free(line_content);
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
free(line_content);
|
||||
current_row++;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
|
||||
void cursor_down(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line_content = next_line(it);
|
||||
if (line_content == nullptr)
|
||||
return;
|
||||
uint32_t visual_col =
|
||||
get_visual_col_from_bytes(line_content, editor->cursor.col);
|
||||
do {
|
||||
free(line_content);
|
||||
line_content = next_line(it);
|
||||
editor->cursor.row += 1;
|
||||
if (editor->cursor.row >= editor->root->line_count) {
|
||||
editor->cursor.row = editor->root->line_count - 1;
|
||||
break;
|
||||
};
|
||||
if (editor->folded && editor->folded[editor->cursor.row] != 0)
|
||||
number++;
|
||||
} while (--number > 0);
|
||||
free(it);
|
||||
if (line_content == nullptr)
|
||||
return;
|
||||
editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col);
|
||||
free(line_content);
|
||||
}
|
||||
|
||||
void cursor_up(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line_content = next_line(it);
|
||||
if (!line_content) {
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
uint32_t visual_col =
|
||||
get_visual_col_from_bytes(line_content, editor->cursor.col);
|
||||
free(line_content);
|
||||
while (number > 0 && editor->cursor.row > 0) {
|
||||
editor->cursor.row--;
|
||||
if (editor->folded && editor->folded[editor->cursor.row] != 0)
|
||||
continue;
|
||||
number--;
|
||||
}
|
||||
free(it);
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line_content = next_line(it);
|
||||
if (!line_content) {
|
||||
free(it);
|
||||
return;
|
||||
}
|
||||
editor->cursor.col = get_bytes_from_visual_col(line_content, visual_col);
|
||||
free(line_content);
|
||||
free(it);
|
||||
}
|
||||
|
||||
void cursor_right(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
return;
|
||||
uint32_t line_len = strlen(line);
|
||||
if (line[line_len - 1] == '\n')
|
||||
line[--line_len] = '\0';
|
||||
while (number > 0) {
|
||||
if (editor->cursor.col >= line_len) {
|
||||
free(line);
|
||||
line = nullptr;
|
||||
uint32_t next_row = editor->cursor.row + 1;
|
||||
while (editor->folded && next_row < editor->root->line_count &&
|
||||
editor->folded[next_row] != 0)
|
||||
next_row++;
|
||||
if (next_row >= editor->root->line_count) {
|
||||
editor->cursor.col = line_len;
|
||||
break;
|
||||
}
|
||||
editor->cursor.row = next_row;
|
||||
editor->cursor.col = 0;
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
break;
|
||||
line_len = strlen(line);
|
||||
if (line[line_len - 1] == '\n')
|
||||
line[--line_len] = '\0';
|
||||
} else {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(
|
||||
line + editor->cursor.col, line_len - editor->cursor.col);
|
||||
if (inc == 0)
|
||||
break;
|
||||
editor->cursor.col += inc;
|
||||
}
|
||||
number--;
|
||||
}
|
||||
if (line)
|
||||
free(line);
|
||||
}
|
||||
|
||||
void cursor_left(Editor *editor, uint32_t number) {
|
||||
if (!editor || !editor->root || number == 0)
|
||||
return;
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
char *line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
return;
|
||||
uint32_t len = strlen(line);
|
||||
if (line[len - 1] == '\n')
|
||||
line[--len] = '\0';
|
||||
while (number > 0) {
|
||||
if (editor->cursor.col == 0) {
|
||||
free(line);
|
||||
line = nullptr;
|
||||
if (editor->cursor.row == 0)
|
||||
break;
|
||||
int32_t prev_row = editor->cursor.row - 1;
|
||||
while (editor->folded && prev_row >= 0 && editor->folded[prev_row] != 0)
|
||||
prev_row--;
|
||||
if (prev_row < 0)
|
||||
break;
|
||||
editor->cursor.row = prev_row;
|
||||
it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
line = next_line(it);
|
||||
free(it);
|
||||
if (!line)
|
||||
break;
|
||||
uint32_t len = strlen(line);
|
||||
if (line[len - 1] == '\n')
|
||||
line[--len] = '\0';
|
||||
editor->cursor.col = len;
|
||||
} else {
|
||||
uint32_t col = editor->cursor.col;
|
||||
uint32_t new_col = 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t len = strlen(line);
|
||||
while (new_col < col) {
|
||||
uint32_t inc =
|
||||
grapheme_next_character_break_utf8(line + new_col, len - new_col);
|
||||
if (new_col + inc >= col)
|
||||
break;
|
||||
new_col += inc;
|
||||
visual_col++;
|
||||
}
|
||||
editor->cursor.col = new_col;
|
||||
}
|
||||
number--;
|
||||
}
|
||||
if (line)
|
||||
free(line);
|
||||
}
|
||||
|
||||
void ensure_scroll(Editor *editor) {
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->scroll.row);
|
||||
uint32_t rendered_lines = 0;
|
||||
uint32_t line_index = editor->scroll.row;
|
||||
char *line_content = nullptr;
|
||||
while (rendered_lines <= editor->size.row) {
|
||||
line_content = next_line(it);
|
||||
if (!line_content)
|
||||
break;
|
||||
char *line_content_t = line_content;
|
||||
if (rendered_lines == 0)
|
||||
line_content_t = line_content + editor->scroll.col;
|
||||
uint32_t len = grapheme_strlen(line_content_t);
|
||||
free(line_content);
|
||||
uint32_t wrapped_lines = (len + editor->size.col - 1) / editor->size.col;
|
||||
rendered_lines += wrapped_lines;
|
||||
line_index++;
|
||||
}
|
||||
line_index -= 2;
|
||||
free(it);
|
||||
if (editor->cursor.row >= line_index && line_content)
|
||||
scroll_down(editor, editor->cursor.row - line_index);
|
||||
if (editor->cursor.row < editor->scroll.row)
|
||||
scroll_up(editor, editor->scroll.row - editor->cursor.row);
|
||||
}
|
||||
|
||||
void fold(Editor *editor, uint32_t start_line, uint32_t end_line) {
|
||||
if (!editor)
|
||||
return;
|
||||
if (!editor->folded) {
|
||||
editor->folded = (int *)calloc(editor->root->line_count + 2, sizeof(int));
|
||||
if (!editor->folded)
|
||||
return;
|
||||
}
|
||||
for (uint32_t i = start_line; i <= end_line && i < editor->size.row; i++)
|
||||
editor->folded[i] = 1;
|
||||
editor->folded[start_line] = 2;
|
||||
}
|
||||
|
||||
void update_render_fold_marker(uint32_t row, uint32_t cols) {
|
||||
const char *marker = "... folded ...";
|
||||
uint32_t len = strlen(marker);
|
||||
uint32_t i = 0;
|
||||
for (; i < len && i < cols; i++)
|
||||
update(row, i, (char[2]){marker[i], 0}, 0xc6c6c6, 0, 0);
|
||||
for (; i < cols; i++)
|
||||
update(row, i, " ", 0xc6c6c6, 0, 0);
|
||||
}
|
||||
|
||||
void render_editor(Editor *editor) {
|
||||
uint32_t screen_rows = editor->size.row;
|
||||
uint32_t screen_cols = editor->size.col;
|
||||
uint32_t line_index = editor->scroll.row;
|
||||
SpanCursor span_cursor(editor->spans);
|
||||
std::shared_lock knot_lock(editor->knot_mtx);
|
||||
std::shared_lock span_lock(editor->span_mtx);
|
||||
LineIterator *it = begin_l_iter(editor->root, line_index);
|
||||
if (!it)
|
||||
return;
|
||||
uint32_t rendered_rows = 0;
|
||||
uint32_t global_byte_offset = line_to_byte(editor->root, line_index, nullptr);
|
||||
span_cursor.sync(global_byte_offset);
|
||||
while (rendered_rows < screen_rows) {
|
||||
if (editor->folded && editor->folded[line_index]) {
|
||||
if (editor->folded[line_index] == 2) {
|
||||
update_render_fold_marker(rendered_rows, screen_cols);
|
||||
rendered_rows++;
|
||||
}
|
||||
do {
|
||||
char *line = next_line(it);
|
||||
if (!line)
|
||||
break;
|
||||
global_byte_offset += strlen(line);
|
||||
if (line[strlen(line) - 1] == '\n')
|
||||
global_byte_offset--;
|
||||
global_byte_offset++;
|
||||
free(line);
|
||||
line_index++;
|
||||
} while (line_index < editor->size.row &&
|
||||
editor->folded[line_index] == 1);
|
||||
continue;
|
||||
}
|
||||
char *line = next_line(it);
|
||||
if (!line)
|
||||
break;
|
||||
uint32_t line_len = strlen(line);
|
||||
if (line_len > 0 && line[line_len - 1] == '\n')
|
||||
line_len--;
|
||||
uint32_t current_byte_offset = 0;
|
||||
if (rendered_rows == 0 && editor->scroll.col > 0) {
|
||||
uint32_t skipped_cols = 0;
|
||||
while (skipped_cols < editor->scroll.col &&
|
||||
current_byte_offset < line_len) {
|
||||
uint32_t len = grapheme_next_character_break_utf8(
|
||||
line + current_byte_offset, line_len - current_byte_offset);
|
||||
current_byte_offset += len;
|
||||
skipped_cols++;
|
||||
}
|
||||
}
|
||||
while (current_byte_offset < line_len && rendered_rows < screen_rows) {
|
||||
uint32_t slice_byte_len = 0;
|
||||
uint32_t slice_visual_cols = 0;
|
||||
uint32_t probe_offset = current_byte_offset;
|
||||
while (slice_visual_cols < screen_cols && probe_offset < line_len) {
|
||||
uint32_t len = grapheme_next_character_break_utf8(
|
||||
line + probe_offset, line_len - probe_offset);
|
||||
slice_byte_len += len;
|
||||
probe_offset += len;
|
||||
slice_visual_cols++;
|
||||
}
|
||||
uint32_t col = 0;
|
||||
uint32_t local_render_offset = 0;
|
||||
while (local_render_offset < slice_byte_len) {
|
||||
if (line_index == editor->cursor.row &&
|
||||
editor->cursor.col == (current_byte_offset + local_render_offset))
|
||||
set_cursor(editor->position.row + rendered_rows,
|
||||
editor->position.col + col, 1);
|
||||
uint32_t absolute_byte_pos =
|
||||
global_byte_offset + current_byte_offset + local_render_offset;
|
||||
Highlight *hl = span_cursor.get_highlight(absolute_byte_pos);
|
||||
uint32_t fg = hl ? hl->fg : 0xFFFFFF;
|
||||
uint32_t bg = hl ? hl->bg : 0;
|
||||
uint8_t fl = hl ? hl->flags : 0;
|
||||
uint32_t cluster_len = grapheme_next_character_break_utf8(
|
||||
line + current_byte_offset + local_render_offset,
|
||||
slice_byte_len - local_render_offset);
|
||||
if (cluster_len == 0)
|
||||
cluster_len = 1;
|
||||
std::string cluster(line + current_byte_offset + local_render_offset,
|
||||
cluster_len);
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
cluster.c_str(), fg, bg, fl);
|
||||
local_render_offset += cluster_len;
|
||||
col++;
|
||||
}
|
||||
if (line_index == editor->cursor.row &&
|
||||
editor->cursor.col == (current_byte_offset + slice_byte_len))
|
||||
set_cursor(editor->position.row + rendered_rows,
|
||||
editor->position.col + col, 1);
|
||||
while (col < screen_cols) {
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
col++;
|
||||
}
|
||||
rendered_rows++;
|
||||
current_byte_offset += slice_byte_len;
|
||||
}
|
||||
if (line_len == 0 ||
|
||||
(current_byte_offset >= line_len && rendered_rows == 0)) {
|
||||
if (editor->cursor.row == line_index)
|
||||
set_cursor(editor->position.row + rendered_rows, editor->position.col,
|
||||
1);
|
||||
uint32_t col = 0;
|
||||
while (col < screen_cols) {
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
col++;
|
||||
}
|
||||
rendered_rows++;
|
||||
}
|
||||
global_byte_offset += line_len + 1;
|
||||
line_index++;
|
||||
free(line);
|
||||
}
|
||||
while (rendered_rows < screen_rows) {
|
||||
for (uint32_t col = 0; col < screen_cols; col++)
|
||||
update(editor->position.row + rendered_rows, editor->position.col + col,
|
||||
" ", 0xFFFFFF, 0, 0);
|
||||
rendered_rows++;
|
||||
}
|
||||
free(it);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../headers/header.hpp"
|
||||
#include "../include/ui.h"
|
||||
|
||||
int read_input(char *buf, size_t buflen) {
|
||||
size_t i = 0;
|
||||
@@ -115,11 +115,9 @@ KeyEvent read_key_nonblock() {
|
||||
}
|
||||
|
||||
KeyEvent read_key() {
|
||||
KeyEvent ret;
|
||||
while (1) {
|
||||
ret = read_key_nonblock();
|
||||
while (true) {
|
||||
KeyEvent ret = read_key_nonblock();
|
||||
if (ret.key_type != KEY_NONE)
|
||||
return ret;
|
||||
usleep(2500);
|
||||
}
|
||||
}
|
||||
199
src/main.cc
Normal file
199
src/main.cc
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "../include/editor.h"
|
||||
#include "../include/ts.h"
|
||||
#include "../include/ui.h"
|
||||
#include "../libs/tree-sitter/lib/include/tree_sitter/api.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sys/ioctl.h>
|
||||
#include <thread>
|
||||
|
||||
std::atomic<bool> running{true};
|
||||
Queue<KeyEvent> event_queue;
|
||||
|
||||
std::atomic<uint64_t> render_frames{0};
|
||||
std::atomic<uint64_t> worker_frames{0};
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
void background_worker(Editor *editor) {
|
||||
while (running) {
|
||||
worker_frames++;
|
||||
|
||||
ts_collect_spans(editor);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
}
|
||||
|
||||
void input_listener() {
|
||||
while (running) {
|
||||
KeyEvent event = read_key();
|
||||
if (event.key_type == KEY_CHAR && event.c == CTRL('q'))
|
||||
running = false;
|
||||
event_queue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_DOWN)
|
||||
cursor_down(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_UP)
|
||||
cursor_up(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_LEFT)
|
||||
cursor_left(editor, 1);
|
||||
if (event.key_type == KEY_SPECIAL && event.special_key == KEY_RIGHT)
|
||||
cursor_right(editor, 1);
|
||||
if (event.key_type == KEY_CHAR &&
|
||||
((event.c >= 'a' && event.c <= 'z') ||
|
||||
(event.c >= 'A' && event.c <= 'Z') ||
|
||||
(event.c >= '0' && event.c <= '9') || event.c == ' ' || event.c == '!' ||
|
||||
event.c == '@' || event.c == '#' || event.c == '$' || event.c == '%' ||
|
||||
event.c == '^' || event.c == '&' || event.c == '*' || event.c == '(' ||
|
||||
event.c == ')' || event.c == '-' || event.c == '_' || event.c == '=' ||
|
||||
event.c == '+' || event.c == '[' || event.c == ']' || event.c == '{' ||
|
||||
event.c == '}' || event.c == '\\' || event.c == '|' || event.c == ';' ||
|
||||
event.c == ':' || event.c == '\'' || event.c == '"' || event.c == ',' ||
|
||||
event.c == '.' || event.c == '<' || event.c == '>' || event.c == '/' ||
|
||||
event.c == '?' || event.c == '`' || event.c == '~')) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, &event.c, 1);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 1,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 1},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 1);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && event.c == '\t') {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, (char *)" ", 2);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 2,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 2},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 2);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && (event.c == '\n' || event.c == '\r')) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = insert(editor->root, pos, (char *)"\n", 1);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = pos,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = pos + 1,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col + 1},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
cursor_right(editor, 1);
|
||||
}
|
||||
if (event.key_type == KEY_CHAR && event.c == 0x7F) {
|
||||
std::shared_lock lock_1(editor->knot_mtx);
|
||||
uint32_t pos = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
TSPoint old_point = {editor->cursor.row, editor->cursor.col};
|
||||
cursor_left(editor, 1);
|
||||
uint32_t start = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
lock_1.unlock();
|
||||
std::unique_lock lock_2(editor->knot_mtx);
|
||||
editor->root = erase(editor->root, start, pos - start);
|
||||
lock_2.unlock();
|
||||
if (editor->tree) {
|
||||
TSInputEdit edit = {
|
||||
.start_byte = start,
|
||||
.old_end_byte = pos,
|
||||
.new_end_byte = start,
|
||||
.start_point = {editor->cursor.row, editor->cursor.col},
|
||||
.old_end_point = old_point,
|
||||
.new_end_point = {editor->cursor.row, editor->cursor.col},
|
||||
};
|
||||
editor->edit_queue.push(edit);
|
||||
}
|
||||
}
|
||||
ensure_scroll(editor);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
Coord screen = start_screen();
|
||||
const char *filename = (argc > 1) ? argv[1] : "ts.cpp";
|
||||
|
||||
Editor *editor = new_editor(filename, {0, 0}, {screen.row, screen.col});
|
||||
if (!editor) {
|
||||
end_screen();
|
||||
fprintf(stderr, "Failed to load editor\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
start_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
std::thread input_thread(input_listener);
|
||||
std::thread work_thread(background_worker, editor);
|
||||
|
||||
while (running) {
|
||||
render_frames++;
|
||||
|
||||
KeyEvent event;
|
||||
while (event_queue.pop(event))
|
||||
handle_editor_event(editor, event);
|
||||
|
||||
render_editor(editor);
|
||||
render();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
|
||||
input_thread.detach();
|
||||
|
||||
if (work_thread.joinable())
|
||||
work_thread.join();
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
double seconds = std::chrono::duration<double>(end_time - start_time).count();
|
||||
|
||||
double render_fps = render_frames / seconds;
|
||||
double worker_fps = worker_frames / seconds;
|
||||
|
||||
end_screen();
|
||||
|
||||
std::cout << "\n======= Performance Summary =======\n";
|
||||
std::cout << "Runtime: " << seconds << "s\n";
|
||||
std::cout << "Render loop FPS: " << render_fps << "Hz\n";
|
||||
std::cout << "Worker loop FPS: " << worker_fps << "Hz\n";
|
||||
std::cout << "===================================\n";
|
||||
|
||||
free_editor(editor);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
// includes
|
||||
#include "../../libs/libgrapheme/grapheme.h"
|
||||
#include "../../libs/unicode_width/unicode_width.h"
|
||||
#include "../headers/header.hpp"
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
#include "../libs/unicode_width/unicode_width.h"
|
||||
}
|
||||
#include "../include/ui.h"
|
||||
|
||||
struct termios orig_termios;
|
||||
termios orig_termios;
|
||||
|
||||
int rows, cols;
|
||||
bool show_cursor = false;
|
||||
uint32_t rows, cols;
|
||||
int show_cursor = 0;
|
||||
std::vector<ScreenCell> screen;
|
||||
std::vector<ScreenCell> old_screen;
|
||||
std::mutex screen_mutex;
|
||||
|
||||
int real_width(std::string str) {
|
||||
if (!str.size())
|
||||
int display_width(const char *str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
if (!strlen(str))
|
||||
return 0;
|
||||
const char *p = str.c_str();
|
||||
if (str[0] == '\t')
|
||||
return 4;
|
||||
unicode_width_state_t state;
|
||||
unicode_width_init(&state);
|
||||
int width = 0;
|
||||
for (size_t j = 0; j < str.size(); j++) {
|
||||
for (size_t j = 0; j < strlen(str); j++) {
|
||||
unsigned char c = str[j];
|
||||
if (c < 128) {
|
||||
int char_width = unicode_width_process(&state, c);
|
||||
@@ -28,7 +31,7 @@ int real_width(std::string str) {
|
||||
width += char_width;
|
||||
} else {
|
||||
uint_least32_t cp;
|
||||
size_t bytes = grapheme_decode_utf8(p + j, str.size() - j, &cp);
|
||||
size_t bytes = grapheme_decode_utf8(str + j, strlen(str) - j, &cp);
|
||||
if (bytes > 1) {
|
||||
int char_width = unicode_width_process(&state, cp);
|
||||
if (char_width > 0)
|
||||
@@ -37,6 +40,7 @@ int real_width(std::string str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
@@ -63,6 +67,7 @@ void enable_raw_mode() {
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
raw.c_lflag |= ISIG;
|
||||
raw.c_cc[VMIN] = 0;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
|
||||
@@ -74,7 +79,7 @@ void enable_raw_mode() {
|
||||
}
|
||||
|
||||
void disable_raw_mode() {
|
||||
std::string os = "\x1b[?1049l\x1b[?25h";
|
||||
std::string os = "\x1b[?1049l\x1b[2 q\x1b[?1002l\x1b[?25h";
|
||||
write(STDOUT_FILENO, os.c_str(), os.size());
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
|
||||
perror("tcsetattr");
|
||||
@@ -82,30 +87,40 @@ void disable_raw_mode() {
|
||||
}
|
||||
}
|
||||
|
||||
void start_screen() {
|
||||
Coord start_screen() {
|
||||
enable_raw_mode();
|
||||
get_terminal_size();
|
||||
screen.assign(rows * cols, {}); // allocate & zero-init
|
||||
old_screen.assign(rows * cols, {}); // allocate & zero-init
|
||||
return {rows, cols};
|
||||
}
|
||||
|
||||
void end_screen() { disable_raw_mode(); }
|
||||
|
||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||
uint8_t flags) {
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols)
|
||||
Coord get_size() { return {rows, cols}; }
|
||||
|
||||
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||
uint32_t bg, uint8_t flags) {
|
||||
if (row >= rows || col >= cols)
|
||||
return;
|
||||
|
||||
int idx = row * cols + col;
|
||||
uint32_t idx = row * cols + col;
|
||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||
|
||||
screen[idx].utf8 = utf8 ? utf8 : ""; // nullptr => empty string
|
||||
screen[idx].utf8 = utf8 ? utf8 : "";
|
||||
screen[idx].fg = fg;
|
||||
screen[idx].bg = bg;
|
||||
screen[idx].flags = flags;
|
||||
}
|
||||
|
||||
coords get_size() { return {rows, cols}; }
|
||||
bool ends_with(const std::string &string_to_check) {
|
||||
size_t len = string_to_check.size();
|
||||
if (len < 3)
|
||||
return false;
|
||||
return (string_to_check[len - 3] == VS16_BYTE_A) &&
|
||||
(string_to_check[len - 2] == VS16_BYTE_B) &&
|
||||
(string_to_check[len - 1] == VS16_BYTE_C);
|
||||
}
|
||||
|
||||
void render() {
|
||||
static bool first_render = true;
|
||||
@@ -114,147 +129,135 @@ void render() {
|
||||
bool current_italic = false;
|
||||
bool current_bold = false;
|
||||
bool current_underline = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||
|
||||
std::string out;
|
||||
// reserve a conservative amount to avoid repeated reallocs
|
||||
out.reserve(static_cast<size_t>(rows) * static_cast<size_t>(cols) * 4 + 256);
|
||||
|
||||
// save cursor + hide
|
||||
out += "\x1b[s\x1b[?25l";
|
||||
|
||||
if (first_render) {
|
||||
out += "\x1b[2J\x1b[H";
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (uint32_t row = 0; row < rows; ++row) {
|
||||
int first_change_col = -1;
|
||||
int last_change_col = -1;
|
||||
|
||||
// detect change span in this row
|
||||
for (int col = 0; col < cols; ++col) {
|
||||
int idx = row * cols + col;
|
||||
for (uint32_t col = 0; col < cols; ++col) {
|
||||
uint32_t idx = row * cols + col;
|
||||
ScreenCell &old_cell = old_screen[idx];
|
||||
ScreenCell &new_cell = screen[idx];
|
||||
|
||||
bool old_empty = old_cell.utf8.empty();
|
||||
bool new_empty = new_cell.utf8.empty();
|
||||
|
||||
bool content_changed =
|
||||
(old_empty && !new_empty) || (!old_empty && new_empty) ||
|
||||
(!old_empty && !new_empty && old_cell.utf8 != new_cell.utf8);
|
||||
|
||||
bool content_changed = old_cell.utf8 != new_cell.utf8;
|
||||
bool style_changed =
|
||||
(old_cell.fg != new_cell.fg) || (old_cell.bg != new_cell.bg) ||
|
||||
((old_cell.flags & CF_ITALIC) != (new_cell.flags & CF_ITALIC)) ||
|
||||
((old_cell.flags & CF_BOLD) != (new_cell.flags & CF_BOLD)) ||
|
||||
((old_cell.flags & CF_UNDERLINE) != (new_cell.flags & CF_UNDERLINE));
|
||||
|
||||
if (content_changed || style_changed) {
|
||||
if (first_change_col == -1)
|
||||
first_change_col = col;
|
||||
last_change_col = col;
|
||||
}
|
||||
}
|
||||
|
||||
if (first_change_col == -1)
|
||||
continue;
|
||||
|
||||
// move cursor once to the start of change region
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1,
|
||||
first_change_col + 1);
|
||||
out.append(buf, n);
|
||||
|
||||
// render changed region
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1);
|
||||
out.append(buf);
|
||||
for (int col = first_change_col; col <= last_change_col; ++col) {
|
||||
int idx = row * cols + col;
|
||||
ScreenCell &old_cell = old_screen[idx];
|
||||
ScreenCell &new_cell = screen[idx];
|
||||
|
||||
// foreground change
|
||||
if (current_fg != new_cell.fg) {
|
||||
if (new_cell.fg) {
|
||||
char fb[64];
|
||||
int m = snprintf(
|
||||
fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm", (new_cell.fg >> 16) & 0xFF,
|
||||
(new_cell.fg >> 8) & 0xFF, (new_cell.fg >> 0) & 0xFF);
|
||||
out.append(fb, m);
|
||||
snprintf(fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm",
|
||||
(new_cell.fg >> 16) & 0xFF, (new_cell.fg >> 8) & 0xFF,
|
||||
(new_cell.fg >> 0) & 0xFF);
|
||||
out.append(fb);
|
||||
} else {
|
||||
out += "\x1b[39m";
|
||||
}
|
||||
current_fg = new_cell.fg;
|
||||
}
|
||||
|
||||
// background change
|
||||
if (current_bg != new_cell.bg) {
|
||||
if (new_cell.bg) {
|
||||
char bb[64];
|
||||
int m = snprintf(
|
||||
bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm", (new_cell.bg >> 16) & 0xFF,
|
||||
(new_cell.bg >> 8) & 0xFF, (new_cell.bg >> 0) & 0xFF);
|
||||
out.append(bb, m);
|
||||
snprintf(bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm",
|
||||
(new_cell.bg >> 16) & 0xFF, (new_cell.bg >> 8) & 0xFF,
|
||||
(new_cell.bg >> 0) & 0xFF);
|
||||
out.append(bb);
|
||||
} else {
|
||||
out += "\x1b[49m";
|
||||
}
|
||||
current_bg = new_cell.bg;
|
||||
}
|
||||
|
||||
// italic
|
||||
bool italic = (new_cell.flags & CF_ITALIC) != 0;
|
||||
if (italic != current_italic) {
|
||||
out += italic ? "\x1b[3m" : "\x1b[23m";
|
||||
current_italic = italic;
|
||||
}
|
||||
|
||||
// bold
|
||||
bool bold = (new_cell.flags & CF_BOLD) != 0;
|
||||
if (bold != current_bold) {
|
||||
out += bold ? "\x1b[1m" : "\x1b[22m";
|
||||
current_bold = bold;
|
||||
}
|
||||
|
||||
// underline
|
||||
bool underline = (new_cell.flags & CF_UNDERLINE) != 0;
|
||||
if (underline != current_underline) {
|
||||
out += underline ? "\x1b[4m" : "\x1b[24m";
|
||||
current_underline = underline;
|
||||
}
|
||||
|
||||
// content
|
||||
if (!new_cell.utf8.empty()) {
|
||||
if (new_cell.utf8[0] == '\t')
|
||||
if (new_cell.utf8[0] == '\t') {
|
||||
out.append(" ");
|
||||
else
|
||||
out.append(new_cell.utf8);
|
||||
} else {
|
||||
// HACK: This is a hack to work around the fact that emojis should be
|
||||
// double width but handling them as so requires a lot of
|
||||
// calculations for word wrapping so eventually have to do that
|
||||
// and render them as the 2 wide they should be.
|
||||
if (new_cell.utf8.size() > 1) {
|
||||
if (new_cell.utf8.size() >= 3 && ends_with(new_cell.utf8)) {
|
||||
out.append(new_cell.utf8.substr(0, new_cell.utf8.size() - 3));
|
||||
out.append("\xEF\xB8\x8E");
|
||||
} else {
|
||||
out.append(new_cell.utf8);
|
||||
out.append("\xEF\xB8\x8E");
|
||||
}
|
||||
} else {
|
||||
out.append(new_cell.utf8);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.append(1, ' ');
|
||||
}
|
||||
|
||||
// copy new -> old (std::string assignment, no strdup/free)
|
||||
old_cell.utf8 = new_cell.utf8;
|
||||
old_cell.fg = new_cell.fg;
|
||||
old_cell.bg = new_cell.bg;
|
||||
old_cell.flags = new_cell.flags;
|
||||
}
|
||||
}
|
||||
|
||||
// final reset + restore cursor + show cursor
|
||||
out += "\x1b[0m";
|
||||
out += "\x1b[u";
|
||||
if (show_cursor)
|
||||
out += "\x1b[?25h";
|
||||
|
||||
// single syscall to write the whole frame
|
||||
ssize_t written = write(STDOUT_FILENO, out.data(), out.size());
|
||||
(void)written; // you may check for errors in debug builds
|
||||
const char *ptr = out.data();
|
||||
size_t remaining = out.size();
|
||||
while (remaining > 0) {
|
||||
ssize_t written = write(STDOUT_FILENO, ptr, remaining);
|
||||
if (written == 0) {
|
||||
break;
|
||||
} else if (written == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
die("write failed");
|
||||
break;
|
||||
} else {
|
||||
ptr += written;
|
||||
remaining -= written;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_cursor(int row, int col, bool show_cursor_param) {
|
||||
void set_cursor(int row, int col, int show_cursor_param) {
|
||||
char buf[32];
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, col + 1);
|
||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH\x1b[5 q", row + 1, col + 1);
|
||||
show_cursor = show_cursor_param;
|
||||
write(STDOUT_FILENO, buf, n);
|
||||
}
|
||||
858
src/rope.cc
Normal file
858
src/rope.cc
Normal file
@@ -0,0 +1,858 @@
|
||||
#include "../include/rope.h"
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <pcre2.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static void update(Knot *n) {
|
||||
if (!n)
|
||||
return;
|
||||
if (!n->depth || n->depth == 0)
|
||||
return;
|
||||
uint32_t left_chars = n->left ? n->left->char_count : 0;
|
||||
uint32_t right_chars = n->right ? n->right->char_count : 0;
|
||||
n->char_count = left_chars + right_chars;
|
||||
uint32_t left_lines = n->left ? n->left->line_count : 0;
|
||||
uint32_t right_lines = n->right ? n->right->line_count : 0;
|
||||
n->line_count = left_lines + right_lines;
|
||||
uint8_t left_depth = n->left ? n->left->depth : 0;
|
||||
uint8_t right_depth = n->right ? n->right->depth : 0;
|
||||
n->depth = MAX(left_depth, right_depth) + 1;
|
||||
n->chunk_size = n->left ? n->left->chunk_size : n->right->chunk_size;
|
||||
}
|
||||
|
||||
// str is not consumed and \0 is not handled
|
||||
// So if str is null terminated then len must be strlen(str)
|
||||
// and freed by caller
|
||||
Knot *load(char *str, uint32_t len, uint32_t chunk_size) {
|
||||
if (len > (uint32_t)(chunk_size - (chunk_size / 16))) {
|
||||
Knot *left = load(str, len / 2, chunk_size);
|
||||
Knot *right = load(str + len / 2, len - len / 2, chunk_size);
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
node->chunk_size = chunk_size;
|
||||
node->depth = MAX(left->depth, right->depth) + 1;
|
||||
node->char_count = left->char_count + right->char_count;
|
||||
node->line_count = left->line_count + right->line_count;
|
||||
return node;
|
||||
} else {
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot) + chunk_size);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = nullptr;
|
||||
node->right = nullptr;
|
||||
node->chunk_size = chunk_size;
|
||||
node->depth = 0;
|
||||
node->char_count = len;
|
||||
uint32_t newline_count = 0;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
node->data[i] = c;
|
||||
if (c == '\n')
|
||||
newline_count++;
|
||||
}
|
||||
node->line_count = newline_count;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
// leaf if consumed and freed (so dont use or free it after)
|
||||
// left and right are the new nodes
|
||||
static void split_leaf(Knot *leaf, uint32_t k, Knot **left, Knot **right) {
|
||||
Knot *left_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
|
||||
left_node->left = nullptr;
|
||||
left_node->right = nullptr;
|
||||
left_node->chunk_size = leaf->chunk_size;
|
||||
left_node->depth = 0;
|
||||
left_node->char_count = k;
|
||||
uint32_t newline_count = 0;
|
||||
for (uint32_t i = 0; i < k; i++) {
|
||||
char c = leaf->data[i];
|
||||
left_node->data[i] = c;
|
||||
if (c == '\n')
|
||||
newline_count++;
|
||||
}
|
||||
left_node->line_count = newline_count;
|
||||
uint16_t right_line_count = leaf->line_count - newline_count;
|
||||
*left = left_node;
|
||||
Knot *right_node = (Knot *)malloc(sizeof(Knot) + leaf->chunk_size);
|
||||
right_node->left = nullptr;
|
||||
right_node->right = nullptr;
|
||||
right_node->chunk_size = leaf->chunk_size;
|
||||
right_node->depth = 0;
|
||||
right_node->char_count = leaf->char_count - k;
|
||||
right_node->line_count = right_line_count;
|
||||
for (uint32_t i = k; i < leaf->char_count; i++) {
|
||||
char c = leaf->data[i];
|
||||
right_node->data[i - k] = c;
|
||||
}
|
||||
*right = right_node;
|
||||
free(leaf);
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
void split(Knot *node, uint32_t offset, Knot **left, Knot **right) {
|
||||
if (!node) {
|
||||
*left = nullptr;
|
||||
*right = nullptr;
|
||||
return;
|
||||
}
|
||||
if (node->depth == 0) {
|
||||
split_leaf(node, offset, left, right);
|
||||
return;
|
||||
}
|
||||
uint32_t left_size = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_size) {
|
||||
Knot *L = nullptr, *R = nullptr;
|
||||
split(node->left, offset, &L, &R);
|
||||
node->left = R;
|
||||
update(node);
|
||||
*right = node;
|
||||
*left = L;
|
||||
} else {
|
||||
uint32_t new_offset = offset - left_size;
|
||||
Knot *L = nullptr, *R = nullptr;
|
||||
split(node->right, new_offset, &L, &R);
|
||||
node->right = L;
|
||||
update(node);
|
||||
*left = node;
|
||||
*right = R;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int get_balance_factor(Knot *n) {
|
||||
if (!n)
|
||||
return 0;
|
||||
return (int)DEPTH(n->left) - (int)DEPTH(n->right);
|
||||
}
|
||||
|
||||
static inline Knot *rotate_right(Knot *y) {
|
||||
Knot *x = y->left;
|
||||
Knot *T2 = x->right;
|
||||
x->right = y;
|
||||
y->left = T2;
|
||||
update(y);
|
||||
update(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline Knot *rotate_left(Knot *x) {
|
||||
Knot *y = x->right;
|
||||
Knot *T2 = y->left;
|
||||
y->left = x;
|
||||
x->right = T2;
|
||||
update(x);
|
||||
update(y);
|
||||
return y;
|
||||
}
|
||||
|
||||
// Technically n can be used after calling
|
||||
// but use return value instead
|
||||
Knot *balance(Knot *n) {
|
||||
update(n);
|
||||
int bal = get_balance_factor(n);
|
||||
if (bal > 1) {
|
||||
if (get_balance_factor(n->left) < 0)
|
||||
n->left = rotate_left(n->left);
|
||||
return rotate_right(n);
|
||||
}
|
||||
if (bal < -1) {
|
||||
if (get_balance_factor(n->right) > 0)
|
||||
n->right = rotate_right(n->right);
|
||||
return rotate_left(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Dont free left or right after calling (only free return value)
|
||||
// Assumes both ropes have equal chunk sizes
|
||||
Knot *concat(Knot *left, Knot *right) {
|
||||
if (!left)
|
||||
return right;
|
||||
if (!right)
|
||||
return left;
|
||||
if (!left || left->char_count == 0) {
|
||||
if (left)
|
||||
free_rope(left);
|
||||
return right;
|
||||
}
|
||||
if (!right || right->char_count == 0) {
|
||||
if (right)
|
||||
free_rope(right);
|
||||
return left;
|
||||
}
|
||||
if (left->depth == 0 && right->depth == 0) {
|
||||
if (left->char_count + right->char_count <= left->chunk_size) {
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot) + left->chunk_size);
|
||||
node->left = nullptr;
|
||||
node->right = nullptr;
|
||||
node->chunk_size = left->chunk_size;
|
||||
node->depth = 0;
|
||||
node->char_count = left->char_count + right->char_count;
|
||||
node->line_count = left->line_count + right->line_count;
|
||||
memcpy(node->data, left->data, left->char_count);
|
||||
memcpy(node->data + left->char_count, right->data, right->char_count);
|
||||
free(left);
|
||||
free(right);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
uint16_t d_left = left->depth;
|
||||
uint16_t d_right = right->depth;
|
||||
if (d_left > d_right + 1) {
|
||||
left->right = concat(left->right, right);
|
||||
return balance(left);
|
||||
}
|
||||
if (d_right > d_left + 1) {
|
||||
right->left = concat(left, right->left);
|
||||
return balance(right);
|
||||
}
|
||||
Knot *node = (Knot *)malloc(sizeof(Knot));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
node->chunk_size = left->chunk_size;
|
||||
node->depth = MAX(d_left, d_right) + 1;
|
||||
update(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
// Instead, free the return value or use it in node's place
|
||||
Knot *insert(Knot *node, uint32_t offset, char *str, uint32_t len) {
|
||||
if (!node)
|
||||
return nullptr;
|
||||
if (len == 0)
|
||||
return node;
|
||||
if (node->depth == 0 && node->char_count + len <= node->chunk_size) {
|
||||
if (offset < node->char_count)
|
||||
memmove(node->data + offset + len, node->data + offset,
|
||||
node->char_count - offset);
|
||||
memcpy(node->data + offset, str, len);
|
||||
node->char_count += len;
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
if (str[i] == '\n')
|
||||
node->line_count++;
|
||||
return node;
|
||||
}
|
||||
if (node->depth > 0) {
|
||||
uint32_t left_count = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_count) {
|
||||
Knot *new_left = insert(node->left, offset, str, len);
|
||||
node->left = new_left;
|
||||
update(node);
|
||||
return balance(node);
|
||||
} else {
|
||||
Knot *new_right = insert(node->right, offset - left_count, str, len);
|
||||
node->right = new_right;
|
||||
update(node);
|
||||
return balance(node);
|
||||
}
|
||||
}
|
||||
Knot *left_part = nullptr;
|
||||
Knot *right_part = nullptr;
|
||||
split(node, offset, &left_part, &right_part);
|
||||
Knot *middle_part = load(str, len, node->chunk_size);
|
||||
return concat(concat(left_part, middle_part), right_part);
|
||||
}
|
||||
|
||||
// This makes node nonsensical, so dont use or free it after
|
||||
// Instead, free the return value or use it in node's place
|
||||
Knot *erase(Knot *node, uint32_t offset, uint32_t len) {
|
||||
if (!node || len == 0 || offset >= node->char_count)
|
||||
return node;
|
||||
if (offset + len > node->char_count)
|
||||
len = node->char_count - offset;
|
||||
if (node->depth == 0) {
|
||||
uint32_t deleted_newlines = 0;
|
||||
for (uint32_t i = offset; i < offset + len; i++)
|
||||
if (node->data[i] == '\n')
|
||||
deleted_newlines++;
|
||||
node->line_count -= deleted_newlines;
|
||||
if (offset + len < node->char_count)
|
||||
memmove(node->data + offset, node->data + offset + len,
|
||||
node->char_count - (offset + len));
|
||||
node->char_count -= len;
|
||||
return node;
|
||||
}
|
||||
uint32_t left_count = node->left ? node->left->char_count : 0;
|
||||
if (offset + len <= left_count) {
|
||||
node->left = erase(node->left, offset, len);
|
||||
} else if (offset >= left_count) {
|
||||
node->right = erase(node->right, offset - left_count, len);
|
||||
} else {
|
||||
Knot *left = nullptr, *middle = nullptr, *right = nullptr;
|
||||
split(node, offset, &left, &right);
|
||||
split(right, len, &middle, &right);
|
||||
free_rope(middle);
|
||||
return concat(left, right);
|
||||
}
|
||||
update(node);
|
||||
return balance(node);
|
||||
}
|
||||
|
||||
static void _read_into(Knot *node, uint32_t offset, uint32_t len, char *dest) {
|
||||
if (!node || len == 0)
|
||||
return;
|
||||
if (node->depth == 0) {
|
||||
memcpy(dest, node->data + offset, len);
|
||||
return;
|
||||
}
|
||||
Knot *left = node->left;
|
||||
uint32_t left_count = left ? left->char_count : 0;
|
||||
if (offset < left_count) {
|
||||
uint32_t chunk_len = left_count - offset;
|
||||
if (chunk_len > len)
|
||||
chunk_len = len;
|
||||
_read_into(left, offset, chunk_len, dest);
|
||||
dest += chunk_len;
|
||||
len -= chunk_len;
|
||||
offset = 0;
|
||||
} else {
|
||||
offset -= left_count;
|
||||
}
|
||||
if (len > 0 && node->right)
|
||||
_read_into(node->right, offset, len, dest);
|
||||
}
|
||||
|
||||
char *read(Knot *root, uint32_t offset, uint32_t len) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
if (offset >= root->char_count) {
|
||||
char *empty = (char *)malloc(1);
|
||||
if (empty)
|
||||
empty[0] = '\0';
|
||||
return empty;
|
||||
}
|
||||
if (offset + len > root->char_count) {
|
||||
len = root->char_count - offset;
|
||||
}
|
||||
char *buffer = (char *)malloc((len + 1) * sizeof(char));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
_read_into(root, offset, len, buffer);
|
||||
buffer[len] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Hopefully free the tree only once at the end of its use using the pointer
|
||||
// from the last insert or concat or erase call.
|
||||
// (or use twice if last call was split - for both left and right).
|
||||
void free_rope(Knot *root) {
|
||||
if (!root)
|
||||
return;
|
||||
free_rope(root->left);
|
||||
free_rope(root->right);
|
||||
free(root);
|
||||
}
|
||||
|
||||
static uint32_t find_nth_newline_offset(Knot *node, uint32_t n) {
|
||||
if (!node || n > node->line_count)
|
||||
return UINT32_MAX;
|
||||
if (node->depth == 0) {
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < node->char_count; i++) {
|
||||
if (node->data[i] == '\n') {
|
||||
if (count == n)
|
||||
return i;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
uint32_t left_lines = node->left ? node->left->line_count : 0;
|
||||
if (n < left_lines) {
|
||||
return find_nth_newline_offset(node->left, n);
|
||||
} else {
|
||||
uint32_t right_offset =
|
||||
find_nth_newline_offset(node->right, n - left_lines);
|
||||
if (right_offset == UINT32_MAX)
|
||||
return UINT32_MAX;
|
||||
uint32_t left_chars = node->left ? node->left->char_count : 0;
|
||||
return left_chars + right_offset;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t byte_to_line(Knot *node, uint32_t offset) {
|
||||
if (!node)
|
||||
return 0;
|
||||
if (offset >= node->char_count)
|
||||
return node->line_count;
|
||||
if (node->depth == 0) {
|
||||
uint32_t lines_before = 0;
|
||||
uint32_t limit = (offset < node->char_count) ? offset : node->char_count;
|
||||
for (uint32_t i = 0; i < limit; i++)
|
||||
if (node->data[i] == '\n')
|
||||
lines_before++;
|
||||
return lines_before;
|
||||
}
|
||||
uint32_t left_chars = node->left ? node->left->char_count : 0;
|
||||
if (offset < left_chars) {
|
||||
return byte_to_line(node->left, offset);
|
||||
} else {
|
||||
uint32_t left_lines = node->left ? node->left->line_count : 0;
|
||||
return left_lines + byte_to_line(node->right, offset - left_chars);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t line_to_byte(Knot *node, uint32_t line, uint32_t *out_len) {
|
||||
if (!node) {
|
||||
if (out_len)
|
||||
*out_len = 0;
|
||||
return 0;
|
||||
}
|
||||
uint32_t start_offset = 0;
|
||||
uint32_t end_offset = 0;
|
||||
if (line == 0) {
|
||||
start_offset = 0;
|
||||
} else {
|
||||
uint32_t prev_newline = find_nth_newline_offset(node, line - 1);
|
||||
if (prev_newline == UINT32_MAX)
|
||||
start_offset = node->char_count;
|
||||
else
|
||||
start_offset = prev_newline + 1;
|
||||
}
|
||||
uint32_t current_newline = find_nth_newline_offset(node, line);
|
||||
if (current_newline == UINT32_MAX)
|
||||
end_offset = node->char_count;
|
||||
else
|
||||
end_offset = current_newline + 1;
|
||||
if (out_len) {
|
||||
if (end_offset > start_offset)
|
||||
*out_len = end_offset - start_offset;
|
||||
else
|
||||
*out_len = 0;
|
||||
}
|
||||
return start_offset;
|
||||
}
|
||||
|
||||
LineIterator *begin_l_iter(Knot *root, uint32_t start_line) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
if (start_line > root->line_count)
|
||||
return nullptr;
|
||||
LineIterator *it = (LineIterator *)malloc(sizeof(LineIterator));
|
||||
if (!it)
|
||||
return nullptr;
|
||||
it->top = 0;
|
||||
it->line = start_line;
|
||||
it->node = nullptr;
|
||||
if (start_line == 0) {
|
||||
it->offset = 0;
|
||||
while (root->left) {
|
||||
it->stack[it->top++] = root;
|
||||
root = root->left;
|
||||
}
|
||||
if (!root->left && !root->right)
|
||||
it->node = root;
|
||||
it->stack[it->top++] = root;
|
||||
return it;
|
||||
}
|
||||
Knot *curr = root;
|
||||
uint32_t relative_line = --start_line;
|
||||
it->line = start_line;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
break;
|
||||
}
|
||||
uint32_t left_lines = (curr->left) ? curr->left->line_count : 0;
|
||||
if (relative_line < left_lines) {
|
||||
curr = curr->left;
|
||||
} else {
|
||||
relative_line -= left_lines;
|
||||
curr = curr->right;
|
||||
}
|
||||
}
|
||||
if (!it->node) {
|
||||
free(it);
|
||||
return nullptr;
|
||||
}
|
||||
it->offset = 0;
|
||||
if (relative_line > 0) {
|
||||
uint32_t found_newlines = 0;
|
||||
uint32_t i = 0;
|
||||
for (i = 0; i < it->node->char_count; i++) {
|
||||
if (it->node->data[i] == '\n') {
|
||||
found_newlines++;
|
||||
if (found_newlines == relative_line) {
|
||||
it->offset = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(next_line(it));
|
||||
return it;
|
||||
}
|
||||
|
||||
static inline void iter_advance_leaf(LineIterator *it) {
|
||||
if (it->top == 0) {
|
||||
it->node = nullptr;
|
||||
return;
|
||||
}
|
||||
Knot *prev = it->stack[--it->top];
|
||||
while (it->top > 0) {
|
||||
Knot *parent = it->stack[it->top - 1];
|
||||
if (parent->left == prev && parent->right) {
|
||||
Knot *curr = parent->right;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
it->offset = 0;
|
||||
return;
|
||||
}
|
||||
curr = (curr->left) ? curr->left : curr->right;
|
||||
}
|
||||
}
|
||||
prev = it->stack[--it->top];
|
||||
}
|
||||
it->node = nullptr;
|
||||
}
|
||||
|
||||
char *next_line(LineIterator *it) {
|
||||
if (!it || !it->node)
|
||||
return nullptr;
|
||||
size_t capacity = 128;
|
||||
size_t len = 0;
|
||||
char *buffer = (char *)malloc(capacity);
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
while (it->node) {
|
||||
if (it->offset >= it->node->char_count) {
|
||||
iter_advance_leaf(it);
|
||||
if (!it->node)
|
||||
break;
|
||||
}
|
||||
char *start = it->node->data + it->offset;
|
||||
char *end = it->node->data + it->node->char_count;
|
||||
char *newline_ptr = (char *)memchr(start, '\n', end - start);
|
||||
size_t chunk_len;
|
||||
bool found_newline = false;
|
||||
if (newline_ptr) {
|
||||
chunk_len = (newline_ptr - start) + 1;
|
||||
found_newline = true;
|
||||
} else {
|
||||
chunk_len = end - start;
|
||||
}
|
||||
if (len + chunk_len + 1 > capacity) {
|
||||
capacity = (capacity * 2) + chunk_len;
|
||||
char *new_buf = (char *)realloc(buffer, capacity);
|
||||
if (!new_buf) {
|
||||
free(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
buffer = new_buf;
|
||||
}
|
||||
memcpy(buffer + len, start, chunk_len);
|
||||
len += chunk_len;
|
||||
it->offset += chunk_len;
|
||||
if (found_newline) {
|
||||
buffer[len] = '\0';
|
||||
it->line++;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
if (len > 0) {
|
||||
buffer[len] = '\0';
|
||||
it->line++;
|
||||
return buffer;
|
||||
}
|
||||
free(buffer);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LeafIterator *begin_k_iter(Knot *root) {
|
||||
if (!root)
|
||||
return nullptr;
|
||||
LeafIterator *it = (LeafIterator *)malloc(sizeof(LeafIterator));
|
||||
if (!it)
|
||||
return nullptr;
|
||||
it->top = 0;
|
||||
Knot *curr = root;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
return it;
|
||||
}
|
||||
curr = curr->left;
|
||||
if (!curr) {
|
||||
curr = it->stack[--it->top]->right;
|
||||
Knot *temp = it->stack[it->top];
|
||||
it->stack[it->top++] = temp;
|
||||
curr = temp->left ? temp->left : temp->right;
|
||||
Knot *parent = it->stack[it->top - 1];
|
||||
curr = parent->left;
|
||||
if (!curr) {
|
||||
curr = parent->right;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(it);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Caller must never free the returned string
|
||||
char *next_leaf(LeafIterator *it) {
|
||||
if (!it || !it->node)
|
||||
return nullptr;
|
||||
char *data_to_return = it->node->data;
|
||||
data_to_return[it->node->char_count] = '\0';
|
||||
Knot *prev_leaf = it->node;
|
||||
Knot *parent = nullptr;
|
||||
while (it->top > 0) {
|
||||
parent = it->stack[--it->top];
|
||||
if (parent->right && parent->right != prev_leaf) {
|
||||
Knot *curr = parent->right;
|
||||
while (curr) {
|
||||
it->stack[it->top++] = curr;
|
||||
if (!curr->left && !curr->right) {
|
||||
it->node = curr;
|
||||
return data_to_return;
|
||||
}
|
||||
curr = curr->left;
|
||||
if (!curr)
|
||||
curr = it->stack[it->top - 1]->right;
|
||||
}
|
||||
}
|
||||
prev_leaf = parent;
|
||||
}
|
||||
it->node = nullptr;
|
||||
return data_to_return;
|
||||
}
|
||||
|
||||
ByteIterator *begin_b_iter(Knot *root) {
|
||||
ByteIterator *b_it = (ByteIterator *)malloc(sizeof(ByteIterator));
|
||||
LeafIterator *l_it = begin_k_iter(root);
|
||||
b_it->it = l_it;
|
||||
b_it->offset_g = 0;
|
||||
b_it->offset_l = 0;
|
||||
b_it->char_count = 0;
|
||||
b_it->data = nullptr;
|
||||
return b_it;
|
||||
}
|
||||
|
||||
char next_byte(ByteIterator *it) {
|
||||
if (it->data && it->offset_l < it->char_count) {
|
||||
return it->data[it->offset_l++];
|
||||
} else {
|
||||
it->offset_g += it->offset_l;
|
||||
it->offset_l = 1;
|
||||
char *data = next_leaf(it->it);
|
||||
it->char_count = strlen(data);
|
||||
it->data = data;
|
||||
if (it->data)
|
||||
return *it->data;
|
||||
else
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> search_rope(Knot *root,
|
||||
const char *pattern) {
|
||||
std::vector<std::pair<size_t, size_t>> results;
|
||||
int errorcode;
|
||||
PCRE2_SIZE erroffset;
|
||||
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0,
|
||||
&errorcode, &erroffset, nullptr);
|
||||
if (!re) {
|
||||
fprintf(stderr, "PCRE2 compile error: %d\n", errorcode);
|
||||
return results;
|
||||
}
|
||||
pcre2_match_data *mdata = pcre2_match_data_create(128, nullptr);
|
||||
int workspace[PCRE_WORKSPACE_SIZE];
|
||||
LeafIterator *it = begin_k_iter(root);
|
||||
if (!it) {
|
||||
pcre2_code_free(re);
|
||||
pcre2_match_data_free(mdata);
|
||||
return results;
|
||||
}
|
||||
size_t chunk_abs_offset = 0;
|
||||
size_t saved_match_start = 0;
|
||||
bool match_in_progress = false;
|
||||
int flags = PCRE2_PARTIAL_SOFT;
|
||||
while (1) {
|
||||
const char *chunk_start = next_leaf(it);
|
||||
if (!chunk_start)
|
||||
break;
|
||||
size_t chunk_len = strlen(chunk_start);
|
||||
const char *current_ptr = chunk_start;
|
||||
size_t remaining_len = chunk_len;
|
||||
while (remaining_len > 0) {
|
||||
int rc =
|
||||
pcre2_dfa_match(re, (PCRE2_SPTR)current_ptr, remaining_len, 0, flags,
|
||||
mdata, nullptr, workspace, PCRE_WORKSPACE_SIZE);
|
||||
if (rc >= 0) {
|
||||
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||
size_t match_start_abs;
|
||||
size_t match_end_abs;
|
||||
if (match_in_progress) {
|
||||
match_start_abs = saved_match_start;
|
||||
match_end_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
|
||||
} else {
|
||||
match_start_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
|
||||
match_end_abs =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[1];
|
||||
}
|
||||
size_t total_len = match_end_abs - match_start_abs;
|
||||
results.push_back(std::make_pair(match_start_abs, total_len));
|
||||
size_t consumed = ov[1];
|
||||
if (consumed == 0)
|
||||
consumed = 1;
|
||||
current_ptr += consumed;
|
||||
if (consumed > remaining_len)
|
||||
remaining_len = 0;
|
||||
else
|
||||
remaining_len -= consumed;
|
||||
match_in_progress = false;
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
continue;
|
||||
} else if (rc == PCRE2_ERROR_PARTIAL) {
|
||||
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(mdata);
|
||||
if (!match_in_progress) {
|
||||
saved_match_start =
|
||||
chunk_abs_offset + (current_ptr - chunk_start) + ov[0];
|
||||
match_in_progress = true;
|
||||
}
|
||||
flags |= PCRE2_DFA_RESTART;
|
||||
flags |= PCRE2_NOTBOL;
|
||||
break;
|
||||
} else {
|
||||
if (match_in_progress) {
|
||||
match_in_progress = false;
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
current_ptr++;
|
||||
remaining_len--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
// if (rc != PCRE2_ERROR_NOMATCH) {} // handle error
|
||||
}
|
||||
}
|
||||
chunk_abs_offset += chunk_len;
|
||||
if (!match_in_progress)
|
||||
flags = PCRE2_PARTIAL_SOFT | PCRE2_NOTBOL;
|
||||
}
|
||||
pcre2_match_data_free(mdata);
|
||||
pcre2_code_free(re);
|
||||
free(it);
|
||||
return results;
|
||||
}
|
||||
|
||||
uint32_t optimal_chunk_size(uint64_t length) {
|
||||
if (length <= MIN_CHUNK_SIZE)
|
||||
return MIN_CHUNK_SIZE;
|
||||
double target_exponent = MIN(std::log2((double)MAX_CHUNK_SIZE),
|
||||
7.0 + (std::log2((double)length) - 10.0) * 0.25);
|
||||
uint32_t final_chunk_size =
|
||||
MAX((uint32_t)MIN_CHUNK_SIZE, (uint32_t)std::pow(2.0, target_exponent));
|
||||
final_chunk_size = MIN(final_chunk_size, (uint32_t)MAX_CHUNK_SIZE);
|
||||
final_chunk_size = 1U << (32 - __builtin_clz(final_chunk_size - 1));
|
||||
return final_chunk_size;
|
||||
}
|
||||
|
||||
// Basic correctness test & usage example
|
||||
/*
|
||||
int _main() {
|
||||
char *buffer = (char *)malloc(44 * 4 + 5);
|
||||
strcpy(buffer, "The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.\n\
|
||||
The quick brown fox jumps over the lazy dog.");
|
||||
// This loads all (excluding \0 put in by strcpy)
|
||||
Knot *root = load(buffer, 44 * 4 + 3, optimal_chunk_size(44 * 4 + 3));
|
||||
Knot *left = nullptr, *right = nullptr;
|
||||
// Splits root into left and right (root is no longer valid)
|
||||
split(root, 5, &left, &right);
|
||||
// simple read based on byte offset and length
|
||||
char *s1 = read(left, 0, 100);
|
||||
printf("%s\n:\n", s1);
|
||||
char *s2 = read(right, 0, 100);
|
||||
printf("%s\n;\n", s2);
|
||||
free(s1);
|
||||
free(s2);
|
||||
// Recombines left and right into root (both can
|
||||
// be valid or invalid in optimized cases)
|
||||
// they are to not be used after concat
|
||||
root = concat(left, right);
|
||||
// root should be set to return value from insert always
|
||||
root = insert(root, 5, buffer, 5);
|
||||
free(buffer);
|
||||
char *s3 = read(root, 0, 100);
|
||||
printf("%s\n,\n", s3);
|
||||
// Similar to insert but for erase
|
||||
root = erase(root, 5, 5);
|
||||
char *s4 = read(root, 0, 100);
|
||||
printf("%s\n.\n", s4);
|
||||
free(s3);
|
||||
free(s4);
|
||||
uint32_t byte_offset;
|
||||
uint32_t len;
|
||||
// Byte offset given reltive to how it would
|
||||
// be in a file offset + len includes the \n
|
||||
// at the end of the line (or nothing is EOF)
|
||||
byte_offset = line_to_byte(root, 2, &len);
|
||||
char *s5 = read(root, byte_offset, len);
|
||||
printf("%s\n'\n", s5);
|
||||
free(s5);
|
||||
// returns line number of which line that
|
||||
// byte position would be in.
|
||||
// the ending \n position is included in this
|
||||
uint32_t line = byte_to_line(root, byte_offset + len - 1);
|
||||
printf("%u\n:\n", line);
|
||||
// From second line onwards (0 indexed)
|
||||
LineIterator *it = begin_l_iter(root, 0);
|
||||
char *c = nullptr;
|
||||
while ((c = next_line(it)) != nullptr) {
|
||||
printf("%s :wow:\n", c);
|
||||
free(c);
|
||||
}
|
||||
free(it);
|
||||
printf("\n/\n");
|
||||
// Starts at first byte (to be used for regex search)
|
||||
ByteIterator *it2 = begin_b_iter(root);
|
||||
|
||||
uint32_t saved[40];
|
||||
|
||||
for (int i = 0; i < 40; i++)
|
||||
saved[i] = 0;
|
||||
|
||||
// std::string pattern = "f.x";
|
||||
|
||||
// Inst *program = compile_regex(pattern);
|
||||
//
|
||||
// bool result;
|
||||
// while ((result = next_match(program, it2, saved))) {
|
||||
// printf("\nRES: %d\n", result);
|
||||
// for (int i = 0; i < 40; i++)
|
||||
// printf("%d, ", saved[i]);
|
||||
// }
|
||||
|
||||
// char c2 = ' ';
|
||||
// while ((c2 = next_byte(it2)) != '\0')
|
||||
// printf("%c :wow!:\n", c2);
|
||||
// free(it2);
|
||||
// search // uses leaf iterator internally // PCRE2 based
|
||||
std::vector<std::pair<size_t, size_t>> matches = search_rope(root, "f.x");
|
||||
for (size_t i = 0; i < matches.size(); i++)
|
||||
printf("\n%lu %lu", matches[i].first, matches[i].second);
|
||||
// A rope needs to be freed only once if last action on the rope is
|
||||
// insert or concat or erase.
|
||||
// for splits we need to free both left and right separately
|
||||
free_rope(root);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
@@ -1,70 +0,0 @@
|
||||
class Buffer
|
||||
# Simple structs for clarity
|
||||
Diagnostic = Struct.new(:x0, :y0, :x1, :y1, :message)
|
||||
Highlight = Struct.new(:x0, :y0, :x1, :y1, :fg, :bg)
|
||||
VirtualText = Struct.new(:x, :y, :lines)
|
||||
Cursor = Struct.new(:x, :y)
|
||||
|
||||
attr_accessor :text, :cursor, :selection_start,
|
||||
:diagnostics, :highlights, :virtual_texts
|
||||
|
||||
def initialize(initial_text = "")
|
||||
@text = initial_text
|
||||
@cursor = Cursor.new(0, 0)
|
||||
@selection_start = Cursor.new(0, 0)
|
||||
@diagnostics = []
|
||||
@highlights = []
|
||||
@virtual_texts = []
|
||||
end
|
||||
|
||||
# Utility methods
|
||||
def lines
|
||||
@text.split("\n")
|
||||
end
|
||||
|
||||
def line_count
|
||||
lines.size
|
||||
end
|
||||
|
||||
def line(y)
|
||||
lines[y] || ""
|
||||
end
|
||||
|
||||
def insert(x, y, str)
|
||||
current_line = lines[y] || ""
|
||||
before = current_line[0...x] || ""
|
||||
after = current_line[x..-1] || ""
|
||||
lines_arr = lines
|
||||
lines_arr[y] = before + str + after
|
||||
@text = lines_arr.join("\n")
|
||||
end
|
||||
|
||||
def erase(x0, y0, x1, y1)
|
||||
lines_arr = lines
|
||||
if y0 == y1
|
||||
line = lines_arr[y0] || ""
|
||||
lines_arr[y0] = line[0...x0] + line[x1..-1].to_s
|
||||
else
|
||||
first = lines_arr[y0][0...x0]
|
||||
last = lines_arr[y1][x1..-1].to_s
|
||||
lines_arr[y0..y1] = [first + last]
|
||||
end
|
||||
@text = lines_arr.join("\n")
|
||||
end
|
||||
|
||||
# Add overlays
|
||||
def add_diagnostic(x0, y0, x1, y1, message)
|
||||
@diagnostics << Diagnostic.new(x0, y0, x1, y1, message)
|
||||
end
|
||||
|
||||
def add_highlight(x0, y0, x1, y1, fg: 0xFFFFFF, bg: 0x000000)
|
||||
@highlights << Highlight.new(x0, y0, x1, y1, fg, bg)
|
||||
end
|
||||
|
||||
def add_virtual_text(x, y, lines)
|
||||
@virtual_texts << VirtualText.new(x, y, lines)
|
||||
end
|
||||
|
||||
def render()
|
||||
end
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
require "ffi"
|
||||
require_relative "utils"
|
||||
|
||||
module C
|
||||
extend FFI::Library
|
||||
ffi_lib File.join(__dir__, "../../builds/C-crib.so")
|
||||
|
||||
class KeyEvent < FFI::Struct
|
||||
layout :key_type, :uint8,
|
||||
:c, :char,
|
||||
:special_key, :uint8,
|
||||
:special_modifier, :uint8,
|
||||
:mouse_x, :uint8,
|
||||
:mouse_y, :uint8,
|
||||
:mouse_button, :uint8,
|
||||
:mouse_state, :uint8,
|
||||
:mouse_direction, :uint8,
|
||||
:mouse_modifier, :uint8
|
||||
|
||||
def to_s
|
||||
case KEY_TYPE[self[:key_type]]
|
||||
when :char
|
||||
"#<KeyEvent char=#{self[:c].inspect}>"
|
||||
when :special
|
||||
"#<KeyEvent special key=#{SPECIAL_KEY[self[:special_key]]} mod=#{MODIFIER[self[:special_modifier]]}>"
|
||||
when :mouse
|
||||
"#<KeyEvent mouse x=#{self[:mouse_x]} y=#{self[:mouse_y]} " \
|
||||
"btn=#{MOUSE_BUTTON[self[:mouse_button]]} state=#{MOUSE_STATE[self[:mouse_state]]} " \
|
||||
"scroll_dir=#{SCROLL_DIR[self[:mouse_direction]]} mod=#{MODIFIER[self[:mouse_modifier]]}>"
|
||||
else
|
||||
"#<KeyEvent type=#{self[:key_type]} unknown=true>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Coords < FFI::Struct
|
||||
layout :x, :int,
|
||||
:y, :int
|
||||
|
||||
def to_ary
|
||||
[self[:x], self[:y]]
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#<Coords x=#{self[:x]} y=#{self[:y]}>"
|
||||
end
|
||||
end
|
||||
|
||||
attach_function :start_screen, [], :void
|
||||
attach_function :end_screen, [], :void
|
||||
attach_function :update, [:int, :int, :string, :uint32, :uint32, :uint8], :void
|
||||
attach_function :render, [], :void
|
||||
attach_function :set_cursor, [:int, :int, :int], :void
|
||||
attach_function :read_key, [], KeyEvent.by_value
|
||||
attach_function :get_size, [], Coords.by_value
|
||||
attach_function :real_width, [:string], :int
|
||||
end
|
||||
243
src/ts.cc
Normal file
243
src/ts.cc
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "../include/ts.h"
|
||||
#include "../include/editor.h"
|
||||
#include "../include/rope.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
static std::unordered_map<std::string, std::regex> regex_cache;
|
||||
|
||||
static const std::regex scm_regex(
|
||||
R"((@[A-Za-z0-9_.]+)|(;; \#[0-9a-fA-F]{6} \#[0-9a-fA-F]{6} [01] [01] [01] \d+))");
|
||||
|
||||
TSQuery *load_query(const char *query_path, Editor *editor) {
|
||||
const TSLanguage *lang = editor->language;
|
||||
std::ifstream file(query_path, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return nullptr;
|
||||
std::string highlight_query((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
std::smatch match;
|
||||
std::map<std::string, int> capture_name_cache;
|
||||
Highlight *c_hl = nullptr;
|
||||
int i = 0;
|
||||
int limit = 20;
|
||||
editor->query_map.resize(limit);
|
||||
std::string::const_iterator searchStart(highlight_query.cbegin());
|
||||
while (std::regex_search(searchStart, highlight_query.cend(), match,
|
||||
scm_regex)) {
|
||||
std::string mct = match.str();
|
||||
if (mct.substr(0, 1) == "@") {
|
||||
std::string capture_name = mct;
|
||||
if (!capture_name_cache.count(capture_name)) {
|
||||
if (c_hl) {
|
||||
if (i >= limit) {
|
||||
limit += 20;
|
||||
editor->query_map.resize(limit);
|
||||
}
|
||||
editor->query_map[i] = *c_hl;
|
||||
delete c_hl;
|
||||
c_hl = nullptr;
|
||||
}
|
||||
capture_name_cache[capture_name] = i;
|
||||
i++;
|
||||
}
|
||||
} else if (mct.substr(0, 2) == ";;") {
|
||||
if (c_hl)
|
||||
delete c_hl;
|
||||
c_hl = new Highlight();
|
||||
c_hl->fg = HEX(mct.substr(4, 6));
|
||||
c_hl->bg = HEX(mct.substr(12, 6));
|
||||
int bold = std::stoi(mct.substr(19, 1));
|
||||
int italic = std::stoi(mct.substr(21, 1));
|
||||
int underline = std::stoi(mct.substr(23, 1));
|
||||
c_hl->priority = std::stoi(mct.substr(25));
|
||||
c_hl->flags = (bold ? CF_BOLD : 0) | (italic ? CF_ITALIC : 0) |
|
||||
(underline ? CF_UNDERLINE : 0);
|
||||
}
|
||||
searchStart = match.suffix().first;
|
||||
}
|
||||
if (c_hl)
|
||||
delete c_hl;
|
||||
uint32_t error_offset = 0;
|
||||
TSQueryError error_type = (TSQueryError)0;
|
||||
TSQuery *q = ts_query_new(lang, highlight_query.c_str(),
|
||||
(uint32_t)highlight_query.length(), &error_offset,
|
||||
&error_type);
|
||||
return q;
|
||||
}
|
||||
|
||||
static inline const TSNode *find_capture_node(const TSQueryMatch &match,
|
||||
uint32_t capture_id) {
|
||||
for (uint32_t i = 0; i < match.capture_count; i++)
|
||||
if (match.captures[i].index == capture_id)
|
||||
return &match.captures[i].node;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline std::string node_text(const TSNode &node, Knot *source) {
|
||||
uint32_t start = ts_node_start_byte(node);
|
||||
uint32_t end = ts_node_end_byte(node);
|
||||
char *text = read(source, start, end - start);
|
||||
std::string final = std::string(text, end - start);
|
||||
free(text);
|
||||
return final;
|
||||
}
|
||||
|
||||
static inline bool ts_predicate(TSQuery *query, const TSQueryMatch &match,
|
||||
Knot *source) {
|
||||
uint32_t step_count;
|
||||
const TSQueryPredicateStep *steps =
|
||||
ts_query_predicates_for_pattern(query, match.pattern_index, &step_count);
|
||||
if (!steps || step_count != 4)
|
||||
return true;
|
||||
if (source->char_count >= (64 * 1024))
|
||||
return false;
|
||||
std::string command;
|
||||
std::string regex_txt;
|
||||
uint32_t subject_id = 0;
|
||||
for (uint32_t i = 0; i < step_count; i++) {
|
||||
const TSQueryPredicateStep *step = &steps[i];
|
||||
if (step->type == TSQueryPredicateStepTypeDone)
|
||||
break;
|
||||
switch (step->type) {
|
||||
case TSQueryPredicateStepTypeString: {
|
||||
uint32_t length = 0;
|
||||
const char *s =
|
||||
ts_query_string_value_for_id(query, step->value_id, &length);
|
||||
if (i == 0)
|
||||
command.assign(s, length);
|
||||
else
|
||||
regex_txt.assign(s, length);
|
||||
break;
|
||||
}
|
||||
case TSQueryPredicateStepTypeCapture: {
|
||||
subject_id = step->value_id;
|
||||
break;
|
||||
}
|
||||
case TSQueryPredicateStepTypeDone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const TSNode *node = find_capture_node(match, subject_id);
|
||||
std::string subject = node_text(*node, source);
|
||||
auto it = regex_cache.find(regex_txt);
|
||||
if (it == regex_cache.end())
|
||||
it = regex_cache.emplace(regex_txt, std::regex(regex_txt)).first;
|
||||
const std::regex &re = it->second;
|
||||
if (command == "match?")
|
||||
return std::regex_match(subject, re);
|
||||
else if (command == "not-match?")
|
||||
return !std::regex_match(subject, re);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *read_ts(void *payload, uint32_t byte_index, TSPoint,
|
||||
uint32_t *bytes_read) {
|
||||
if (!running) {
|
||||
*bytes_read = 0;
|
||||
return "";
|
||||
}
|
||||
TSLoad *load = (TSLoad *)payload;
|
||||
Knot *root = load->editor->root;
|
||||
if (load->prev)
|
||||
free(load->prev);
|
||||
if (byte_index >= root->char_count) {
|
||||
*bytes_read = 0;
|
||||
load->prev = nullptr;
|
||||
return "";
|
||||
}
|
||||
uint32_t chunk_size = 4096;
|
||||
uint32_t remaining = root->char_count - byte_index;
|
||||
uint32_t len_to_read = remaining > chunk_size ? chunk_size : remaining;
|
||||
std::shared_lock lock(load->editor->knot_mtx);
|
||||
char *buffer = read(root, byte_index, len_to_read);
|
||||
lock.unlock();
|
||||
load->prev = buffer;
|
||||
*bytes_read = len_to_read;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static inline Highlight *safe_get(std::vector<Highlight> &vec, size_t index) {
|
||||
if (index >= vec.size())
|
||||
return nullptr;
|
||||
return &vec[index];
|
||||
}
|
||||
|
||||
void ts_collect_spans(Editor *editor) {
|
||||
if (!editor->parser || !editor->root || !editor->query)
|
||||
return;
|
||||
TSLoad load = {editor, nullptr};
|
||||
TSInput tsinput = {
|
||||
.payload = &load,
|
||||
.read = read_ts,
|
||||
.encoding = TSInputEncodingUTF8,
|
||||
.decode = nullptr,
|
||||
};
|
||||
TSTree *tree, *copy = nullptr;
|
||||
std::unique_lock lock_0(editor->knot_mtx);
|
||||
if (editor->tree)
|
||||
copy = ts_tree_copy(editor->tree);
|
||||
lock_0.unlock();
|
||||
if (!running)
|
||||
return;
|
||||
std::vector<TSInputEdit> edits;
|
||||
TSInputEdit edit;
|
||||
if (copy) {
|
||||
while (editor->edit_queue.pop(edit)) {
|
||||
edits.push_back(edit);
|
||||
ts_tree_edit(copy, &edits.back());
|
||||
}
|
||||
}
|
||||
tree = ts_parser_parse(editor->parser, copy, tsinput);
|
||||
ts_tree_delete(copy);
|
||||
while (editor->edit_queue.pop(edit)) {
|
||||
edits.push_back(edit);
|
||||
ts_tree_edit(tree, &edits.back());
|
||||
}
|
||||
lock_0.lock();
|
||||
if (editor->tree)
|
||||
ts_tree_delete(editor->tree);
|
||||
editor->tree = tree;
|
||||
copy = ts_tree_copy(tree);
|
||||
lock_0.unlock();
|
||||
std::shared_lock lock_1(editor->span_mtx);
|
||||
TSQueryCursor *cursor = ts_query_cursor_new();
|
||||
ts_query_cursor_exec(cursor, editor->query, ts_tree_root_node(copy));
|
||||
std::vector<Span> new_spans;
|
||||
new_spans.reserve(4096);
|
||||
TSQueryMatch match;
|
||||
while (ts_query_cursor_next_match(cursor, &match)) {
|
||||
if (!running)
|
||||
break;
|
||||
if (!ts_predicate(editor->query, match, editor->root))
|
||||
continue;
|
||||
for (uint32_t i = 0; i < match.capture_count; i++) {
|
||||
if (!running)
|
||||
break;
|
||||
TSQueryCapture cap = match.captures[i];
|
||||
uint32_t start = ts_node_start_byte(cap.node);
|
||||
uint32_t end = ts_node_end_byte(cap.node);
|
||||
Highlight *hl = safe_get(editor->query_map, cap.index);
|
||||
if (hl)
|
||||
new_spans.push_back({start, end, hl});
|
||||
}
|
||||
}
|
||||
if (!running) {
|
||||
ts_tree_delete(copy);
|
||||
ts_query_cursor_delete(cursor);
|
||||
if (load.prev)
|
||||
free(load.prev);
|
||||
return;
|
||||
}
|
||||
lock_1.unlock();
|
||||
std::sort(new_spans.begin(), new_spans.end());
|
||||
std::unique_lock lock_2(editor->span_mtx);
|
||||
editor->spans.swap(new_spans);
|
||||
lock_2.unlock();
|
||||
ts_query_cursor_delete(cursor);
|
||||
if (load.prev)
|
||||
free(load.prev);
|
||||
}
|
||||
87
src/utils.cc
Normal file
87
src/utils.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
}
|
||||
#include "../include/utils.h"
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
std::string get_exe_dir() {
|
||||
char exe_path[PATH_MAX];
|
||||
ssize_t count = readlink("/proc/self/exe", exe_path, PATH_MAX);
|
||||
if (count == -1)
|
||||
return "";
|
||||
exe_path[count] = '\0';
|
||||
std::string path(exe_path);
|
||||
return path.substr(0, path.find_last_of('/'));
|
||||
}
|
||||
|
||||
uint32_t grapheme_strlen(const char *s) {
|
||||
if (!s)
|
||||
return 0;
|
||||
uint32_t count = 0;
|
||||
const char *p = s;
|
||||
while (*p) {
|
||||
uint32_t next = grapheme_next_character_break_utf8(p, UINT32_MAX);
|
||||
if (!next)
|
||||
next = 1;
|
||||
p += next;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t get_visual_col_from_bytes(const char *line, uint32_t byte_limit) {
|
||||
if (!line)
|
||||
return 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t current_byte = 0;
|
||||
uint32_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
len--;
|
||||
while (current_byte < byte_limit && current_byte < len) {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(line + current_byte,
|
||||
len - current_byte);
|
||||
if (current_byte + inc > byte_limit)
|
||||
break;
|
||||
current_byte += inc;
|
||||
visual_col++;
|
||||
}
|
||||
return visual_col;
|
||||
}
|
||||
|
||||
uint32_t get_bytes_from_visual_col(const char *line,
|
||||
uint32_t target_visual_col) {
|
||||
if (!line)
|
||||
return 0;
|
||||
uint32_t visual_col = 0;
|
||||
uint32_t current_byte = 0;
|
||||
uint32_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
len--;
|
||||
while (visual_col < target_visual_col && current_byte < len) {
|
||||
uint32_t inc = grapheme_next_character_break_utf8(line + current_byte,
|
||||
len - current_byte);
|
||||
current_byte += inc;
|
||||
visual_col++;
|
||||
}
|
||||
return current_byte;
|
||||
}
|
||||
|
||||
void log(const char *fmt, ...) {
|
||||
FILE *fp = fopen("/tmp/log.txt", "a");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vfprintf(fp, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fputc('\n', fp);
|
||||
fclose(fp);
|
||||
}
|
||||
Reference in New Issue
Block a user