Switch to c++
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,7 +7,10 @@
|
|||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
tests/tmp_*
|
tests/*
|
||||||
|
!tests/.keep
|
||||||
|
|
||||||
builds/*
|
builds/*
|
||||||
!builds/.keep
|
!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)
|
def ctrl_key(k)
|
||||||
k.ord & 0x1F
|
k.ord & 0x1F
|
||||||
end
|
end
|
||||||
@@ -6,8 +15,9 @@ end
|
|||||||
KEY_TYPE = {
|
KEY_TYPE = {
|
||||||
0 => :char,
|
0 => :char,
|
||||||
1 => :special,
|
1 => :special,
|
||||||
2 => :mouse
|
2 => :mouse,
|
||||||
}
|
3 => :none
|
||||||
|
}.freeze
|
||||||
|
|
||||||
# Special keys
|
# Special keys
|
||||||
SPECIAL_KEY = {
|
SPECIAL_KEY = {
|
||||||
@@ -16,10 +26,10 @@ SPECIAL_KEY = {
|
|||||||
2 => :left,
|
2 => :left,
|
||||||
3 => :right,
|
3 => :right,
|
||||||
4 => :delete
|
4 => :delete
|
||||||
}
|
}.freeze
|
||||||
|
|
||||||
# Control key
|
# Control key
|
||||||
KEY_ESC = "\x1b"
|
KEY_ESC = "\x1b".freeze
|
||||||
|
|
||||||
# Mouse states
|
# Mouse states
|
||||||
MOUSE_STATE = {
|
MOUSE_STATE = {
|
||||||
@@ -27,7 +37,7 @@ MOUSE_STATE = {
|
|||||||
1 => :release,
|
1 => :release,
|
||||||
2 => :drag,
|
2 => :drag,
|
||||||
3 => :scroll
|
3 => :scroll
|
||||||
}
|
}.freeze
|
||||||
|
|
||||||
# Mouse buttons
|
# Mouse buttons
|
||||||
MOUSE_BUTTON = {
|
MOUSE_BUTTON = {
|
||||||
@@ -36,7 +46,7 @@ MOUSE_BUTTON = {
|
|||||||
2 => :right,
|
2 => :right,
|
||||||
3 => :scroll,
|
3 => :scroll,
|
||||||
4 => :none
|
4 => :none
|
||||||
}
|
}.freeze
|
||||||
|
|
||||||
# Scroll directions
|
# Scroll directions
|
||||||
SCROLL_DIR = {
|
SCROLL_DIR = {
|
||||||
@@ -45,12 +55,13 @@ SCROLL_DIR = {
|
|||||||
2 => :left,
|
2 => :left,
|
||||||
3 => :right,
|
3 => :right,
|
||||||
4 => :none
|
4 => :none
|
||||||
}
|
}.freeze
|
||||||
|
|
||||||
# Modifiers
|
# Modifiers
|
||||||
MODIFIER = {
|
MODIFIER = {
|
||||||
|
0 => :none,
|
||||||
1 => :alt,
|
1 => :alt,
|
||||||
2 => :cntrl,
|
2 => :cntrl,
|
||||||
3 => :cntrl_alt,
|
3 => :cntrl_alt,
|
||||||
4 => :shift
|
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
|
#pragma once
|
||||||
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
@@ -41,6 +43,10 @@
|
|||||||
#define CNTRL_ALT 3
|
#define CNTRL_ALT 3
|
||||||
#define SHIFT 4
|
#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 {
|
enum CellFlags : uint8_t {
|
||||||
CF_NONE = 0,
|
CF_NONE = 0,
|
||||||
CF_ITALIC = 1 << 0,
|
CF_ITALIC = 1 << 0,
|
||||||
@@ -49,12 +55,17 @@ enum CellFlags : uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ScreenCell {
|
struct ScreenCell {
|
||||||
std::string utf8; // empty => no content
|
std::string utf8 = std::string(""); // empty => no content
|
||||||
uint32_t fg = 0;
|
uint32_t fg = 0;
|
||||||
uint32_t bg = 0;
|
uint32_t bg = 0;
|
||||||
uint8_t flags = CF_NONE;
|
uint8_t flags = CF_NONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Coord {
|
||||||
|
uint32_t row;
|
||||||
|
uint32_t col;
|
||||||
|
};
|
||||||
|
|
||||||
struct KeyEvent {
|
struct KeyEvent {
|
||||||
uint8_t key_type;
|
uint8_t key_type;
|
||||||
|
|
||||||
@@ -71,32 +82,25 @@ struct KeyEvent {
|
|||||||
uint8_t mouse_modifier;
|
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> screen; // size rows*cols
|
||||||
extern std::vector<ScreenCell> old_screen;
|
extern std::vector<ScreenCell> old_screen;
|
||||||
extern std::mutex screen_mutex;
|
extern std::mutex screen_mutex;
|
||||||
|
extern std::atomic<bool> running;
|
||||||
|
|
||||||
struct coords {
|
|
||||||
int row;
|
|
||||||
int col;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
void get_terminal_size();
|
void get_terminal_size();
|
||||||
void die(const char *s);
|
void die(const char *s);
|
||||||
void enable_raw_mode();
|
void enable_raw_mode();
|
||||||
void disable_raw_mode();
|
void disable_raw_mode();
|
||||||
void start_screen();
|
Coord start_screen();
|
||||||
void end_screen();
|
void end_screen();
|
||||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
void update(uint32_t row, uint32_t col, const char *utf8, uint32_t fg,
|
||||||
uint8_t flags);
|
uint32_t bg, uint8_t flags);
|
||||||
void set_cursor(int row, int col, bool show_cursor_param);
|
void set_cursor(int row, int col, int show_cursor_param);
|
||||||
void render();
|
void render();
|
||||||
coords get_size();
|
Coord get_size();
|
||||||
|
|
||||||
int real_width(std::string str);
|
|
||||||
|
|
||||||
int read_input(char *buf, size_t buflen);
|
int read_input(char *buf, size_t buflen);
|
||||||
KeyEvent read_key_nonblock();
|
|
||||||
KeyEvent read_key();
|
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) {
|
int read_input(char *buf, size_t buflen) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
@@ -115,11 +115,9 @@ KeyEvent read_key_nonblock() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent read_key() {
|
KeyEvent read_key() {
|
||||||
KeyEvent ret;
|
while (true) {
|
||||||
while (1) {
|
KeyEvent ret = read_key_nonblock();
|
||||||
ret = read_key_nonblock();
|
|
||||||
if (ret.key_type != KEY_NONE)
|
if (ret.key_type != KEY_NONE)
|
||||||
return ret;
|
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
|
// includes
|
||||||
#include "../../libs/libgrapheme/grapheme.h"
|
extern "C" {
|
||||||
#include "../../libs/unicode_width/unicode_width.h"
|
#include "../libs/libgrapheme/grapheme.h"
|
||||||
#include "../headers/header.hpp"
|
#include "../libs/unicode_width/unicode_width.h"
|
||||||
|
}
|
||||||
|
#include "../include/ui.h"
|
||||||
|
|
||||||
struct termios orig_termios;
|
termios orig_termios;
|
||||||
|
|
||||||
int rows, cols;
|
uint32_t rows, cols;
|
||||||
bool show_cursor = false;
|
int show_cursor = 0;
|
||||||
std::vector<ScreenCell> screen;
|
std::vector<ScreenCell> screen;
|
||||||
std::vector<ScreenCell> old_screen;
|
std::vector<ScreenCell> old_screen;
|
||||||
std::mutex screen_mutex;
|
std::mutex screen_mutex;
|
||||||
|
|
||||||
int real_width(std::string str) {
|
int display_width(const char *str) {
|
||||||
if (!str.size())
|
if (!str)
|
||||||
|
return 0;
|
||||||
|
if (!strlen(str))
|
||||||
return 0;
|
return 0;
|
||||||
const char *p = str.c_str();
|
|
||||||
if (str[0] == '\t')
|
if (str[0] == '\t')
|
||||||
return 4;
|
return 4;
|
||||||
unicode_width_state_t state;
|
unicode_width_state_t state;
|
||||||
unicode_width_init(&state);
|
unicode_width_init(&state);
|
||||||
int width = 0;
|
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];
|
unsigned char c = str[j];
|
||||||
if (c < 128) {
|
if (c < 128) {
|
||||||
int char_width = unicode_width_process(&state, c);
|
int char_width = unicode_width_process(&state, c);
|
||||||
@@ -28,7 +31,7 @@ int real_width(std::string str) {
|
|||||||
width += char_width;
|
width += char_width;
|
||||||
} else {
|
} else {
|
||||||
uint_least32_t cp;
|
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) {
|
if (bytes > 1) {
|
||||||
int char_width = unicode_width_process(&state, cp);
|
int char_width = unicode_width_process(&state, cp);
|
||||||
if (char_width > 0)
|
if (char_width > 0)
|
||||||
@@ -37,6 +40,7 @@ int real_width(std::string str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +67,7 @@ void enable_raw_mode() {
|
|||||||
raw.c_oflag &= ~(OPOST);
|
raw.c_oflag &= ~(OPOST);
|
||||||
raw.c_cflag |= (CS8);
|
raw.c_cflag |= (CS8);
|
||||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
raw.c_lflag |= ISIG;
|
||||||
raw.c_cc[VMIN] = 0;
|
raw.c_cc[VMIN] = 0;
|
||||||
raw.c_cc[VTIME] = 0;
|
raw.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
@@ -74,7 +79,7 @@ void enable_raw_mode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disable_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());
|
write(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
|
||||||
perror("tcsetattr");
|
perror("tcsetattr");
|
||||||
@@ -82,30 +87,40 @@ void disable_raw_mode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_screen() {
|
Coord start_screen() {
|
||||||
enable_raw_mode();
|
enable_raw_mode();
|
||||||
get_terminal_size();
|
get_terminal_size();
|
||||||
screen.assign(rows * cols, {}); // allocate & zero-init
|
screen.assign(rows * cols, {}); // allocate & zero-init
|
||||||
old_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 end_screen() { disable_raw_mode(); }
|
||||||
|
|
||||||
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
Coord get_size() { return {rows, cols}; }
|
||||||
uint8_t flags) {
|
|
||||||
if (row < 0 || row >= rows || col < 0 || col >= 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;
|
return;
|
||||||
|
|
||||||
int idx = row * cols + col;
|
uint32_t idx = row * cols + col;
|
||||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
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].fg = fg;
|
||||||
screen[idx].bg = bg;
|
screen[idx].bg = bg;
|
||||||
screen[idx].flags = flags;
|
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() {
|
void render() {
|
||||||
static bool first_render = true;
|
static bool first_render = true;
|
||||||
@@ -114,147 +129,135 @@ void render() {
|
|||||||
bool current_italic = false;
|
bool current_italic = false;
|
||||||
bool current_bold = false;
|
bool current_bold = false;
|
||||||
bool current_underline = false;
|
bool current_underline = false;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(screen_mutex);
|
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||||
|
|
||||||
std::string out;
|
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);
|
out.reserve(static_cast<size_t>(rows) * static_cast<size_t>(cols) * 4 + 256);
|
||||||
|
|
||||||
// save cursor + hide
|
|
||||||
out += "\x1b[s\x1b[?25l";
|
out += "\x1b[s\x1b[?25l";
|
||||||
|
|
||||||
if (first_render) {
|
if (first_render) {
|
||||||
out += "\x1b[2J\x1b[H";
|
out += "\x1b[2J\x1b[H";
|
||||||
first_render = false;
|
first_render = false;
|
||||||
}
|
}
|
||||||
|
for (uint32_t row = 0; row < rows; ++row) {
|
||||||
for (int row = 0; row < rows; ++row) {
|
|
||||||
int first_change_col = -1;
|
int first_change_col = -1;
|
||||||
int last_change_col = -1;
|
int last_change_col = -1;
|
||||||
|
for (uint32_t col = 0; col < cols; ++col) {
|
||||||
// detect change span in this row
|
uint32_t idx = row * cols + col;
|
||||||
for (int col = 0; col < cols; ++col) {
|
|
||||||
int idx = row * cols + col;
|
|
||||||
ScreenCell &old_cell = old_screen[idx];
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
ScreenCell &new_cell = screen[idx];
|
ScreenCell &new_cell = screen[idx];
|
||||||
|
bool content_changed = old_cell.utf8 != new_cell.utf8;
|
||||||
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 style_changed =
|
bool style_changed =
|
||||||
(old_cell.fg != new_cell.fg) || (old_cell.bg != new_cell.bg) ||
|
(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_ITALIC) != (new_cell.flags & CF_ITALIC)) ||
|
||||||
((old_cell.flags & CF_BOLD) != (new_cell.flags & CF_BOLD)) ||
|
((old_cell.flags & CF_BOLD) != (new_cell.flags & CF_BOLD)) ||
|
||||||
((old_cell.flags & CF_UNDERLINE) != (new_cell.flags & CF_UNDERLINE));
|
((old_cell.flags & CF_UNDERLINE) != (new_cell.flags & CF_UNDERLINE));
|
||||||
|
|
||||||
if (content_changed || style_changed) {
|
if (content_changed || style_changed) {
|
||||||
if (first_change_col == -1)
|
if (first_change_col == -1)
|
||||||
first_change_col = col;
|
first_change_col = col;
|
||||||
last_change_col = col;
|
last_change_col = col;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first_change_col == -1)
|
if (first_change_col == -1)
|
||||||
continue;
|
continue;
|
||||||
|
char buf[64];
|
||||||
// move cursor once to the start of change region
|
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, first_change_col + 1);
|
||||||
char buf[32];
|
out.append(buf);
|
||||||
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1,
|
|
||||||
first_change_col + 1);
|
|
||||||
out.append(buf, n);
|
|
||||||
|
|
||||||
// render changed region
|
|
||||||
for (int col = first_change_col; col <= last_change_col; ++col) {
|
for (int col = first_change_col; col <= last_change_col; ++col) {
|
||||||
int idx = row * cols + col;
|
int idx = row * cols + col;
|
||||||
ScreenCell &old_cell = old_screen[idx];
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
ScreenCell &new_cell = screen[idx];
|
ScreenCell &new_cell = screen[idx];
|
||||||
|
|
||||||
// foreground change
|
|
||||||
if (current_fg != new_cell.fg) {
|
if (current_fg != new_cell.fg) {
|
||||||
if (new_cell.fg) {
|
if (new_cell.fg) {
|
||||||
char fb[64];
|
char fb[64];
|
||||||
int m = snprintf(
|
snprintf(fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm",
|
||||||
fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm", (new_cell.fg >> 16) & 0xFF,
|
(new_cell.fg >> 16) & 0xFF, (new_cell.fg >> 8) & 0xFF,
|
||||||
(new_cell.fg >> 8) & 0xFF, (new_cell.fg >> 0) & 0xFF);
|
(new_cell.fg >> 0) & 0xFF);
|
||||||
out.append(fb, m);
|
out.append(fb);
|
||||||
} else {
|
} else {
|
||||||
out += "\x1b[39m";
|
out += "\x1b[39m";
|
||||||
}
|
}
|
||||||
current_fg = new_cell.fg;
|
current_fg = new_cell.fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// background change
|
|
||||||
if (current_bg != new_cell.bg) {
|
if (current_bg != new_cell.bg) {
|
||||||
if (new_cell.bg) {
|
if (new_cell.bg) {
|
||||||
char bb[64];
|
char bb[64];
|
||||||
int m = snprintf(
|
snprintf(bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm",
|
||||||
bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm", (new_cell.bg >> 16) & 0xFF,
|
(new_cell.bg >> 16) & 0xFF, (new_cell.bg >> 8) & 0xFF,
|
||||||
(new_cell.bg >> 8) & 0xFF, (new_cell.bg >> 0) & 0xFF);
|
(new_cell.bg >> 0) & 0xFF);
|
||||||
out.append(bb, m);
|
out.append(bb);
|
||||||
} else {
|
} else {
|
||||||
out += "\x1b[49m";
|
out += "\x1b[49m";
|
||||||
}
|
}
|
||||||
current_bg = new_cell.bg;
|
current_bg = new_cell.bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// italic
|
|
||||||
bool italic = (new_cell.flags & CF_ITALIC) != 0;
|
bool italic = (new_cell.flags & CF_ITALIC) != 0;
|
||||||
if (italic != current_italic) {
|
if (italic != current_italic) {
|
||||||
out += italic ? "\x1b[3m" : "\x1b[23m";
|
out += italic ? "\x1b[3m" : "\x1b[23m";
|
||||||
current_italic = italic;
|
current_italic = italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bold
|
|
||||||
bool bold = (new_cell.flags & CF_BOLD) != 0;
|
bool bold = (new_cell.flags & CF_BOLD) != 0;
|
||||||
if (bold != current_bold) {
|
if (bold != current_bold) {
|
||||||
out += bold ? "\x1b[1m" : "\x1b[22m";
|
out += bold ? "\x1b[1m" : "\x1b[22m";
|
||||||
current_bold = bold;
|
current_bold = bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
// underline
|
|
||||||
bool underline = (new_cell.flags & CF_UNDERLINE) != 0;
|
bool underline = (new_cell.flags & CF_UNDERLINE) != 0;
|
||||||
if (underline != current_underline) {
|
if (underline != current_underline) {
|
||||||
out += underline ? "\x1b[4m" : "\x1b[24m";
|
out += underline ? "\x1b[4m" : "\x1b[24m";
|
||||||
current_underline = underline;
|
current_underline = underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
// content
|
|
||||||
if (!new_cell.utf8.empty()) {
|
if (!new_cell.utf8.empty()) {
|
||||||
if (new_cell.utf8[0] == '\t')
|
if (new_cell.utf8[0] == '\t') {
|
||||||
out.append(" ");
|
out.append(" ");
|
||||||
else
|
} else {
|
||||||
out.append(new_cell.utf8);
|
// 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 {
|
} else {
|
||||||
out.append(1, ' ');
|
out.append(1, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy new -> old (std::string assignment, no strdup/free)
|
|
||||||
old_cell.utf8 = new_cell.utf8;
|
old_cell.utf8 = new_cell.utf8;
|
||||||
old_cell.fg = new_cell.fg;
|
old_cell.fg = new_cell.fg;
|
||||||
old_cell.bg = new_cell.bg;
|
old_cell.bg = new_cell.bg;
|
||||||
old_cell.flags = new_cell.flags;
|
old_cell.flags = new_cell.flags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final reset + restore cursor + show cursor
|
|
||||||
out += "\x1b[0m";
|
out += "\x1b[0m";
|
||||||
out += "\x1b[u";
|
out += "\x1b[u";
|
||||||
if (show_cursor)
|
if (show_cursor)
|
||||||
out += "\x1b[?25h";
|
out += "\x1b[?25h";
|
||||||
|
const char *ptr = out.data();
|
||||||
// single syscall to write the whole frame
|
size_t remaining = out.size();
|
||||||
ssize_t written = write(STDOUT_FILENO, out.data(), out.size());
|
while (remaining > 0) {
|
||||||
(void)written; // you may check for errors in debug builds
|
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];
|
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;
|
show_cursor = show_cursor_param;
|
||||||
write(STDOUT_FILENO, buf, n);
|
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