Compare commits

..
25 Commits
Author SHA1 Message Date
syedm 3346bc9f83 Fix a few bugs.
- Moving lines after themselves didnt work.
- moving empty lines deleted them.
- "w" didnt set state to unmodified in generic buffers.
- in "hl" aligned the numbers (by digits)
2026-09-11 22:35:06 +01:00
syedm a8d51dc0b6 Make ` command behave like irb with coloring
- And `` command to run selected lines, but removing ``` as
  that can be done by making a new buffer and writing in that.
2026-09-10 07:21:22 +01:00
syedm 118934a393 Make mruby raii cleanup, and fix cleanup bug.
- Functions were cleaned up after mruby, which was wrong.
- Becuase functions could have stored a reference to a Block which refers to mruby.
- And so it would crash on release builds at exit if custom functions were used.
2026-09-08 23:12:52 +01:00
syedm f5ec5516fa Cleanup flake.nix 2026-09-08 22:44:58 +01:00
syedm c325409dd0 Add basic runtime function operations. 2026-09-08 22:34:45 +01:00
syedm 0e4422b0c8 Fix issue with mruby linking. 2026-09-08 19:10:52 +01:00
syedm bb3ce7549a Add basic scripting support.
- Fix certain bugs in ruby parser
- And a lot more cleanup/minor fixes.
2026-09-08 18:07:39 +01:00
syedm a5794d9ab3 Cleanup. 2026-09-08 11:56:26 +01:00
syedm d6d3f31326 Add cancel buffer (to store cancelled operations.) 2026-09-08 11:50:17 +01:00
syedm 51676362b9 Fix cancellation procedure. 2026-09-08 07:06:38 +01:00
syedm 0760209417 Major updates/cleanup. 2026-09-08 07:00:13 +01:00
syedm 0df03f418a Improve colors for command highlighting. 2026-09-03 18:12:16 +01:00
syedm a276bf60da Add command syntax highlighting. 2026-09-03 16:46:13 +01:00
syedm 3bfebe30ce Add buffer listing function. 2026-09-03 14:40:11 +01:00
syedm 46bdb8cd0f Fix io system and highlighting. 2026-09-03 14:23:05 +01:00
syedm 662df68ab4 Add help and history prune commands
- And other minor fixes/cleanup.
2026-09-03 11:40:07 +01:00
syedm cd9583ec34 Honor suppress mode.
- Allow P command to create a new prompt string.
2026-09-03 09:53:14 +01:00
syedm 0104f96d51 Add comments and echo commands. 2026-09-03 09:52:33 +01:00
syedm abbfcc352a Support piped input. 2026-09-03 09:52:13 +01:00
syedm f8975a60d8 Fix regex and current() system. 2026-09-03 07:42:50 +01:00
syedm 33dc9ad507 Fix bugs with history setup. 2026-09-03 06:59:55 +01:00
syedm 956bdd6039 Add history support, and fix parsing system. 2026-09-03 00:30:47 +01:00
syedm 151817973f Ruby parser: support heredocs for block navigation 2026-08-31 13:06:41 +01:00
syedm 25f4d8efa8 Grow text mode properly during pastes. 2026-08-31 13:00:14 +01:00
syedm 569b69263a Add x function and other bug fixes. 2026-08-31 12:56:14 +01:00
57 changed files with 3737 additions and 1402 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ CFLAGS_RELEASE :=\
-fomit-frame-pointer -DNDEBUG -s \
-I./include -I./libs/unicode_width
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS) $(C_SANITIZER)
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
+4
View File
@@ -58,3 +58,7 @@ It should support:
Not done yet.
### TODO immediately:
- Make "g" command work.
- properly handle escapes for %q ' etc in ruby parser (rn everything is escapable.)
+2 -4
View File
@@ -70,9 +70,8 @@
buildPhase = ''
make \
MRBC=${mruby}/bin/mrbc \
MRUBY_CFLAGS=-I${mruby}/include \
MRUBY_LIBS=-L${mruby}/lib -lmruby
MRUBY_CFLAGS="-I${mruby}/include" \
MRUBY_LIBS="-L${mruby}/lib -lmruby"
'';
installPhase = ''
@@ -110,7 +109,6 @@
export CC="ccache $CC"
export CXX="ccache $CXX"
export MRBC=${mruby}/bin/mrbc
export MRUBY_CFLAGS=-I${mruby}/include
export MRUBY_LIBS="-L${mruby}/lib -lmruby"
'';
+8 -5
View File
@@ -6,6 +6,7 @@
#include "internal/functions/suffixes.h"
#include "internal/io/io.h"
#include "internal/marks/marks.h"
#include "internal/scripting/ruby.h"
#include "internal/theme/theme.h"
#include "internal/ui/command.h"
#include "internal/ui/text_mode.h"
@@ -13,19 +14,22 @@
namespace bed {
struct BEd {
internal::scripting::RubyState mrb;
internal::trie::Trie<internal::functions::Function> functions;
internal::functions::Function no_op;
internal::functions::Function eof_op;
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
internal::theme::Theme theme;
std::unordered_map<std::string, internal::syntax::Language> languages;
internal::io::IO &io;
std::unordered_map<std::string, internal::syntax::Language *> languages;
internal::io::IO io;
internal::vase::AppendStorage append{"/tmp"};
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
bool help_mode = false;
bool prompt_mode = true;
std::function<std::string(BEd &)> prompt = nullptr;
bool suppress_mode = false;
bool color_mode = false;
bool temporary_current = false;
std::string last_help = "";
std::string last_regex = "";
@@ -33,13 +37,11 @@ struct BEd {
std::string last_replacement = "";
std::string last_shell = "";
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
internal::buffer::Range prev_1;
internal::buffer::Range prev_2;
internal::marks::MarksEngine marks;
BEd(std::vector<std::string> args, internal::io::IO &io);
BEd(std::vector<std::string> args);
~BEd();
internal::buffer::Buffer &buffer(const std::string &);
@@ -47,6 +49,7 @@ struct BEd {
internal::buffer::Range &prev();
void mark(uint8_t, internal::buffer::Line);
void print_help();
void handle(std::string_view cmd, bool eof);
void run();
void suffix_handle(char s);
-13
View File
@@ -15,17 +15,4 @@ struct fatal_error : std::runtime_error {
struct ed_error : std::runtime_error {
ed_error(std::string msg) : std::runtime_error(msg) {}
};
struct Highlight {
enum : uint8_t {
None = 0,
Bold = 1 << 0,
Italic = 1 << 1,
Strikethrough = 1 << 2,
Underline = 1 << 3,
};
uint32_t fg;
uint32_t bg;
uint8_t flags;
};
} // namespace bed
+2 -3
View File
@@ -1,6 +1,5 @@
#pragma once
#include "clip.h"
#include "./types/clip.h"
#include "./types/generic.h"
#include "decl.h"
#include "generic.h"
#include "pch.h"
+4 -2
View File
@@ -10,7 +10,7 @@ namespace bed::internal::buffer {
struct Buffer {
enum struct Kind : uint8_t {
Generic,
Null,
History,
Clip,
Cancel,
Shell,
@@ -26,7 +26,7 @@ struct Buffer {
std::string name;
explicit Buffer(std::string name, Kind kind)
: kind(kind), state(Unmodified), name(name) {};
: kind(kind), state(Unmodified), name(std::move(name)) {};
virtual ~Buffer() = default;
virtual bool waste() = 0;
@@ -34,6 +34,7 @@ struct Buffer {
virtual uint64_t bytes() = 0;
virtual void set_filename(std::filesystem::path path) = 0;
virtual std::filesystem::path filename() = 0;
virtual void saved_hook() = 0;
virtual void load(BEd &ctx, vase::Shard *text) = 0;
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
virtual void substitute(
@@ -43,6 +44,7 @@ struct Buffer {
virtual void join(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void append(BEd &ctx, vase::Shard *text, uint64_t line) = 0;
virtual void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) = 0;
virtual void print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
-39
View File
@@ -1,39 +0,0 @@
#pragma once
#include "decl.h"
#include "pch.h"
namespace bed::internal::buffer {
struct GenericBuffer : Buffer {
vase::Shard *root;
std::filesystem::path save_path{};
std::string language{};
std::optional<syntax::Parser> parser{};
GenericBuffer(std::string name)
: Buffer(name, Kind::Generic), root(nullptr) {};
~GenericBuffer();
bool waste() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) override;
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
};
} // namespace bed::internal::buffer
@@ -1,7 +1,6 @@
#pragma once
#include "decl.h"
#include "pch.h"
#include "../decl.h"
namespace bed::internal::buffer {
struct ClipBuffer : Buffer {
@@ -11,6 +10,7 @@ struct ClipBuffer : Buffer {
void clip_write(vase::Shard *text);
bool waste() override;
void saved_hook() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override;
@@ -24,6 +24,7 @@ struct ClipBuffer : Buffer {
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "../decl.h"
#include "readonly.h"
#include "shard.h"
namespace bed::internal::buffer {
struct HistoryItem {
syntax::ParserSnapshot parse_state;
vase::Shard *text;
std::chrono::system_clock::time_point timestamp;
std::string summary;
};
struct GenericBuffer : ShardBuffer {
uint64_t base_version{0};
std::chrono::system_clock::time_point timestamp{std::chrono::system_clock::now()};
std::string action{"Created buffer."};
std::filesystem::path save_path{};
std::vector<HistoryItem> undo_stack;
std::vector<HistoryItem> redo_stack;
GenericBuffer(std::string name)
: ShardBuffer(name, nullptr, nullptr, Kind::Generic) {}
~GenericBuffer();
void language(BEd &ctx, std::string name);
void list_history(BEd &ctx);
ReadonlyBuffer *get_history(uint64_t version);
void snapshot(std::string action);
bool undo(BEd &ctx);
bool redo(BEd &ctx);
uint64_t prune(int);
void saved_hook() override;
bool waste() override;
void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) override;
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) override;
};
} // namespace bed::internal::buffer
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "../decl.h"
#include "shard.h"
namespace bed::internal::buffer {
struct ReadonlyBuffer : ShardBuffer {
bool useless = true;
ReadonlyBuffer(
std::string name, vase::Shard *root,
const syntax::ParserSnapshot &snapshot
) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {}
void saved_hook() override {}
bool waste() override {
return useless;
}
void load(BEd &, vase::Shard *) override {
throw ed_error("This buffer is read-only.");
}
void set_filename(std::filesystem::path) override {}
std::filesystem::path filename() override {
return {};
}
void substitute(BEd &, uint64_t, uint64_t, std::string &, std::string &, std::string &) override {
throw ed_error("This buffer is read-only.");
}
void join(BEd &, uint64_t, uint64_t) override {
throw ed_error("This buffer is read-only.");
}
void remove(BEd &, uint64_t, uint64_t) override {
throw ed_error("This buffer is read-only.");
}
void append(BEd &, vase::Shard *, uint64_t) override {
throw ed_error("This buffer is read-only.");
}
void replace(BEd &, vase::Shard *, uint64_t, uint64_t) override {
throw ed_error("This buffer is read-only.");
}
};
} // namespace bed::internal::buffer
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "../decl.h"
namespace bed::internal::buffer {
struct ShardBuffer : Buffer {
vase::Shard *root;
syntax::ParserSnapshot parse{};
ShardBuffer(std::string name, vase::Shard *root, syntax::Language *language, Kind kind)
: Buffer(std::move(name), kind), root(root) {
vase::Shard::retain(root);
if (language)
parse = syntax::make_parser(root, lines(), language);
}
ShardBuffer(
std::string name,
vase::Shard *root,
const syntax::ParserSnapshot &snapshot,
Kind kind
) : Buffer(std::move(name), kind),
root(root),
parse(syntax::retain(snapshot)) {
vase::Shard::retain(root);
}
~ShardBuffer() {
vase::Shard::release(root);
syntax::release(parse);
}
uint64_t lines() override;
uint64_t bytes() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
};
} // namespace bed::internal::buffer
+18 -3
View File
@@ -2,6 +2,7 @@
#include "definitions.h"
#include "pch.h"
#include "tokens.h"
namespace bed::internal::io {
struct KeyEvent {
@@ -57,13 +58,27 @@ struct IO {
static void enable_raw();
static volatile std::atomic_bool resized;
static void handle_sigwinch(int);
BEd &bed;
IO();
static enum struct Mode {
PIPE,
TERMINAL
} mode;
std::string pipe_input;
std::deque<char> input_queue;
IO(BEd &);
~IO();
IO(const IO &) = delete;
IO &operator=(const IO &) = delete;
bool interactive() const {
return mode == Mode::TERMINAL;
}
void apply(const io::Token::Kind &t);
void reset();
void enable_mouse();
void disable_mouse();
@@ -71,14 +86,14 @@ struct IO {
std::pair<uint16_t, uint16_t> cursor_position();
void move_cursor(uint16_t row, uint16_t col);
std::pair<std::string, bool> read_pipe();
KeyEvent read_key();
void write(const char *, uint64_t);
void write(std::string_view);
void write_line(std::string_view);
void run_pty(const std::string &);
std::deque<char> input_queue;
KeyEvent::ReadResult get_next_byte(char &out);
void enqueue_bytes(const std::string &bytes);
static int utf8_seq_len(uint8_t byte);
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::io {
struct Token {
uint64_t start;
uint64_t end;
enum Kind : uint8_t {
TempCurrent,
BufferName,
AddressSeperator,
AddressSymbol,
Number,
Mark,
RubyFunction,
RubyArg,
Function,
Shell,
File,
Suffix,
Color1,
Color2,
Color3,
Color4,
Color5,
Warning,
Data,
Shebang,
Comment,
Error,
String,
Escape,
Interpolation,
Regexp,
True,
False,
Char,
Keyword,
KeywordOperator,
Operator,
Namespace,
Class,
Module,
Type,
Constant,
VariableInstance,
VariableGlobal,
Annotation,
Directive,
Label,
Brace1,
Brace2,
Brace3,
Brace4,
Brace5,
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
Blockquote,
List,
ListItem,
Code,
LanguageName,
LinkLabel,
ImageLabel,
Link,
Table,
TableHeader,
Italic,
Bold,
Underline,
Strikethrough,
HorizontalRule,
Tag,
Attribute,
CheckDone,
CheckNotDone,
Count
} type;
};
struct Highlight {
enum : uint8_t {
None = 0,
Bold = 1 << 0,
Italic = 1 << 1,
Strikethrough = 1 << 2,
Underline = 1 << 3,
};
uint32_t fg;
uint32_t bg;
uint8_t flags;
};
} // namespace bed::internal::io
+1 -1
View File
@@ -22,7 +22,7 @@ struct MarksEngine {
continue;
if (marks[i].number == UINT64_MAX)
continue;
if (marks[i].number >= start)
if (marks[i].number > start)
marks[i].number += count;
}
}
+6 -3
View File
@@ -85,14 +85,17 @@ struct Parser {
std::string_view cmd;
uint16_t i;
Command *command;
std::vector<ui::Token> *tokens;
std::vector<io::Token> *tokens;
CompletionContext *completion;
explicit Parser(
std::string_view cmd, BEd &bed, Command *command,
std::vector<ui::Token> *tokens, CompletionContext *completion
std::vector<io::Token> *tokens, CompletionContext *completion
);
void token(io::Token::Kind);
void end_token();
char peek(uint16_t = 0); // == \0 if at eof.
std::string_view peek_str(uint16_t = UINT16_MAX);
void advance(uint16_t = 1);
@@ -107,7 +110,7 @@ struct Parser {
void operation();
void parse();
static std::vector<io::Token> get_highlight(std::string_view cmd, BEd &bed);
static Command get_command(std::string_view, BEd &);
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
};
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::scripting {
struct RubyState {
mrb_state *state = nullptr;
int arena = 0;
explicit RubyState(BEd *ctx) : state(mrb_open()) {
if (!state)
throw fatal_error("Failed to initialize mruby.", 1);
state->ud = ctx;
arena = mrb_gc_arena_save(state);
}
~RubyState() {
state->ud = nullptr;
if (state) {
mrb_gc_arena_restore(state, arena);
mrb_full_gc(state);
mrb_close(state);
}
}
RubyState(const RubyState &) = delete;
RubyState &operator=(const RubyState &) = delete;
};
struct Block {
mrb_state *mrb = nullptr;
mrb_value proc = mrb_nil_value();
Block(mrb_state *mrb, mrb_value proc) noexcept
: mrb(mrb), proc(proc) {
mrb_gc_register(mrb, proc);
}
Block() noexcept = default;
~Block() noexcept {
if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc);
}
Block(const Block &other) noexcept
: mrb(other.mrb), proc(other.proc) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
Block &operator=(const Block &other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
return *this;
}
Block(Block &&other) noexcept
: mrb(other.mrb), proc(other.proc) {
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
Block &operator=(Block &&other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
return *this;
}
void set_proc(mrb_state *mrb_, mrb_value new_proc) {
if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc);
mrb = mrb_;
proc = new_proc;
mrb_gc_register(mrb, proc);
}
mrb_value call(int argc = 0, mrb_value *argv = nullptr) const {
if (mrb_nil_p(proc))
return mrb_nil_value();
mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv);
if (!mrb->exc)
return result;
mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error;
if (mrb_string_p(msg))
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
auto *fatal_class = mrb_class_get(mrb, "FatalError");
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
mrb_value code =
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
mrb->exc = nullptr;
int c = 1;
if (mrb_fixnum_p(code))
c = mrb_fixnum(code);
throw fatal_error(error, c);
}
auto *ed_class = mrb_class_get(mrb, "EdError");
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
mrb->exc = nullptr;
throw ed_error(error);
}
mrb->exc = nullptr;
throw ed_error("Ruby Exception: " + error);
}
};
void register_basic(BEd &ctx);
std::string run(BEd &ctx, const std::string &str);
}; // namespace bed::internal::scripting
+113 -70
View File
@@ -1,73 +1,11 @@
#pragma once
#include "internal/io/tokens.h"
#include "internal/trie/trie.h"
#include "internal/vase/vase.h"
#include "pch.h"
namespace bed::internal::syntax {
struct Token {
uint32_t start;
uint32_t end;
enum Kind : uint8_t {
Data,
Shebang,
Comment,
Error,
String,
Escape,
Interpolation,
Regexp,
Number,
True,
False,
Char,
Keyword,
KeywordOperator,
Operator,
Function,
Namespace,
Class,
Module,
Type,
Constant,
VariableInstance,
VariableGlobal,
Annotation,
Directive,
Label,
Brace1,
Brace2,
Brace3,
Brace4,
Brace5,
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
Blockquote,
List,
ListItem,
Code,
LanguageName,
LinkLabel,
ImageLabel,
Link,
Table,
TableHeader,
Italic,
Bold,
Underline,
Strikethrough,
HorizontalRule,
Tag,
Attribute,
CheckDone,
CheckNotDone,
Count
} type;
};
struct ParseEvent {
uint8_t closing;
ParseEvent(uint8_t t) : closing(t) {}
@@ -75,7 +13,11 @@ struct ParseEvent {
struct Language {
std::function<void *()> none_state;
std::function<void(void **, std::string_view, bool, std::vector<Token> *, std::vector<ParseEvent> *)> parse;
std::function<void(
void **, std::string_view,
bool, std::vector<io::Token> *, std::vector<ParseEvent> *
)>
parse;
std::function<void *(void *)> copy;
std::function<bool(void *, void *)> equal;
std::function<void(void *)> destroy;
@@ -85,35 +27,136 @@ struct ParseState {
static constexpr uint64_t BRANCH_BIT = 1ull << 63;
static constexpr uint64_t LINES_MASK = ~BRANCH_BIT;
uint64_t header;
uint16_t height;
std::atomic_uint16_t refs;
bool is_branch() const {
return header & BRANCH_BIT;
}
uint64_t lines() const {
return header & LINES_MASK;
}
explicit ParseState(bool branch, uint64_t lines, uint16_t height)
: height(height), refs(1) {
header = lines;
header |= branch * BRANCH_BIT;
};
static void retain(ParseState *node);
static void release(Language &lang, ParseState *node);
static ParseState *build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi);
static ParseState *splice(
Language &lang, ParseState *node, vase::Shard *vase,
uint64_t line, uint64_t original, uint64_t final
);
static ParseState *concat(Language &lang, ParseState *a, ParseState *b);
static void *state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line);
};
struct ParseStateBranch : ParseState {
ParseState *left;
ParseState *right;
ParseStateBranch(ParseState *l, ParseState *r)
: ParseState(
true, l->lines() + r->lines(),
1 + std::max(l->height, r->height)
),
left(l), right(r) {
retain(l);
retain(r);
}
};
struct ParseStateLeaf : ParseState {
void *state;
uint32_t n;
uint32_t cap;
uint16_t *blocks;
static constexpr uint64_t MAX_CHUNK = 512;
uint32_t n{0};
static constexpr uint16_t IS_CLOSING = 0x8000;
static constexpr uint16_t LINE_MASK = 0x7fff;
uint16_t *blocks{nullptr};
void *state;
ParseStateLeaf(void *state, uint64_t lines, uint32_t n, uint16_t *blocks_)
: ParseState(false, lines, 1), n(n), state(state) {
blocks = (uint16_t *)malloc(sizeof(uint16_t) * n);
memcpy(blocks, blocks_, sizeof(uint16_t) * n);
};
};
struct ParsePieceBuilder {
Language &lang;
std::vector<ParseState *> pieces;
std::vector<uint16_t> blocks;
void *piece_state{nullptr};
void *prev_state{nullptr};
uint64_t chunk_start{0};
uint64_t chunk_lines{0};
ParsePieceBuilder(Language &lang, uint64_t first_line, void *entry_state)
: lang(lang), chunk_start(first_line) {
prev_state = lang.copy(entry_state);
}
~ParsePieceBuilder() {
if (prev_state)
lang.destroy(prev_state);
if (piece_state)
lang.destroy(piece_state);
}
ParsePieceBuilder(const ParsePieceBuilder &) = delete;
ParsePieceBuilder &operator=(const ParsePieceBuilder &) = delete;
void add(void *state, uint64_t line, const std::vector<ParseEvent> &events) {
if (chunk_lines == 0) {
chunk_start = line;
piece_state = lang.copy(prev_state);
}
for (const auto &ev : events) {
blocks.push_back(
(ev.closing ? ParseStateLeaf::IS_CLOSING : 0)
| (line - chunk_start)
);
}
++chunk_lines;
if (prev_state)
lang.destroy(prev_state);
prev_state = lang.copy(state);
if (chunk_lines == ParseStateLeaf::MAX_CHUNK)
flush();
}
void flush() {
if (chunk_lines == 0)
return;
pieces.push_back(new ParseStateLeaf(
piece_state,
chunk_lines,
blocks.size(),
blocks.data()
));
piece_state = nullptr;
blocks.clear();
chunk_lines = 0;
}
ParseState *finish() {
flush();
if (pieces.empty())
return nullptr;
ParseState *root = ParseState::build(lang, pieces.data(), 0, pieces.size());
pieces.clear();
return root;
}
};
struct TreeCursor {
Language &lang;
ParseState *root;
ParseStateLeaf *leaf = nullptr;
ParseStateBranch *stack[64];
uint8_t depth = 0;
bool went_left[64];
TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative);
TreeCursor(
Language &lang, ParseState *root,
uint64_t target_line, uint64_t *relative
);
~TreeCursor();
TreeCursor(const TreeCursor &) = delete;
TreeCursor &operator=(const TreeCursor &) = delete;
void next();
void prev();
ParseState *prefix();
ParseState *suffix();
};
} // namespace bed::internal::syntax
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "decl.h"
namespace bed::internal::syntax {
struct MiniParser {
Language &lang;
void *start_state;
std::vector<std::pair<void *, std::vector<io::Token>>> lines;
explicit MiniParser(Language &lang, vase::Shard *vase, void *initial_state);
~MiniParser();
MiniParser(const MiniParser &) = delete;
MiniParser &operator=(const MiniParser &) = delete;
void dirty(vase::Shard *vase, uint64_t start, uint64_t count);
void insert(vase::Shard *vase, uint64_t start, uint64_t count);
void erase(vase::Shard *vase, uint64_t start, uint64_t count);
};
} // namespace bed::internal::syntax
+34 -36
View File
@@ -1,57 +1,55 @@
#pragma once
#include "decl.h"
#include "internal/vase/vase.h"
#include "pch.h"
namespace bed::internal::syntax {
struct Parser {
static constexpr uint64_t MAX_CHUNK = 512;
struct ParserSnapshot {
ParseState *root = nullptr;
Language *lang = nullptr;
};
ParseState *root;
Language lang;
bool in_edit = false;
bool dirty = false;
uint64_t dirty_start = 0;
uint64_t dirty_end = 0;
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
ParserSnapshot retain(const ParserSnapshot &snap);
void release(ParserSnapshot &snap);
Parser(vase::Shard *, uint64_t, Language);
~Parser();
Parser(const Parser &) = delete;
Parser &operator=(const Parser &) = delete;
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line);
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line);
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line);
void reset(vase::Shard *, uint64_t, Language);
void erase(vase::Shard *, uint64_t, uint64_t);
void insert(vase::Shard *, uint64_t, uint64_t);
void modify(vase::Shard *, uint64_t, uint64_t);
void begin_edit();
void erase(uint64_t start, uint64_t count);
void insert(uint64_t start, uint64_t count);
void end_edit(vase::Shard *vase);
void mark_dirty(uint64_t start, uint64_t end);
uint64_t next_closing(uint64_t line);
uint64_t prev_opening(uint64_t line);
std::pair<ParseState *, ParseState *> split_tree(ParseState *node, uint64_t line);
ParseState *join_tree(ParseState *a, ParseState *b);
struct Iterator {
Parser *p;
struct Iterator {
ParserSnapshot snap;
std::optional<vase::Iterator> it;
void *state;
uint64_t at;
std::vector<Token> tokens;
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
Iterator(uint64_t, Parser *, vase::Shard *);
Iterator(uint64_t, ParserSnapshot, vase::Shard *);
~Iterator();
Iterator(const Iterator &) = delete;
Iterator &operator=(const Iterator &) = delete;
Iterator(Iterator &&other);
Iterator &operator=(Iterator &&other);
void next();
};
std::optional<Iterator> get_hl(vase::Shard *, uint64_t);
};
std::optional<Iterator> get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target);
void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count);
void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count);
struct Edit {
ParserSnapshot *target;
bool dirty = false;
uint64_t dirty_start = 0;
uint64_t dirty_end = 0;
int64_t edit_delta = 0;
explicit Edit(ParserSnapshot &snap) : target(&snap) {}
void mark_dirty(uint64_t start, uint64_t end);
void insert(uint64_t start, uint64_t count);
void erase(uint64_t start, uint64_t count);
void commit(vase::Shard *vase);
};
} // namespace bed::internal::syntax
+1 -1
View File
@@ -104,6 +104,6 @@ struct RubyParser {
void ruby_parse(
void **v_state, std::string_view line, bool first_line,
std::vector<Token> *tokens, std::vector<ParseEvent> *events
std::vector<io::Token> *tokens, std::vector<ParseEvent> *events
);
} // namespace bed::internal::syntax::ruby
+5
View File
@@ -68,6 +68,11 @@ const static std::vector<std::string> builtins = {
};
const static std::vector<std::string> methods = {
// BEd methods.
"handle",
"register",
"unregister",
// Normal:
"abort",
"at_exit",
"binding",
+2 -2
View File
@@ -6,11 +6,11 @@
namespace bed::internal::theme {
struct Theme {
std::array<Highlight, internal::syntax::Token::Count> hl;
std::array<io::Highlight, io::Token::Count> hl;
Theme();
Highlight get(internal::syntax::Token token) const;
io::Highlight get(const io::Token::Kind &token) const;
static Theme default_theme();
static Theme from_name(std::string_view name);
+2 -25
View File
@@ -4,31 +4,6 @@
#include "pch.h"
namespace bed::internal::ui {
struct Token {
enum struct Type : uint8_t {
TempCurrent, // @
AddressSeperator, // ; ,
Address, // . $ % [ ] ^ ~
Offset, // +N -N + -
AddressRegex, // /re/ ?re?
AddressSymbol, // >s> <s< <s>
Number, // 10
Mark, // 'm
RubyFunction, // (func:arg)
RubyArg,
Function,
Any,
Shell,
Ruby,
File,
Regex,
Replacement,
Suffix
} type;
uint16_t start;
uint16_t end;
};
struct CommandIO {
std::string cmd;
uint16_t cursor;
@@ -41,6 +16,8 @@ struct CommandIO {
CommandIO(BEd &);
std::pair<std::string, bool> run();
std::pair<std::string, bool> run_pipe();
std::pair<std::string, bool> run_terminal();
void redraw();
};
} // namespace bed::internal::ui
+17 -7
View File
@@ -1,21 +1,31 @@
#pragma once
#include "definitions.h"
#include "internal/syntax/miniparser.h"
#include "pch.h"
namespace bed::internal::ui {
namespace bed::internal::ui::text_mode {
struct TextMode {
std::string cmd;
uint16_t cursor;
struct Row {
uint64_t start;
uint64_t length;
};
using Lines = std::vector<Row>;
vase::Shard *vase;
vase::Point cursor;
std::optional<syntax::MiniParser> parser;
std::vector<Lines> lines;
uint16_t start;
uint16_t height;
uint16_t term_height;
uint16_t term_width;
BEd &bed;
TextMode(BEd &);
TextMode(BEd &, vase::Shard *, syntax::Language *, void *);
std::pair<vase::Shard *, bool> run();
void grow(size_t required_height);
std::pair<vase::Shard *, bool> run_pipe();
std::pair<vase::Shard *, bool> run_terminal();
void grow();
void redraw();
void layout_lines();
};
} // namespace bed::internal::ui
} // namespace bed::internal::ui::text_mode
+2 -3
View File
@@ -12,13 +12,13 @@ struct Shard {
Petal
} kind;
uint8_t height;
uint16_t height;
std::atomic_uint32_t refs;
uint64_t length;
uint64_t lines;
Shard(Kind kind, uint64_t length, uint64_t lines, uint8_t height)
Shard(Kind kind, uint64_t length, uint64_t lines, uint16_t height)
: kind(kind), height(height), refs(1), length(length), lines(lines) {};
static void retain(Shard *n);
@@ -62,5 +62,4 @@ struct Petal : Shard {
source->retain();
};
};
} // namespace bed::internal::vase
+2 -1
View File
@@ -41,13 +41,14 @@ struct ReplacePart {
uint64_t offset_of(Shard *root, uint64_t line);
uint64_t offset_of(Shard *root, Point point);
Point point_of(Shard *root, uint64_t offset);
Point eof_point(Shard *root);
std::string to_string(Shard *root);
std::string to_string(Shard *root, Range range);
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
Shard *erase(Shard *root, uint64_t start, uint64_t end);
Shard *replace(Shard *root, Shard *text, uint64_t start, uint64_t end);
Shard *join(Shard *root, uint64_t start, uint64_t end);
Shard *copy(Shard *root, uint64_t start, uint64_t end);
void write_file(std::filesystem::path path, Shard *text);
+19
View File
@@ -4,10 +4,29 @@
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/boxing_word.h>
#include <mruby/class.h>
#include <mruby/common.h>
#include <mruby/compile.h>
#include <mruby/data.h>
#include <mruby/dump.h>
#include <mruby/error.h>
#include <mruby/gc.h>
#include <mruby/hash.h>
#include <mruby/internal.h>
#include <mruby/irep.h>
#include <mruby/numeric.h>
#include <mruby/object.h>
#include <mruby/opcode.h>
#include <mruby/presym.h>
#include <mruby/proc.h>
#include <mruby/range.h>
#include <mruby/re.h>
#include <mruby/string.h>
#include <mruby/throw.h>
#include <mruby/value.h>
#include <mruby/variable.h>
#include <mruby/version.h>
#include <pcre2.h>
extern "C" {
#include <grapheme.h>
+3
View File
@@ -0,0 +1,3 @@
register :happy, address: :none do
puts "be nice"
end
+73
View File
@@ -0,0 +1,73 @@
#include "bed.h"
namespace bed {
BEd::BEd(std::vector<std::string> args)
: mrb(this), theme(internal::theme::Theme::default_theme()), io(*this) {
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
bool color = true;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "--no-color") {
color = false;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else if (args[i] == "-h" || args[i] == "--help") {
print_help();
throw fatal_error("", 0);
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
const char *no_color = getenv("NO_COLOR");
if (no_color && *no_color != '\0')
color = false;
const char *colorterm = getenv("COLORTERM");
if (colorterm
&& strcmp(colorterm, "truecolor") != 0
&& strcmp(colorterm, "24bit") != 0)
color = false;
color_mode = color;
internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this);
internal::scripting::register_basic(*this);
languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby());
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
for (auto &[_, lang] : languages)
delete lang;
}
void BEd::print_help() {
io.write("BEd - A line editor.\n");
}
} // namespace bed
+115
View File
@@ -0,0 +1,115 @@
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed {
void BEd::handle(std::string_view cmd, bool eof) {
if (eof && cmd.empty()) {
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
return;
}
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) {
marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true;
}
internal::buffer::Address address;
switch (c.function->address_kind) {
case internal::functions::Function::AddressKind::None: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = a->buffername;
} break;
case internal::functions::Function::AddressKind::Line: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = *a;
} break;
case internal::functions::Function::AddressKind::Range: {
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_range(*this, vec);
if (!a.has_value())
a = internal::buffer::Range(current(), current());
}
address = *a;
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = internal::buffer::Range(current(), current());
}
if (!c.function->accept_zero) {
if (std::holds_alternative<internal::buffer::Line>(address)) {
if (std::get<internal::buffer::Line>(address).number == 0)
throw ed_error("Line number can't be zero.");
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
auto r = std::get<internal::buffer::Range>(address);
if (r.start == 0 || r.end == 0)
throw ed_error("Line number can't be zero.");
}
}
internal::vase::Shard *text = nullptr;
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
internal::vase::Shard *vase = nullptr;
internal::syntax::Language *lang = nullptr;
void *state = nullptr;
if (c.function->pre_text_mode)
std::tie(vase, lang, state) = c.function->pre_text_mode(*this, address, c.argument);
internal::ui::text_mode::TextMode tm(*this, vase, lang, state);
auto [a, b] = tm.run();
if (!b) {
text = a;
} else {
if (a) {
auto p = internal::syntax::make_parser(a, a->lines + 1, lang);
auto cancel_buf = new internal::buffer::ReadonlyBuffer("cancel", a, p);
internal::syntax::release(p);
buffers["cancel"] = cancel_buf;
cancel_buf->useless = false;
internal::vase::Shard::release(a);
}
throw ed_error("Operation cancelled.");
}
}
if (c.function->handle)
c.function->handle(*this, address, text, c.argument, nullptr);
if (c.suffix)
c.suffix->handle(*this);
if (c.temp_address)
temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second;
if (buf->waste()) {
delete buf;
it = buffers.erase(it);
} else {
++it;
}
}
}
} // namespace bed
+44 -144
View File
@@ -2,169 +2,69 @@
#include "internal/parser/parser.h"
namespace bed {
BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
: theme(internal::theme::Theme::default_theme()), io(io) {
internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this);
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
}
void BEd::run() {
while (true) {
internal::ui::CommandIO command(*this);
auto [cmd, eof] = command.run();
try {
handle(cmd, eof);
} catch (ed_error &e) {
} catch (const ed_error &e) {
io.apply(internal::io::Token::Warning);
io.write_line("?");
if (help_mode)
io.write_line(e.what());
io.reset();
last_help = e.what();
}
}
}
void BEd::handle(std::string_view cmd, bool eof) {
if (eof) {
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
return;
}
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) {
marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true;
}
internal::buffer::Address address;
switch (c.function->address_kind) {
case internal::functions::Function::AddressKind::None: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = a->buffername;
} break;
case internal::functions::Function::AddressKind::Line: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = *a;
} break;
case internal::functions::Function::AddressKind::Range: {
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_range(*this, vec);
if (!a.has_value())
a = internal::buffer::Range(current(), current());
}
address = *a;
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = internal::buffer::Range(current(), current());
}
if (!c.function->accept_zero) {
if (std::holds_alternative<internal::buffer::Line>(address)) {
if (std::get<internal::buffer::Line>(address).number == 0)
throw ed_error("Line number can't be zero.");
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
auto r = std::get<internal::buffer::Range>(address);
if (r.start == 0 || r.end == 0)
throw ed_error("Line number can't be zero.");
}
}
internal::vase::Shard *text = nullptr;
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
internal::ui::TextMode tm(*this);
auto [a, b] = tm.run();
if (!b)
text = a;
}
if (c.function->handle)
c.function->handle(*this, address, text, c.argument, nullptr);
if (c.suffix)
c.suffix->handle(*this);
if (c.temp_address)
temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second;
if (buf->waste()) {
delete buf;
it = buffers.erase(it);
} else {
++it;
}
}
}
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
if (name.empty())
throw ed_error("can't have empty buffer name");
throw ed_error("Can't have empty buffer name");
{
auto it = buffers.find(name);
if (it != buffers.end())
return *it->second;
}
constexpr std::string_view prefix = "history/";
if (name == "cancel")
throw ed_error("Cancel buffer empty.");
if (name.starts_with(prefix)) {
std::string_view path{name};
path.remove_prefix(prefix.size());
auto slash = path.rfind('_');
if (slash == std::string_view::npos || slash == 0 || slash == path.size() - 1)
throw ed_error("invalid history");
std::string bufname(path.substr(0, slash));
auto version_str = path.substr(slash + 1);
std::size_t version;
try {
version = std::stoull(std::string(version_str));
} catch (...) {
throw ed_error("invalid history version");
}
auto it = buffers.find(bufname);
if (it != buffers.end()) {
auto &buf_ = *it->second;
if (buf_.kind != internal::buffer::Buffer::Kind::Generic)
throw ed_error("Only normal buffers can have history.");
auto &buf = *(internal::buffer::GenericBuffer *)&buf_;
auto *history_buf = buf.get_history(version);
buffers.emplace(name, history_buf);
return *history_buf;
}
throw ed_error("Buffer has no history.");
}
for (auto &c : name)
if (!(('0' <= c && c <= '9')
|| ('a' <= c && c <= 'z')
|| ('A' <= c && c <= 'Z')
|| c == '-' || c == '_'
|| c == '+' || c == '.'
|| c == ',' || c == '$'
|| c == '/' || c == '~'))
throw ed_error("Invalid buffer name.");
auto *buf = new internal::buffer::GenericBuffer(name);
buffers.emplace(name, buf);
return *buf;
+279 -179
View File
@@ -3,27 +3,236 @@
namespace bed::internal::buffer {
GenericBuffer::~GenericBuffer() {
for (auto &item : undo_stack) {
syntax::release(item.parse_state);
vase::Shard::release(item.text);
}
for (auto &item : redo_stack) {
syntax::release(item.parse_state);
vase::Shard::release(item.text);
}
}
void GenericBuffer::list_history(BEd &ctx) {
uint64_t current = base_version + undo_stack.size();
uint64_t max_version = current + redo_stack.size();
size_t width = std::to_string(max_version).size();
for (size_t i = 0; i < undo_stack.size(); ++i) {
auto &item = undo_stack[i];
uint64_t version = base_version + i;
auto time = std::chrono::system_clock::to_time_t(item.timestamp);
std::tm tm = *std::localtime(&time);
ctx.io.write_line(
std::format(
" {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version,
width,
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
item.summary
)
);
}
{
auto time = std::chrono::system_clock::to_time_t(timestamp);
std::tm tm = *std::localtime(&time);
ctx.io.write_line(
std::format(
"* {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
current,
width,
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
action
)
);
}
for (size_t i = 0; i < redo_stack.size(); ++i) {
auto &item = redo_stack[redo_stack.size() - 1 - i];
uint64_t version = current + i + 1;
auto time = std::chrono::system_clock::to_time_t(item.timestamp);
std::tm tm = *std::localtime(&time);
ctx.io.write_line(
std::format(
" {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version,
width,
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
item.summary
)
);
}
}
ReadonlyBuffer *GenericBuffer::get_history(uint64_t version) {
uint64_t current = base_version + undo_stack.size();
if (version < base_version)
throw ed_error("History version has been pruned.");
if (version < current) {
auto &item = undo_stack[version - base_version];
return new ReadonlyBuffer(name, item.text, item.parse_state);
}
if (version == current)
return new ReadonlyBuffer(name, root, parse);
uint64_t redo_offset = version - current - 1;
if (redo_offset >= redo_stack.size())
throw ed_error("No such history version.");
auto &item = redo_stack[redo_stack.size() - 1 - redo_offset];
return new ReadonlyBuffer(name, item.text, item.parse_state);
}
void GenericBuffer::snapshot(std::string action_) {
if (base_version == 0) {
base_version++;
} else {
vase::Shard::retain(root);
undo_stack.push_back(
HistoryItem{
syntax::retain(parse),
root,
timestamp,
action
}
);
}
for (auto &item : redo_stack) {
syntax::release(item.parse_state);
vase::Shard::release(item.text);
}
redo_stack.clear();
timestamp = std::chrono::system_clock::now();
action = action_;
}
bool GenericBuffer::undo(BEd &ctx) {
if (undo_stack.empty())
return false;
HistoryItem prev = undo_stack.back();
undo_stack.pop_back();
vase::Shard::retain(root);
redo_stack.push_back(
HistoryItem{
syntax::retain(parse),
root,
timestamp,
action
}
);
vase::Shard::release(root);
root = prev.text;
syntax::release(parse);
parse = prev.parse_state;
timestamp = prev.timestamp;
action = prev.summary;
state = Modified;
if (!root) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = root->lines + 1;
}
return true;
}
bool GenericBuffer::redo(BEd &ctx) {
if (redo_stack.empty())
return false;
HistoryItem next = redo_stack.back();
redo_stack.pop_back();
vase::Shard::retain(root);
undo_stack.push_back(
HistoryItem{
syntax::retain(parse),
root,
timestamp,
action
}
);
vase::Shard::release(root);
root = next.text;
syntax::release(parse);
parse = next.parse_state;
timestamp = next.timestamp;
action = next.summary;
state = Modified;
if (!root) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = root->lines + 1;
}
return true;
}
uint64_t GenericBuffer::prune(int keep) {
size_t drop =
undo_stack.size() > (size_t)keep
? undo_stack.size() - keep
: 0;
for (size_t i = 0; i < drop; ++i) {
syntax::release(undo_stack[i].parse_state);
vase::Shard::release(undo_stack[i].text);
}
undo_stack.erase(
undo_stack.begin(),
undo_stack.begin() + drop
);
base_version += drop;
for (auto &item : redo_stack) {
syntax::release(item.parse_state);
vase::Shard::release(item.text);
}
redo_stack.clear();
return undo_stack.size();
}
bool GenericBuffer::waste() {
return save_path.empty()
&& root == nullptr;
&& root == nullptr
&& undo_stack.empty()
&& parse.lang == nullptr;
}
uint64_t GenericBuffer::lines() {
if (root)
return root->lines + 1;
return 0;
void GenericBuffer::saved_hook() {
state = buffer::GenericBuffer::Unmodified;
}
uint64_t GenericBuffer::bytes() {
if (root)
return root->length + 1;
return 0;
void GenericBuffer::language(BEd &ctx, std::string name) {
syntax::Language *lang = nullptr;
if (name.size()) {
auto it = ctx.languages.find(name);
if (it == ctx.languages.end())
throw ed_error("Language not found.");
lang = it->second;
}
if (lang == parse.lang)
return;
snapshot("Change buffer language.");
syntax::release(parse);
parse = syntax::make_parser(root, lines(), lang);
}
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
snapshot("Load file.");
if (lines())
ctx.marks.erase(name, 1, lines());
vase::Shard::release(root);
@@ -39,7 +248,9 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
ctx.current() = {name, lines()};
syntax::release(parse);
parse = syntax::make_parser(root, lines(), nullptr);
}
void GenericBuffer::set_filename(std::filesystem::path path) {
@@ -51,35 +262,71 @@ std::filesystem::path GenericBuffer::filename() {
};
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
snapshot(std::format("Insert {} lines after line {}", (text ? text->lines + 1 : 1), line));
ctx.prev().buffername = name;
ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1;
ctx.prev().end = line + (text ? text->lines + 1 : 1);
ctx.current() = {name, line + (text ? text->lines + 1 : 1)};
root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
if (parser)
parser->insert(root, ctx.prev().start, ctx.prev().end);
ctx.marks.insert(name, line, (text ? text->lines + 1 : 1));
if (parse.lang)
syntax::insert(parse, root, line, (text ? text->lines + 1 : 1));
state = Modified;
}
void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
snapshot(std::format("Remove lines {} to {}", start_line, end_line));
root = vase::erase(root, start_line, end_line);
ctx.prev().buffername = name;
ctx.prev().start = std::min(start_line, lines());
ctx.prev().end = std::min(start_line, lines());
uint64_t l = std::min(start_line, lines());
ctx.prev().start = l;
ctx.prev().end = l;
ctx.current() = {name, l};
ctx.marks.erase(name, start_line, end_line - start_line + 1);
if (parser)
parser->erase(root, start_line, end_line - start_line + 1);
if (parse.lang)
syntax::erase(parse, root, start_line, end_line - start_line + 1);
state = Modified;
}
void GenericBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) {
if (!text) {
remove(ctx, start_line, end_line);
return;
}
snapshot(std::format("Replace lines {} to {} with {} lines", start_line, end_line, text->lines));
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line + text->lines;
ctx.current() = {name, start_line + text->lines};
uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 1;
root = vase::replace(root, text, start_line, end_line);
if (parse.lang) {
syntax::Edit edit(parse);
edit.erase(start_line, old_count);
edit.insert(start_line, new_count);
edit.commit(root);
}
if (new_count > old_count) {
uint64_t diff = new_count - old_count;
ctx.marks.insert(name, end_line, diff);
} else if (new_count < old_count) {
uint64_t diff = old_count - new_count;
ctx.marks.collapse(name, start_line + new_count - 1, diff);
}
state = Modified;
}
void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
snapshot(std::format("Join lines {} to {}", start_line, end_line));
root = vase::join(root, start_line, end_line);
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line;
ctx.current() = {name, start_line};
ctx.marks.collapse(name, start_line, end_line - start_line);
if (parser)
parser->erase(root, start_line, end_line - start_line);
if (parse.lang)
syntax::erase(parse, root, start_line, end_line - start_line);
state = Modified;
}
@@ -87,11 +334,13 @@ void GenericBuffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
snapshot(std::format("Substitute /{}/ with /{}/ in lines {} to {}", regex, replacement, start_line, end_line));
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
if (parser)
parser->begin_edit();
std::optional<syntax::Edit> edit;
if (parse.lang)
edit.emplace(parse);
root = vase::substitute(
&ctx.append,
root,
@@ -103,168 +352,19 @@ void GenericBuffer::substitute(
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
if (old_lines) {
ctx.marks.erase(name, line, old_lines);
if (parser)
parser->erase(line, old_lines);
if (edit)
edit->erase(line, old_lines);
}
if (new_lines) {
ctx.marks.insert(name, line, line + new_lines - 1);
if (parser)
parser->insert(line, line + new_lines - 1);
ctx.marks.insert(name, line, new_lines);
if (edit)
edit->insert(line, new_lines);
}
ctx.current() = {name, line + new_lines};
}
);
if (parser)
parser->end_edit(root);
ctx.prev().buffername = name;
if (edit)
edit->commit(root);
state = Modified;
}
vase::Shard *GenericBuffer::copy(uint64_t start_line, uint64_t end_line) {
return vase::copy(root, start_line, end_line);
}
uint64_t GenericBuffer::find_next(std::string_view pattern, uint64_t start) {
return vase::find_next(root, pattern, start);
}
uint64_t GenericBuffer::find_prev(std::string_view pattern, uint64_t start) {
return vase::find_prev(root, pattern, start);
}
uint64_t GenericBuffer::next_closing(uint64_t start) {
if (parser.has_value()) {
uint64_t closing = parser->next_closing(start - 1);
if (closing == UINT64_MAX)
return lines();
return closing + 1;
} else {
start += 10;
if (start > lines())
return lines();
return start;
}
}
uint64_t GenericBuffer::prev_closing(uint64_t start) {
if (parser.has_value()) {
return parser->prev_opening(start - 1) + 1;
} else {
if (start > 10)
return start - 10;
return 0;
}
}
inline void apply(io::IO &io, const Highlight &hl) {
io.write("\x1b[0m");
const uint8_t r = (hl.fg >> 16) & 0xff;
const uint8_t g = (hl.fg >> 8) & 0xff;
const uint8_t b = hl.fg & 0xff;
io.write(std::format("\x1b[38;2;{};{};{}m", r, g, b));
if (hl.bg != 0) {
const uint8_t br = (hl.bg >> 16) & 0xff;
const uint8_t bg = (hl.bg >> 8) & 0xff;
const uint8_t bb = hl.bg & 0xff;
io.write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb));
}
if (hl.flags & Highlight::Bold)
io.write("\x1b[1m");
if (hl.flags & Highlight::Italic)
io.write("\x1b[3m");
if (hl.flags & Highlight::Underline)
io.write("\x1b[4m");
if (hl.flags & Highlight::Strikethrough)
io.write("\x1b[9m");
}
inline void reset(io::IO &io) {
io.write("\x1b[0m");
}
void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
if (parser) {
auto it_o = parser->get_hl(root, start_line - 1);
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
const auto highlight = ctx.theme.get(token);
apply(ctx.io, highlight);
ctx.io.write(line.data() + start, end - start);
reset(ctx.io);
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
++start_line;
}
} else {
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line);
}
}
void GenericBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10)
++width;
if (parser) {
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(root, start_line - 1);
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
ctx.io.write(std::format("{:>{}}\t", start_line, width));
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
const auto highlight = ctx.theme.get(token);
apply(ctx.io, highlight);
ctx.io.write(line.data() + start, end - start);
reset(ctx.io);
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
++start_line;
}
} else {
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
}
}
void GenericBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line));
}
} // namespace bed::internal::buffer
+141
View File
@@ -0,0 +1,141 @@
#include "bed.h"
#include "internal/buffer/buffer.h"
namespace bed::internal::buffer {
uint64_t ShardBuffer::lines() {
if (root)
return root->lines + 1;
return 0;
}
uint64_t ShardBuffer::bytes() {
if (root)
return root->length + 1;
return 0;
}
vase::Shard *ShardBuffer::copy(uint64_t start_line, uint64_t end_line) {
return vase::copy(root, start_line, end_line);
}
uint64_t ShardBuffer::find_next(std::string_view pattern, uint64_t start) {
return vase::find_next(root, pattern, start);
}
uint64_t ShardBuffer::find_prev(std::string_view pattern, uint64_t start) {
return vase::find_prev(root, pattern, start);
}
uint64_t ShardBuffer::next_closing(uint64_t start) {
if (parse.lang) {
uint64_t closing = syntax::next_closing(parse, start - 1);
return closing + 1;
} else {
start += 10;
if (start > lines())
return lines();
return start;
}
}
uint64_t ShardBuffer::prev_closing(uint64_t start) {
if (parse.lang) {
return syntax::prev_opening(parse, start - 1) + 1;
} else {
if (start > 10)
return start - 10;
return 0;
}
}
void ShardBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
if (parse.lang) {
auto it_o = syntax::get_hl(parse, root, start_line - 1);
if (!it_o)
goto h;
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
ctx.io.apply(token.type);
ctx.io.write(line.data() + start, end - start);
ctx.io.reset();
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
++start_line;
}
} else {
h:
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line);
}
}
void ShardBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10)
++width;
if (parse.lang) {
std::optional<syntax::Iterator> it_o = syntax::get_hl(parse, root, start_line - 1);
if (!it_o)
goto h;
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
ctx.io.write(std::format("{:>{}}\t", start_line, width));
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
ctx.io.apply(token.type);
ctx.io.write(line.data() + start, end - start);
ctx.io.reset();
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
++start_line;
}
} else {
h:
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
}
}
void ShardBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line));
}
} // namespace bed::internal::buffer
+28 -3
View File
@@ -8,6 +8,8 @@ bool ClipBuffer::waste() {
return false;
}
void ClipBuffer::saved_hook() {}
uint64_t ClipBuffer::lines() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
uint64_t lines = s ? s->lines + 1 : 0;
@@ -54,12 +56,12 @@ std::filesystem::path ClipBuffer::filename() {
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1;
ctx.prev().end = line + (text ? text->lines + 1 : 0);
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::insert(&ctx.append, s, text, line);
clip_write(s);
vase::Shard::release(s);
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
ctx.marks.insert(name, line, text->lines + 1);
}
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
@@ -73,6 +75,29 @@ void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.marks.erase(name, start_line, end_line - start_line + 1);
}
void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) {
if (!text) {
remove(ctx, start_line, end_line);
return;
}
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line + text->lines;
uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 1;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::replace(s, text, start_line, end_line);
clip_write(s);
vase::Shard::release(s);
if (new_count > old_count) {
uint64_t diff = new_count - old_count;
ctx.marks.insert(name, end_line, diff);
} else if (new_count < old_count) {
uint64_t diff = old_count - new_count;
ctx.marks.collapse(name, start_line + new_count - 1, diff);
}
}
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::join(s, start_line, end_line);
@@ -104,7 +129,7 @@ void ClipBuffer::substitute(
if (old_lines)
ctx.marks.erase(name, line, old_lines);
if (new_lines)
ctx.marks.insert(name, line, line + new_lines - 1);
ctx.marks.insert(name, line, new_lines);
}
);
clip_write(s);
+477 -4
View File
@@ -3,18 +3,340 @@
#include "internal/functions/suffixes.h"
namespace bed::internal::functions {
static std::string_view address_kind_str(Function::AddressKind k) {
switch (k) {
case Function::AddressKind::None:
return "none";
case Function::AddressKind::Line:
return "1 line";
case Function::AddressKind::Range:
return "range";
}
return "";
}
static std::string_view input_mode_str(Function::InputMode m) {
switch (m) {
case Function::InputMode::None:
return "";
case Function::InputMode::Text:
return "text";
case Function::InputMode::Interactive:
return "interactive";
case Function::InputMode::CommandList:
return "command list";
}
return "";
}
static std::string_view argument_kind_str(Function::ArgumentKind a) {
switch (a) {
case Function::ArgumentKind::None:
return "";
case Function::ArgumentKind::Regex:
return "regex";
case Function::ArgumentKind::Shell:
return "shell command";
case Function::ArgumentKind::Any:
return "any";
case Function::ArgumentKind::File:
return "file";
case Function::ArgumentKind::Global:
return "ed global-style";
case Function::ArgumentKind::Mark:
return "mark name";
case Function::ArgumentKind::Number:
return "number";
case Function::ArgumentKind::Line:
return "address";
case Function::ArgumentKind::Range:
return "range";
case Function::ArgumentKind::Ruby:
return "ruby code";
}
return "";
}
static void append_field(BEd &ctx, std::string_view label, io::Token::Kind color, std::string_view value) {
if (value.empty())
return;
ctx.io.write("\n");
ctx.io.apply(color);
ctx.io.write(label);
ctx.io.reset();
ctx.io.write(": ");
ctx.io.write(value);
}
static void describe_function(BEd &ctx, const Function &func) {
ctx.io.write(func.desc);
ctx.io.reset();
append_field(ctx, "Default address", io::Token::Color1, func.default_address);
append_field(ctx, "Address", io::Token::Color2, address_kind_str(func.address_kind));
if (func.accept_zero)
append_field(ctx, "Zero address", io::Token::Color3, "allowed");
append_field(ctx, "Input", io::Token::Color4, input_mode_str(func.input_mode));
append_field(ctx, "Argument", io::Token::Color5, argument_kind_str(func.argument_kind));
ctx.io.write("\n");
}
void Function::register_extented(BEd &ctx) {
ctx.functions.insert(
"*",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Explain a command",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto str = std::get<std::string>(arg_);
const auto first = str.find_first_not_of(" \t");
const auto last = str.find_last_not_of(" \t");
if (first == std::string::npos)
str.clear();
else
str = str.substr(first, last - first + 1);
if (str.empty()) {
describe_function(ctx, ctx.no_op);
return;
}
auto len = ctx.functions.longest_match(str);
if (len == str.size()) {
describe_function(ctx, *ctx.functions.get_ptr(str));
return;
}
throw ed_error("Not a valid function name.");
},
}
);
ctx.functions.insert(
"b",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "List active buffers",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &current = std::get<std::string>(addr_);
bool current_real = false;
for (auto &[name, _] : ctx.buffers) {
if (current == name) {
ctx.io.write("* ");
current_real = true;
} else {
ctx.io.write(" ");
}
ctx.io.write_line(name);
}
if (!current_real)
ctx.io.write_line("* " + current);
},
}
);
ctx.functions.insert(
"#",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Write a comment",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {},
}
);
ctx.functions.insert(
"`",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Ruby,
.input_mode = Function::InputMode::None,
.desc = "Execute given ruby code.",
.default_address = "0,0",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto &arg = std::get<RubyArg>(arg_);
auto pre_code = "$START=" + std::to_string(addr.start)
+ ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code);
auto line = scripting::run(ctx, arg.cmd);
ctx.io.write("=> ", 3);
auto parser = syntax::MiniParser(
*ctx.languages["ruby"],
vase::Shard::from_string(line.data(), line.size(), true),
nullptr
);
const auto &tokens = parser.lines[0].second;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size() || end > line.size())
break;
if (cursor < start)
ctx.io.write(line.data() + cursor, start - cursor);
ctx.io.apply(token.type);
ctx.io.write(line.data() + start, end - start);
ctx.io.reset();
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
},
}
);
ctx.functions.insert(
"``",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Execute addressed lines as ruby code.",
.default_address = "1,$",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto &buf = ctx.buffer(addr.buffername);
auto code = buf.copy(addr.start, addr.end);
ctx.prev().buffername = addr.buffername;
ctx.prev().start = addr.start;
ctx.prev().end = addr.end;
ctx.current() = {addr.buffername, addr.end};
auto pre_code = "$START=" + std::to_string(addr.start)
+ ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code);
scripting::run(ctx, vase::to_string(code));
vase::Shard::release(code);
},
}
);
ctx.functions.insert(
"echo",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Echo the given message (replacing $1-$4 with address information)",
.default_address = "",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto str = std::get<std::string>(arg_);
const auto first = str.find_first_not_of(" \t");
const auto last = str.find_last_not_of(" \t");
if (first == std::string::npos)
str.clear();
else
str = str.substr(first, last - first + 1);
for (size_t i = 0; i < str.size();) {
if (str[i] == '\\') {
if (i + 1 >= str.size())
break;
str.erase(i++, 1);
if (str[i - 1] == 'n')
str[i - 1] = '\n';
continue;
}
if (str[i] == '$' && i + 1 < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') {
char c = str[i + 1];
str.erase(i, 2);
switch (c) {
case '1': {
auto start = std::to_string(addr.start);
str.insert(i, start);
i += start.size();
} break;
case '2': {
auto end = std::to_string(addr.end);
str.insert(i, end);
i += end.size();
} break;
case '3': {
str.insert(i, addr.buffername);
i += addr.buffername.size();
} break;
case '4': {
auto &buf = ctx.buffer(addr.buffername);
auto filename = buf.filename().string();
str.insert(i, filename);
i += filename.size();
} break;
}
continue;
}
i++;
}
ctx.io.write_line(str);
},
}
);
ctx.functions.insert(
"cd",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Change directory.",
.desc = "Change directory",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
.handle = [](
BEd &,
const buffer::Address &,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto path = std::get<std::string>(arg_);
const auto first = path.find_first_not_of(" \t");
const auto last = path.find_last_not_of(" \t");
@@ -42,11 +364,17 @@ void Function::register_extented(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print directory.",
.desc = "Print the current working directory",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
char cwd[PATH_MAX];
if (!getcwd(cwd, sizeof(cwd)))
throw ed_error("Can't determine current directory.");
@@ -54,5 +382,150 @@ void Function::register_extented(BEd &ctx) {
},
}
);
ctx.functions.insert(
"x",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Range,
.input_mode = Function::InputMode::None,
.desc = "Exchange a range of lines for another",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Range>(arg_);
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
try {
ctx.buffer(arg.buffername).replace(ctx, text, arg.start, arg.end);
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
},
}
);
ctx.functions.insert(
"U",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Redo the last undo modification",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't redo buffer");
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (!buf.redo(ctx))
throw ed_error("Can't redo buffer.");
}
}
);
ctx.functions.insert(
"hl",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "List available history versions",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't redo buffer");
auto &buf = *(buffer::GenericBuffer *)&buf_;
buf.list_history(ctx);
}
}
);
ctx.functions.insert(
"hp",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Number,
.input_mode = Function::InputMode::None,
.desc = "Prune old history versions",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &arg = std::get<int64_t>(arg_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't prune buffer.");
auto &buf = *(buffer::GenericBuffer *)&buf_;
auto versions = buf.prune(arg);
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{} undo versions left.", versions));
}
}
);
ctx.functions.insert(
"lang",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Set buffer language",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto name = std::get<std::string>(arg_);
const auto first = name.find_first_not_of(" \t");
const auto last = name.find_last_not_of(" \t");
if (first == std::string::npos)
name.clear();
else
name = name.substr(first, last - first + 1);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't set language to buffer.");
auto &buf = *(buffer::GenericBuffer *)&buf_;
buf.language(ctx, name);
}
}
);
}
} // namespace bed::internal::functions
+322 -94
View File
@@ -34,16 +34,32 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Append text to a line.",
.desc = "Append text to a line",
.default_address = ".",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
.pre_text_mode = [](
BEd &ctx,
const buffer::Address &addr_,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
const auto &addr = std::get<buffer::Line>(addr_);
auto &buf_ = ctx.buffer(addr.buffername);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {nullptr, nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
return {nullptr, buf.parse.lang, state};
},
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *text,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
vase::Shard::release(text); }
}
);
ctx.functions.insert(
@@ -52,17 +68,32 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Change set of lines.",
.desc = "Change a range of lines",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).append(ctx, text, addr.start - 1);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
.pre_text_mode = [](
BEd &ctx,
const buffer::Address &addr_,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
const auto &addr = std::get<buffer::Range>(addr_);
auto &buf_ = ctx.buffer(addr.buffername);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {buf_.copy(addr.start, addr.end), nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
void *state = syntax::state_before(buf.parse, buf.root, addr.start - 1);
return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
},
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *text,
const Argument &,
std::vector<buffer::Line> *
) {
const auto &addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).replace(ctx, text, addr.start, addr.end);
vase::Shard::release(text); }
}
);
ctx.functions.insert(
@@ -71,14 +102,19 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Delete set of lines.",
.desc = "Delete a range of lines",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
@@ -88,11 +124,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Try load a file into the current buffer.",
.desc = "Load a file into the current buffer, warn once if unsaved changes exist",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
if (buf.state == buffer::Buffer::Modified) {
@@ -121,8 +163,8 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(s);
throw;
}
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
}
}
);
@@ -132,11 +174,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Load a file into the current buffer.",
.desc = "Load a file into the current buffer, discarding unsaved changes",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
vase::Shard *s = nullptr;
@@ -161,8 +209,8 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(s);
throw;
}
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
}
}
);
@@ -172,16 +220,19 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Set a save path.",
.desc = "Set/print a save path",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return;
auto &buf = *(buffer::GenericBuffer *)&buf_;
auto &buf = ctx.buffer(addr);
if (std::holds_alternative<std::filesystem::path>(arg))
buf.set_filename(std::get<std::filesystem::path>(arg));
else if (std::holds_alternative<ShellArg>(arg))
@@ -202,8 +253,16 @@ void Function::register_posix(BEd &ctx) {
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
ctx.io.apply(internal::io::Token::Warning);
ctx.io.write_line(ctx.last_help);
ctx.io.reset();
}
}
);
@@ -213,14 +272,22 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle help mode.",
.desc = "Toggle help mode",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
ctx.help_mode = !ctx.help_mode;
ctx.io.apply(internal::io::Token::Warning);
if (ctx.help_mode)
ctx.io.write_line(ctx.last_help);
ctx.io.reset();
}
}
);
@@ -230,18 +297,36 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Insert text before a line.",
.desc = "Insert text before a line",
.default_address = ".",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
.pre_text_mode = [](
BEd &ctx,
const buffer::Address &addr_,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number)
addr.number--;
auto &buf_ = ctx.buffer(addr.buffername);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {nullptr, nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
return {nullptr, buf.parse.lang, state};
},
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *text,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number)
addr.number--;
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
vase::Shard::release(text); }
}
);
ctx.functions.insert(
@@ -250,14 +335,19 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Join a set of lines.",
.desc = "Join a range of lines",
.default_address = ".,.+1",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
@@ -267,11 +357,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::Mark,
.input_mode = Function::InputMode::None,
.desc = "Mark a line.",
.desc = "Mark a line",
.default_address = ".",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
}
@@ -283,11 +379,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "List (print unambiguous) range",
.desc = "Print a range, showing nonprinting characters unambiguously",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
@@ -300,16 +402,21 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Move a range of lines.",
.desc = "Move a range of lines",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
if (arg.buffername == addr.buffername
&& addr.start <= arg.number
&& addr.end < arg.number)
&& arg.number >= addr.start && arg.number < addr.end)
throw ed_error("Can't move lines within themselves.");
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
ctx.mark(252, arg);
@@ -329,7 +436,6 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
@@ -339,11 +445,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range with line numbers",
.desc = "Print a range with line numbers",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
@@ -356,11 +468,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range",
.desc = "Print a range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
@@ -371,17 +489,28 @@ void Function::register_posix(BEd &ctx) {
"P",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Toggle prompt.",
.desc = "Toggle/set prompt string",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.prompt_mode = !ctx.prompt_mode;
if (ctx.prompt_mode && !ctx.prompt) {
ctx.prompt = [](BEd &) { return "*"; };
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &arg = std::get<std::string>(arg_);
if (arg.size()) {
ctx.prompt_mode = true;
ctx.prompt = [arg](BEd &) { return arg; };
return;
}
ctx.prompt_mode = !ctx.prompt_mode;
if (ctx.prompt_mode && !ctx.prompt)
ctx.prompt = [](BEd &) { return "*"; };
}
}
);
@@ -391,11 +520,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.desc = "Quit, warn once if unsaved changes exist",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
std::string modified_buffers;
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
@@ -416,11 +551,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Force quit.",
.desc = "Quit without saving, discarding unsaved changes",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
throw fatal_error("Force Quitting", 0);
}
}
@@ -431,11 +572,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Read from file into buffer.",
.desc = "Read a file's contents into the buffer after the given address",
.default_address = "$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Line>(addr_);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *s = nullptr;
@@ -456,13 +603,13 @@ void Function::register_posix(BEd &ctx) {
};
try {
buf.append(ctx, s, addr.number);
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
ctx.current() = {addr.buffername, addr.number + (s ? s->lines + 1 : 0)};
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
}
}
);
@@ -472,11 +619,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Regex,
.input_mode = Function::InputMode::None,
.desc = "Substitute regex.",
.desc = "Substitute pattern in range for replacement",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto arg = std::get<RegexArg>(arg_);
if (arg.expression == "")
@@ -505,11 +658,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Copy a range of lines.",
.desc = "Copy a range of lines",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
@@ -517,29 +676,56 @@ void Function::register_posix(BEd &ctx) {
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
vase::Shard::release(text);
} catch (...) {
if (!addr.start) {
vase::Shard::release(text);
throw;
}
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
ctx.functions.insert(
"u",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Undo the last modification to the buffer",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't undo buffer");
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (!buf.undo(ctx))
throw ed_error("Can't undo buffer.");
}
}
);
ctx.functions.insert(
"w",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Write buffer to disk.",
.desc = "Write a range of lines to disk",
.default_address = "0,$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Range>(addr_);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *text;
@@ -565,12 +751,14 @@ void Function::register_posix(BEd &ctx) {
throw ed_error("Need filename.");
vase::write_file(path, text);
};
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
buf.saved_hook();
if (!ctx.suppress_mode)
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
}
}
);
@@ -580,16 +768,34 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print line numbers",
.desc = "Print the line number of the given address",
.default_address = "$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
if (addr.start == addr.end)
ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start));
else
ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end));
ctx.io.apply(io::Token::BufferName);
ctx.io.write(":" + addr.buffername + ":");
ctx.io.apply(io::Token::Number);
if (addr.start == addr.end) {
ctx.io.write_line(std::format(" {}", addr.start));
} else {
ctx.io.write(std::format(" {}", addr.start));
ctx.io.apply(io::Token::AddressSeperator);
ctx.io.write(",");
ctx.io.apply(io::Token::Number);
ctx.io.write_line(std::format("{}", addr.end));
}
ctx.io.reset();
ctx.prev().buffername = addr.buffername;
ctx.prev().start = addr.start;
ctx.prev().end = addr.end;
ctx.current() = {addr.buffername, addr.end};
},
}
@@ -600,11 +806,17 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Shell,
.input_mode = Function::InputMode::None,
.desc = "Run a shell command.",
.desc = "Run a shell command",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto filename = ctx.buffer(addr).filename();
auto &arg = std::get<ShellArg>(arg_);
@@ -612,6 +824,7 @@ void Function::register_posix(BEd &ctx) {
if (ctx.escape_command(cmd, filename.string()))
ctx.io.write(cmd + "\n");
ctx.io.run_pty(cmd);
if (!ctx.suppress_mode)
ctx.io.write("!\n");
},
}
@@ -620,26 +833,41 @@ void Function::register_posix(BEd &ctx) {
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Prints a line and jumps to it.",
.desc = "Print given line and move the cursor to it",
.default_address = ".+1",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number != 0)
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
ctx.current() = addr;
ctx.prev().buffername = addr.buffername;
ctx.prev().start = addr.number;
ctx.prev().end = addr.number;
}
};
ctx.eof_op = Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.desc = "Quit, warn once if unsaved changes exist",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
.handle = [](
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {
std::string modified_buffers;
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
+27
View File
@@ -1,6 +1,33 @@
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::read_pipe() {
char buf[4096];
while (true) {
auto pos = pipe_input.find('\n');
if (pos != std::string::npos) {
std::string line = pipe_input.substr(0, pos);
pipe_input.erase(0, pos + 1);
return {line, false};
}
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0) {
pipe_input.append(buf, n);
continue;
}
if (n == 0) {
if (pipe_input.empty())
return {"", true};
std::string line = std::move(pipe_input);
pipe_input.clear();
return {line, false};
}
if (errno == EINTR)
continue;
throw fatal_error("Can't read stdin.", 1);
}
}
KeyEvent::ReadResult IO::get_next_byte(char &out) {
if (!input_queue.empty()) {
out = input_queue.front();
+51 -3
View File
@@ -1,12 +1,17 @@
#include "internal/io/io.h"
#include "bed.h"
namespace bed::internal::io {
termios IO::orig_termios{};
termios IO::raw_termios{};
bool IO::cleaned = true;
volatile std::atomic_bool IO::resized(false);
IO::Mode IO::mode = IO::Mode::PIPE;
IO::IO() {
IO::IO(BEd &bed) : bed(bed) {
if (!isatty(STDIN_FILENO))
return;
mode = Mode::TERMINAL;
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
throw fatal_error("Can't get terminal state.", 1);
struct sigaction sa{};
@@ -29,17 +34,54 @@ IO::~IO() {
cleanup();
}
void IO::apply(const io::Token::Kind &t) {
if (!bed.color_mode)
return;
write("\x1b[0m");
const auto &hl = bed.theme.get(t);
const uint8_t r = (hl.fg >> 16) & 0xff;
const uint8_t g = (hl.fg >> 8) & 0xff;
const uint8_t b = hl.fg & 0xff;
write(std::format("\x1b[38;2;{};{};{}m", r, g, b));
if (hl.bg != 0) {
const uint8_t br = (hl.bg >> 16) & 0xff;
const uint8_t bg = (hl.bg >> 8) & 0xff;
const uint8_t bb = hl.bg & 0xff;
write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb));
}
if (hl.flags & Highlight::Bold)
write("\x1b[1m");
if (hl.flags & Highlight::Italic)
write("\x1b[3m");
if (hl.flags & Highlight::Underline)
write("\x1b[4m");
if (hl.flags & Highlight::Strikethrough)
write("\x1b[9m");
}
void IO::reset() {
if (!bed.color_mode)
return;
write("\x1b[0m");
}
void IO::enable_mouse() {
if (mode == Mode::PIPE)
throw fatal_error("no mouse in pipe mode.", 1);
const char *seq = "\x1b[?1000h";
write_all(STDOUT_FILENO, seq, 8);
}
void IO::disable_mouse() {
if (mode == Mode::PIPE)
throw fatal_error("no mouse in pipe mode.", 1);
const char *seq = "\x1b[?1000l";
write_all(STDOUT_FILENO, seq, 8);
}
std::pair<uint16_t, uint16_t> IO::terminal_size() {
if (mode == Mode::PIPE)
throw fatal_error("no terminal size in pipe mode.", 1);
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
throw fatal_error("Can't get terminal size.", 1);
@@ -47,6 +89,8 @@ std::pair<uint16_t, uint16_t> IO::terminal_size() {
}
std::pair<uint16_t, uint16_t> IO::cursor_position() {
if (mode == Mode::PIPE)
throw fatal_error("no cursor in pipe mode.", 1);
write_all(STDOUT_FILENO, "\x1b[6n", 4);
std::string response;
char c;
@@ -69,7 +113,7 @@ std::pair<uint16_t, uint16_t> IO::cursor_position() {
}
void IO::enable_raw() {
if (!cleaned)
if (!cleaned || mode == Mode::PIPE)
return;
std::string os = "\x1b[?2004h";
write_all(STDOUT_FILENO, os.c_str(), os.size());
@@ -79,7 +123,7 @@ void IO::enable_raw() {
}
void IO::cleanup() {
if (cleaned)
if (cleaned || mode == Mode::PIPE)
return;
std::string os = "\x1b[?1000l\x1b[?2004l";
write_all(STDOUT_FILENO, os.c_str(), os.size());
@@ -93,6 +137,8 @@ void IO::handle_sigwinch(int) {
}
void IO::move_cursor(uint16_t row, uint16_t col) {
if (mode == Mode::PIPE)
return;
char buf[32];
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
write_all(STDOUT_FILENO, buf, n);
@@ -112,6 +158,8 @@ void IO::write_line(std::string_view s) {
}
void IO::run_pty(const std::string &cmd) {
if (mode == Mode::PIPE)
throw ed_error("Shell running not allowed in pipe mode.");
int master_fd = -1;
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
+53 -3
View File
@@ -6,34 +6,49 @@ void Parser::locator(AddressPromise &addr) {
addr.base = AddressPromise::None{};
switch (peek()) {
case '.':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Current{};
break;
case '$':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Last{};
break;
case '%':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::LastRange{};
break;
case '[':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Block{Direction::Backward};
break;
case ']':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Block{Direction::Forward};
break;
case '^':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Diagnostic{Direction::Backward};
break;
case '~':
token(io::Token::AddressSymbol);
advance();
end_token();
addr.base = AddressPromise::Diagnostic{Direction::Forward};
break;
case '\'':
token(io::Token::Mark);
advance();
if (('a' <= peek() && peek() <= 'z')
|| ('A' <= peek() && peek() <= 'Z'))
@@ -41,12 +56,16 @@ void Parser::locator(AddressPromise &addr) {
else
throw ed_error("Valid mark needed after \'");
advance();
end_token();
break;
case '{': {
token(io::Token::AddressSymbol);
advance();
end_token();
uint16_t j = 0;
std::string func;
std::string arg;
token(io::Token::RubyFunction);
while (peek(j) != '}') {
if (peek(j) == '\0')
throw ed_error("Scripted address not terminated");
@@ -55,6 +74,8 @@ void Parser::locator(AddressPromise &addr) {
if (peek(j) == ':') {
func = peek_str(j);
advance(j + 1);
end_token();
token(io::Token::RubyArg);
j = 0;
continue;
}
@@ -65,9 +86,14 @@ void Parser::locator(AddressPromise &addr) {
else
func = peek_str(j);
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
advance(j + 1);
advance(j);
end_token();
token(io::Token::AddressSymbol);
advance();
end_token();
} break;
case '/': {
token(io::Token::Regexp);
advance();
uint64_t j = 0;
while (true) {
@@ -88,8 +114,10 @@ void Parser::locator(AddressPromise &addr) {
std::string(peek_str(j))
);
advance(j + 1);
end_token();
} break;
case '?': {
token(io::Token::Regexp);
advance();
uint16_t j = 0;
while (true) {
@@ -110,8 +138,10 @@ void Parser::locator(AddressPromise &addr) {
std::string(peek_str(j))
);
advance(j + 1);
end_token();
} break;
case '<': {
token(io::Token::Label);
advance();
uint16_t j = 0;
while (peek(j) != '>'
@@ -133,8 +163,10 @@ void Parser::locator(AddressPromise &addr) {
break;
}
advance(j + 1);
end_token();
} break;
case '>': {
token(io::Token::Label);
advance();
uint16_t j = 0;
while (peek(j) != '>' && peek(j) != '\0')
@@ -150,9 +182,13 @@ void Parser::locator(AddressPromise &addr) {
break;
}
advance(j + 1);
end_token();
} break;
case '+': {
token(io::Token::AddressSymbol);
advance();
end_token();
token(io::Token::Number);
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
@@ -162,11 +198,15 @@ void Parser::locator(AddressPromise &addr) {
}
if (j == 0)
num = 1;
advance(j);
addr.offset += num;
advance(j);
end_token();
} break;
case '-': {
token(io::Token::AddressSymbol);
advance();
end_token();
token(io::Token::Number);
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
@@ -176,17 +216,20 @@ void Parser::locator(AddressPromise &addr) {
}
if (j == 0)
num = 1;
advance(j);
addr.offset -= num;
advance(j);
end_token();
} break;
default:
if ('0' <= peek() && peek() <= '9') {
token(io::Token::Number);
uint64_t num = 0;
while ('0' <= peek() && peek() <= '9') {
num = num * 10 + (peek() - '0');
advance();
}
addr.base = AddressPromise::Number{num};
end_token();
}
}
}
@@ -196,8 +239,11 @@ int64_t Parser::offset() {
while (peek() == '+' || peek() == '-'
|| ('0' <= peek() && peek() <= '9')) {
bool positive = peek() != '-';
token(io::Token::AddressSymbol);
if (peek() == '+' || peek() == '-')
advance();
end_token();
token(io::Token::Number);
uint16_t j = 0;
int64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9')
@@ -205,6 +251,7 @@ int64_t Parser::offset() {
if (j == 0)
num = 1;
advance(j);
end_token();
offset += positive ? num : -num;
skip_ws();
}
@@ -214,6 +261,7 @@ int64_t Parser::offset() {
void Parser::address(AddressPromise &addr) {
if (peek() == ':') {
token(io::Token::BufferName);
advance();
uint16_t j = 0;
while (peek(j) != ':' && peek(j) != '\0')
@@ -222,6 +270,7 @@ void Parser::address(AddressPromise &addr) {
advance(j);
if (peek() == ':')
advance();
end_token();
}
skip_ws();
if (peek() == '\0')
@@ -238,6 +287,7 @@ void Parser::addresses(std::vector<AddressPromise> &addresses) {
address(*addr);
while (peek() == ',' || peek() == ';') {
addr->jumping = peek() == ';';
token(io::Token::AddressSeperator);
advance();
skip_ws();
addresses.push_back({});
+11 -23
View File
@@ -81,16 +81,10 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
if (!curr.bufname.has_value())
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
else if (curr.bufname->empty())
curr.bufname = bufname = ctx.current().buffername;
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
@@ -107,15 +101,15 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
if (curr.jumping)
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
prev = curr;
prev_set = true;
bufname = *curr.bufname;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (prev_set) {
@@ -144,16 +138,10 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
if (!curr.bufname.has_value())
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
else if (curr.bufname->empty())
curr.bufname = bufname = ctx.current().buffername;
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
@@ -170,15 +158,15 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
if (curr.jumping)
ctx.current() = resolved;
prev = curr;
prev_set = true;
bufname = *curr.bufname;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (prev_set) {
+35 -3
View File
@@ -11,7 +11,9 @@ void Parser::operation() {
if (len == 0)
throw ed_error("Function not found.");
functions::Function *function = bed.functions.get_ptr(peek_str(len));
token(io::Token::Function);
advance(len);
end_token();
command->function = function;
char suffix = '\0';
switch (command->function->argument_kind) {
@@ -27,11 +29,15 @@ void Parser::operation() {
command->argument = peek();
else
throw ed_error("Valid mark needed.");
token(io::Token::Mark);
advance();
end_token();
break;
case functions::Function::ArgumentKind::Any:
command->argument = std::string(peek_str());
token(io::Token::Data);
advance(peek_str().size());
end_token();
break;
case functions::Function::ArgumentKind::Global: {
char delim;
@@ -108,16 +114,22 @@ void Parser::operation() {
skip_ws();
switch (peek()) {
case '!':
token(io::Token::Error);
advance();
end_token();
token(io::Token::Shell);
command->argument = functions::Function::ShellArg(std::string(peek_str()));
advance(peek_str().size());
end_token();
break;
case '\0':
command->argument = std::monostate();
break;
default:
token(io::Token::File);
command->argument = std::filesystem::path(peek_str());
advance(peek_str().size());
end_token();
break;
}
break;
@@ -133,6 +145,7 @@ void Parser::operation() {
char delim = peek();
if (delim == '\0')
throw ed_error("regex expected");
token(io::Token::Regexp);
advance();
uint16_t j = 0;
while (true) {
@@ -165,16 +178,20 @@ void Parser::operation() {
j++;
}
std::string replacement(peek_str(j));
std::string options;
if (peek(j) != '\0') {
advance(j + 1);
} else {
advance(j);
options = "p";
}
std::string options;
end_token();
token(io::Token::Suffix);
if (peek() != '\0') {
options = std::string(peek_str());
advance(peek_str().size());
}
end_token();
std::erase_if(options, [&](char c) {
if (bed.suffixes[c - 'a'].has_value()) {
suffix = c;
@@ -184,18 +201,33 @@ void Parser::operation() {
});
command->argument = functions::Function::RegexArg(expression, replacement, options);
} break;
case functions::Function::ArgumentKind::Ruby:
case functions::Function::ArgumentKind::Ruby: {
command->argument = functions::Function::RubyArg(std::string(peek_str()));
auto *ruby_parser = bed.languages["ruby"];
void *state = ruby_parser->none_state();
std::vector<syntax::ParseEvent> events;
std::vector<io::Token> tokens_;
ruby_parser->parse(&state, peek_str(), false, &tokens_, &events);
for (auto &token : tokens_) {
token.start += i;
token.end += i;
tokens->push_back(token);
}
ruby_parser->destroy(state);
advance(peek_str().size());
break;
} break;
case functions::Function::ArgumentKind::Shell:
command->argument = functions::Function::ShellArg(std::string(peek_str()));
token(io::Token::Shell);
advance(peek_str().size());
end_token();
break;
}
if (!suffix) {
suffix = peek();
token(io::Token::Suffix);
advance();
end_token();
}
if (suffix) {
auto &s = bed.suffixes[suffix - 'a'];
+41 -4
View File
@@ -2,6 +2,19 @@
#include "bed.h"
namespace bed::internal::parser {
void Parser::token(io::Token::Kind k) {
end_token();
tokens->push_back({.start = i, .end = 0, .type = k});
}
void Parser::end_token() {
if (tokens->empty())
return;
auto &b = tokens->back();
if (!b.end)
b.end = i;
}
char Parser::peek(uint16_t o) {
return i + o < cmd.size() ? cmd[i + o] : '\0';
}
@@ -15,13 +28,16 @@ void Parser::advance(uint16_t c) {
}
void Parser::skip_ws() {
end_token();
while (peek() == ' ' || peek() == '\t')
advance();
}
void Parser::parse() {
try {
skip_ws();
if (peek() == '@') {
token(io::Token::TempCurrent);
advance();
command->temp_address = true;
} else {
@@ -31,20 +47,41 @@ void Parser::parse() {
addresses(command->addresses);
operation();
skip_ws();
if (peek() != '\0')
if (peek() != '\0') {
token(io::Token::Error);
throw ed_error("Malformed command");
}
advance(cmd.size() - i);
end_token();
} catch (const ed_error &e) {
advance(cmd.size() - i);
end_token();
throw;
}
}
Parser::Parser(
std::string_view cmd, BEd &bed, Command *command,
std::vector<ui::Token> *tokens, CompletionContext *completion
std::vector<io::Token> *tokens, CompletionContext *completion
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
i = 0;
}
std::vector<io::Token> Parser::get_highlight(std::string_view cmd, BEd &bed) {
Command c;
std::vector<io::Token> tokens;
CompletionContext completion;
try {
Parser p(cmd, bed, &c, &tokens, &completion);
p.parse();
} catch (const ed_error &e) {
}
return tokens;
}
Command Parser::get_command(std::string_view cmd, BEd &bed) {
Command c;
std::vector<ui::Token> tokens;
std::vector<io::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, &c, &tokens, &completion);
p.parse();
@@ -53,7 +90,7 @@ Command Parser::get_command(std::string_view cmd, BEd &bed) {
std::vector<AddressPromise> Parser::get_addresses(std::string_view cmd, BEd &bed) {
std::vector<AddressPromise> result;
std::vector<ui::Token> tokens;
std::vector<io::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, nullptr, &tokens, &completion);
p.addresses(result);
+149
View File
@@ -0,0 +1,149 @@
#include "internal/scripting/ruby.h"
#include "bed.h"
namespace bed::internal::scripting {
static mrb_value mrb_bed_exit(mrb_state *mrb, mrb_value) {
mrb_raise(mrb, E_RUNTIME_ERROR, "Use `handle(\"q\")` to quit.");
return mrb_nil_value();
}
static void raise_fatal(mrb_state *mrb, const fatal_error &e) {
struct RClass *klass = mrb_class_get(mrb, "FatalError");
mrb_value exc = mrb_exc_new_str(mrb, klass, mrb_str_new_cstr(mrb, e.what()));
mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code));
mrb_exc_raise(mrb, exc);
}
static mrb_value mrb_bed_handle(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
const char *command;
mrb_int len;
mrb_get_args(mrb, "s", &command, &len);
std::string_view cmd(command, len);
try {
ctx.handle(cmd, false);
} catch (const ed_error &e) {
mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what());
} catch (const fatal_error &f) {
raise_fatal(mrb, f);
}
return mrb_nil_value();
}
static mrb_value hash_get(mrb_state *mrb, mrb_value hash, const char *name) {
if (mrb_nil_p(hash))
return mrb_nil_value();
return mrb_hash_get(mrb, hash, mrb_symbol_value(mrb_intern_cstr(mrb, name)));
}
static functions::Function::AddressKind parse_address_kind(mrb_state *mrb, mrb_value value) {
if (mrb_nil_p(value))
return functions::Function::AddressKind::None;
if (!mrb_symbol_p(value))
mrb_raise(mrb, E_TYPE_ERROR, "address must be a Symbol");
auto name = mrb_sym_name(mrb, mrb_symbol(value));
if (strcmp(name, "none") == 0)
return functions::Function::AddressKind::None;
if (strcmp(name, "line") == 0)
return functions::Function::AddressKind::Line;
if (strcmp(name, "range") == 0)
return functions::Function::AddressKind::Range;
mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid address type: :%s", name);
return functions::Function::AddressKind::None;
}
static mrb_value mrb_bed_register(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_value proc;
mrb_value opts = mrb_nil_value();
mrb_get_args(mrb, "n&|H", &r_cmd_name, &proc, &opts);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
std::string desc;
mrb_value r_desc = hash_get(mrb, opts, "desc");
if (mrb_string_p(r_desc))
desc.assign(RSTRING_PTR(r_desc), RSTRING_LEN(r_desc));
std::string default_address;
mrb_value r_default_address = hash_get(mrb, opts, "default");
if (mrb_string_p(r_default_address))
default_address.assign(RSTRING_PTR(r_default_address), RSTRING_LEN(r_default_address));
ctx.functions.insert(
cmd_name,
functions::Function{
.address_kind = parse_address_kind(mrb, hash_get(mrb, opts, "address")),
.argument_kind = functions::Function::ArgumentKind::None,
.input_mode = functions::Function::InputMode::None,
.desc = desc,
.default_address = default_address,
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [b = Block(mrb, proc)](
BEd &,
const buffer::Address &,
vase::Shard *,
const functions::Function::Argument &,
std::vector<buffer::Line> *
) {
b.call();
}
}
);
return mrb_nil_value();
}
static mrb_value mrb_bed_unregister(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_get_args(mrb, "n", &r_cmd_name);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
ctx.functions.remove(cmd_name);
return mrb_nil_value();
}
void register_basic(BEd &ctx) {
auto mrb = ctx.mrb.state;
auto *bed_error =
mrb_define_class(mrb, "EdError", mrb_exc_get_id(mrb, MRB_ERROR_SYM(RuntimeError)));
mrb_define_class(mrb, "FatalError", bed_error);
mrb_define_method(mrb, mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
mrb_define_method(mrb, mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
mrb_define_method(mrb, mrb->kernel_module, "register", mrb_bed_register, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK());
mrb_define_method(mrb, mrb->kernel_module, "unregister", mrb_bed_unregister, MRB_ARGS_REQ(1));
}
std::string run(BEd &ctx, const std::string &str) {
mrb_state *mrb = ctx.mrb.state;
mrb_value result = mrb_load_nstring(mrb, str.data(), str.size());
if (!mrb->exc) {
mrb_value inspected = mrb_funcall(mrb, result, "inspect", 0);
std::string output(RSTRING_PTR(inspected), RSTRING_LEN(inspected));
return output;
}
mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error;
if (mrb_string_p(msg))
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
auto *fatal_class = mrb_class_get(mrb, "FatalError");
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
mrb_value code =
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
mrb->exc = nullptr;
int c = 1;
if (mrb_fixnum_p(code))
c = mrb_fixnum(code);
throw fatal_error(error, c);
}
auto *ed_class = mrb_class_get(mrb, "EdError");
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
mrb->exc = nullptr;
throw ed_error(error);
}
mrb->exc = nullptr;
throw ed_error("Ruby Exception: " + error);
}
} // namespace bed::internal::scripting
+48
View File
@@ -0,0 +1,48 @@
#include "internal/syntax/parser.h"
namespace bed::internal::syntax {
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
: snap(p), at(target) {
at = target;
if (snap.lang)
state = ParseState::state_before(*snap.lang, snap.root, vase, at);
it = vase::Iterator(vase, at, Direction::Forward);
}
Iterator::~Iterator() {
if (state)
snap.lang->destroy(state);
release(snap);
}
Iterator::Iterator(Iterator &&other)
: snap(other.snap),
it(std::move(other.it)),
state(other.state),
tokens(std::move(other.tokens)) {
other.snap = {};
other.state = nullptr;
}
Iterator &Iterator::operator=(Iterator &&other) {
if (this == &other)
return *this;
if (state)
snap.lang->destroy(state);
snap = other.snap;
it = std::move(other.it);
state = other.state;
tokens = std::move(other.tokens);
other.state = nullptr;
return *this;
}
void Iterator::next() {
if (!state)
return;
it->next();
tokens.clear();
events.clear();
snap.lang->parse(&state, it->line, at++ == 0, &tokens, &events);
}
} // namespace bed::internal::syntax
+120
View File
@@ -0,0 +1,120 @@
#include "internal/syntax/miniparser.h"
namespace bed::internal::syntax {
MiniParser::MiniParser(Language &lang, vase::Shard *vase, void *initial_state)
: lang(lang),
start_state(initial_state ? lang.copy(initial_state) : lang.none_state()) {
vase::Iterator it(vase, 0, Direction::Forward);
void *state = lang.copy(start_state);
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
const uint64_t count = vase ? vase->lines + 1 : 1;
lines.reserve(count);
for (uint64_t i = 0; i < count; ++i) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, i == 0, &tokens, &events);
lines.emplace_back(lang.copy(state), std::move(tokens));
}
lang.destroy(state);
}
MiniParser::~MiniParser() {
if (start_state)
lang.destroy(start_state);
for (auto &[state, tokens] : lines)
if (state)
lang.destroy(state);
}
void MiniParser::dirty(vase::Shard *vase, uint64_t start, uint64_t count) {
vase::Iterator it(vase, start, Direction::Forward);
void *state = start ? lang.copy(lines[start - 1].first)
: lang.copy(start_state);
std::vector<ParseEvent> events;
uint64_t i = start;
for (; i < start + count; i++) {
it.next();
auto &line = lines[i];
line.second.clear();
events.clear();
lang.parse(&state, it.line, i == 0, &line.second, &events);
lang.destroy(line.first);
line.first = lang.copy(state);
}
while (it.next()) {
auto &line = lines[i++];
line.second.clear();
events.clear();
lang.parse(&state, it.line, false, &line.second, &events);
if (lang.equal(line.first, state))
break;
lang.destroy(line.first);
line.first = lang.copy(state);
}
lang.destroy(state);
}
void MiniParser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
vase::Iterator it(vase, start, Direction::Forward);
void *state = start ? lang.copy(lines[start - 1].first)
: lang.copy(start_state);
std::vector<ParseEvent> events;
std::vector<std::pair<void *, std::vector<io::Token>>> lines_new;
std::vector<io::Token> tokens;
lines_new.reserve(count);
for (uint64_t i = 0; i < count; ++i) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, start + i == 0, &tokens, &events);
lines_new.emplace_back(lang.copy(state), std::move(tokens));
}
uint64_t i = start;
while (it.next()) {
auto &line = lines[i++];
line.second.clear();
events.clear();
lang.parse(&state, it.line, i == 0, &line.second, &events);
if (lang.equal(line.first, state))
break;
lang.destroy(line.first);
line.first = lang.copy(state);
}
lines.insert(
lines.begin() + start,
std::make_move_iterator(lines_new.begin()),
std::make_move_iterator(lines_new.end())
);
lang.destroy(state);
}
void MiniParser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
if (count == 0)
return;
void *state = start ? lang.copy(lines[start - 1].first)
: lang.copy(start_state);
std::vector<ParseEvent> events;
for (uint64_t i = start; i < start + count; ++i)
if (lines[i].first)
lang.destroy(lines[i].first);
lines.erase(
lines.begin() + start,
lines.begin() + start + count
);
vase::Iterator it(vase, start, Direction::Forward);
uint64_t i = start;
while (i < lines.size() && it.next()) {
auto &line = lines[i];
line.second.clear();
events.clear();
lang.parse(&state, it.line, i++ == 0, &line.second, &events);
if (lang.equal(line.first, state))
break;
lang.destroy(line.first);
line.first = lang.copy(state);
}
lang.destroy(state);
}
} // namespace bed::internal::syntax
+72 -310
View File
@@ -1,265 +1,38 @@
#include "internal/syntax/parser.h"
namespace bed::internal::syntax {
static void destroy_tree(ParseState *node, Language &lang) {
if (!node)
return;
if (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
destroy_tree(branch->left, lang);
destroy_tree(branch->right, lang);
free(branch);
} else {
auto *leaf = (ParseStateLeaf *)node;
if (leaf->state)
lang.destroy(leaf->state);
if (leaf->blocks)
free(leaf->blocks);
free(leaf);
}
}
static ParseState *make_branch(ParseState *left, ParseState *right) {
if (!left)
return right;
if (!right)
return left;
auto *branch = (ParseStateBranch *)malloc(sizeof(ParseStateBranch));
branch->header = ParseState::BRANCH_BIT + left->lines() + right->lines();
branch->left = left;
branch->right = right;
return branch;
}
static ParseState *build_tree(std::vector<ParseStateLeaf *> &leaves, size_t begin, size_t end) {
const size_t count = end - begin;
if (count == 0)
return nullptr;
if (count == 1)
return leaves[begin];
const size_t mid = begin + count / 2;
ParseState *left = build_tree(leaves, begin, mid);
ParseState *right = build_tree(leaves, mid, end);
return make_branch(left, right);
}
Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang)
: root(nullptr), lang(lang) {
reset(vase, lines, lang);
}
Parser::~Parser() {
destroy_tree(root, lang);
}
void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) {
destroy_tree(root, lang);
root = nullptr;
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang) {
ParserSnapshot snap{nullptr, lang};
if (lines == 0)
return;
lang = std::move(lang_);
std::vector<ParseStateLeaf *> leaves;
leaves.reserve((lines + MAX_CHUNK - 1) / MAX_CHUNK);
uint64_t consumed = 0;
while (consumed < lines) {
auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
leaf->state = nullptr;
leaf->blocks = nullptr;
leaf->n = 0;
leaf->cap = 0;
uint64_t chunk = std::min(MAX_CHUNK, lines - consumed);
leaf->header = chunk;
consumed += chunk;
leaves.push_back(leaf);
}
root = build_tree(leaves, 0, leaves.size());
modify(vase, 0, lines);
return snap;
if (lang)
snap.root = ParseState::splice(*lang, nullptr, vase, 0, 0, lines);
return snap;
}
std::pair<ParseState *, ParseState *> Parser::split_tree(ParseState *node, uint64_t line) {
if (!node)
return {nullptr, nullptr};
if (line == 0)
return {nullptr, node};
if (line >= node->lines())
return {node, nullptr};
if (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
uint64_t left_lines = branch->left->lines();
if (line < left_lines) {
auto [a, b] = split_tree(branch->left, line);
ParseState *right = join_tree(b, branch->right);
free(branch);
return {a, right};
}
if (line == left_lines) {
ParseState *left = branch->left;
ParseState *right = branch->right;
free(branch);
return {left, right};
}
auto [a, b] = split_tree(branch->right, line - left_lines);
ParseState *left = join_tree(branch->left, a);
free(branch);
return {left, b};
} else {
auto *leaf = (ParseStateLeaf *)node;
uint64_t lines = leaf->lines();
auto *right = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
right->header = lines - line;
right->state = nullptr;
right->blocks = nullptr;
right->n = 0;
right->cap = 0;
leaf->header = line;
leaf->n = 0;
return {leaf, right};
}
ParserSnapshot retain(const ParserSnapshot &snap) {
if (snap.root)
ParseState::retain(snap.root);
return snap;
}
ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
// TODO: balance
return make_branch(a, b);
void release(ParserSnapshot &snap) {
if (snap.root && snap.lang)
ParseState::release(*snap.lang, snap.root);
snap.root = nullptr;
}
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
begin_edit();
erase(start, count);
end_edit(vase);
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line) {
if (!snap.lang)
return nullptr;
return ParseState::state_before(*snap.lang, snap.root, vase, line);
}
void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
begin_edit();
insert(start, count);
end_edit(vase);
}
void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
if (count == 0 || !root)
return;
std::vector<Token> tokens;
std::vector<ParseEvent> events;
uint64_t offset;
TreeCursor c = TreeCursor(root, target, &offset);
uint64_t at = target - offset;
void *state = nullptr;
if (c.leaf->state) {
state = lang.copy(c.leaf->state);
} else {
while (!c.leaf->state) {
c.prev();
if (!c.leaf) {
at = 0;
break;
}
at -= c.leaf->lines();
}
if (c.leaf) {
state = lang.copy(c.leaf->state);
} else {
state = lang.none_state();
c = TreeCursor(root, 0, &offset);
}
}
vase::Iterator it(vase, at, Direction::Forward);
uint64_t chunk_start = at;
uint64_t next_boundary = at + c.leaf->lines();
c.leaf->n = 0;
while (true) {
it.next();
if (at == next_boundary) {
c.next();
if (!c.leaf)
break;
c.leaf->n = 0;
chunk_start = at;
next_boundary += c.leaf->lines();
if (at >= target + count
&& c.leaf->state != nullptr
&& lang.equal(state, c.leaf->state))
break;
if (c.leaf->state)
lang.destroy(c.leaf->state);
c.leaf->state = lang.copy(state);
}
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
for (const auto &ev : events) {
if (c.leaf->n == c.leaf->cap) {
uint32_t cap = c.leaf->cap ? c.leaf->cap * 2 : 8;
c.leaf->blocks = (uint16_t *)realloc(c.leaf->blocks, cap * sizeof(uint16_t));
c.leaf->cap = cap;
}
c.leaf->blocks[c.leaf->n++] = ev.closing << 15 | (at - chunk_start);
}
at++;
}
lang.destroy(state);
}
void Parser::begin_edit() {
in_edit = true;
}
void Parser::mark_dirty(uint64_t start, uint64_t end) {
if (!dirty) {
dirty_start = start;
dirty_end = end;
dirty = true;
} else {
dirty_start = std::min(dirty_start, start);
dirty_end = std::max(dirty_end, end);
}
}
void Parser::erase(uint64_t start, uint64_t count) {
if (count == 0 || !root)
return;
auto [a, remaining] = split_tree(root, start);
auto [waste, b] = split_tree(remaining, count);
destroy_tree(waste, lang);
root = join_tree(a, b);
mark_dirty(start, start + 1);
}
void Parser::insert(uint64_t start, uint64_t count) {
if (count == 0)
return;
std::vector<ParseStateLeaf *> leaves;
leaves.reserve((count + MAX_CHUNK - 1) / MAX_CHUNK);
uint64_t consumed = 0;
while (consumed < count) {
auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
leaf->state = nullptr;
leaf->blocks = nullptr;
leaf->n = 0;
leaf->cap = 0;
uint64_t chunk = std::min(MAX_CHUNK, count - consumed);
leaf->header = chunk;
consumed += chunk;
leaves.push_back(leaf);
}
ParseState *subtree = build_tree(leaves, 0, leaves.size());
auto [left, right] = split_tree(root, start);
root = join_tree(join_tree(left, subtree), right);
mark_dirty(start, start + count);
}
void Parser::end_edit(vase::Shard *vase) {
in_edit = false;
if (!dirty)
return;
uint64_t count = dirty_end > dirty_start ? dirty_end - dirty_start : 1;
modify(vase, dirty_start, count);
dirty = false;
}
uint64_t Parser::next_closing(uint64_t line) {
if (!root)
return UINT64_MAX;
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) {
if (!snap.root || !snap.lang)
return line + 10;
uint64_t relative = 0;
TreeCursor c(root, line, &relative);
TreeCursor c(*snap.lang, snap.root, line, &relative);
uint64_t line_offset = line - relative;
int level = 0;
bool first_leaf = true;
@@ -283,14 +56,14 @@ uint64_t Parser::next_closing(uint64_t line) {
c.next();
first_leaf = false;
}
return UINT64_MAX;
return (snap.root->lines() - line > 10 ? line + 10 : snap.root->lines() - 1);
}
uint64_t Parser::prev_opening(uint64_t line) {
if (!root)
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line) {
if (!snap.root || !snap.lang)
return 0;
uint64_t relative = 0;
TreeCursor c(root, line, &relative);
TreeCursor c(*snap.lang, snap.root, line, &relative);
uint64_t line_offset = line - relative;
int level = 0;
bool first_leaf = true;
@@ -315,77 +88,66 @@ uint64_t Parser::prev_opening(uint64_t line) {
if (c.leaf)
line_offset -= c.leaf->lines();
}
return 0;
return (line > 10 ? line - 10 : 0);
}
std::optional<Parser::Iterator> Parser::get_hl(vase::Shard *vase, uint64_t target) {
if (!root)
std::optional<Iterator> get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target) {
if (!snap.root || !snap.lang)
return std::nullopt;
return Parser::Iterator(target, this, vase);
return Iterator(target, retain(snap), vase);
}
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Shard *vase) : p(p) {
uint64_t offset;
TreeCursor c = TreeCursor(p->root, target, &offset);
at = target - offset;
if (c.leaf->state) {
state = p->lang.copy(c.leaf->state);
void Edit::mark_dirty(uint64_t start, uint64_t end) {
if (!dirty) {
dirty_start = start;
dirty_end = end;
dirty = true;
} else {
while (!c.leaf->state) {
c.prev();
if (!c.leaf) {
at = 0;
break;
}
at -= c.leaf->lines();
}
if (c.leaf) {
state = p->lang.copy(c.leaf->state);
} else {
state = p->lang.none_state();
c = TreeCursor(p->root, 0, &offset);
}
}
it = vase::Iterator(vase, at, Direction::Forward);
while (at < target) {
it->next();
tokens.clear();
events.clear();
p->lang.parse(&state, it->line, at == 0, &tokens, &events);
at++;
dirty_start = std::min(dirty_start, start);
dirty_end = std::max(dirty_end, end);
}
}
Parser::Iterator::~Iterator() {
if (state)
p->lang.destroy(state);
void Edit::insert(uint64_t start, uint64_t count) {
if (count == 0)
return;
uint64_t orig_pos = (uint64_t)((int64_t)start - edit_delta);
mark_dirty(orig_pos, orig_pos);
edit_delta += (int64_t)count;
}
Parser::Iterator::Iterator(Iterator &&other)
: p(other.p),
it(std::move(other.it)),
state(other.state),
tokens(std::move(other.tokens)) {
other.state = nullptr;
void Edit::erase(uint64_t start, uint64_t count) {
if (count == 0 || !target->root)
return;
uint64_t orig_start = (uint64_t)((int64_t)start - edit_delta);
uint64_t orig_end = orig_start + count;
mark_dirty(orig_start, orig_end);
edit_delta -= (int64_t)count;
}
Parser::Iterator &Parser::Iterator::operator=(Iterator &&other) {
if (this == &other)
return *this;
if (state)
p->lang.destroy(state);
p = other.p;
it = std::move(other.it);
state = other.state;
tokens = std::move(other.tokens);
other.state = nullptr;
return *this;
void Edit::commit(vase::Shard *vase) {
if (!dirty || !target->lang)
return;
uint64_t line = dirty_start;
uint64_t original = dirty_end - dirty_start;
uint64_t final = (uint64_t)((int64_t)original + edit_delta);
ParseState *new_root =
ParseState::splice(*target->lang, target->root, vase, line, original, final);
release(*target);
target->root = new_root;
dirty = false;
edit_delta = 0;
}
void Parser::Iterator::next() {
it->next();
tokens.clear();
events.clear();
p->lang.parse(&state, it->line, at++ == 0, &tokens, &events);
void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) {
Edit e(snap);
e.insert(start, count);
e.commit(vase);
}
void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) {
Edit e(snap);
e.erase(start, count);
e.commit(vase);
}
} // namespace bed::internal::syntax
+219
View File
@@ -0,0 +1,219 @@
#include "internal/syntax/parser.h"
namespace bed::internal::syntax {
void ParseState::retain(ParseState *n) {
if (n)
n->refs++;
};
void ParseState::release(Language &lang, ParseState *n) {
if (!n || --n->refs > 0)
return;
if (n->is_branch()) {
auto *branch = (ParseStateBranch *)n;
release(lang, branch->left);
release(lang, branch->right);
delete branch;
} else {
auto *leaf = (ParseStateLeaf *)n;
if (leaf->state)
lang.destroy(leaf->state);
if (leaf->blocks)
free(leaf->blocks);
delete leaf;
}
}
int height(ParseState *n) {
return n ? n->height : 0;
}
int balance_factor(ParseState *n) {
ParseStateBranch *b = (ParseStateBranch *)n;
return height(b->left) - height(b->right);
}
ParseState *rotate_right(Language &lang, ParseStateBranch *z) {
ParseStateBranch *y = (ParseStateBranch *)z->left;
ParseState *middle = new ParseStateBranch(y->right, z->right);
ParseState *out = new ParseStateBranch(y->left, middle);
ParseState::release(lang, middle);
ParseState::release(lang, z);
return out;
}
ParseState *rotate_left(Language &lang, ParseStateBranch *z) {
ParseStateBranch *y = (ParseStateBranch *)z->right;
ParseState *middle = new ParseStateBranch(z->left, y->left);
ParseState *out = new ParseStateBranch(middle, y->right);
ParseState::release(lang, middle);
ParseState::release(lang, z);
return out;
}
ParseState *balance(Language &lang, ParseState *node) {
if (!node || !node->is_branch())
return node;
ParseStateBranch *b = (ParseStateBranch *)node;
int bf = balance_factor(node);
if (bf > 1) {
ParseStateBranch *left = (ParseStateBranch *)b->left;
if (balance_factor(left) < 0) {
ParseState::retain(left);
auto new_left = rotate_left(lang, left);
auto rebuilt = new ParseStateBranch(new_left, b->right);
auto result = rotate_right(lang, (ParseStateBranch *)rebuilt);
ParseState::release(lang, new_left);
ParseState::release(lang, b);
return result;
}
return rotate_right(lang, b);
}
if (bf < -1) {
ParseStateBranch *right = (ParseStateBranch *)b->right;
if (balance_factor(right) > 0) {
ParseState::retain(right);
auto new_right = rotate_right(lang, right);
auto rebuilt = new ParseStateBranch(b->left, new_right);
auto result = rotate_left(lang, (ParseStateBranch *)rebuilt);
ParseState::release(lang, new_right);
ParseState::release(lang, b);
return result;
}
return rotate_left(lang, b);
}
return node;
}
ParseState *ParseState::build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi) {
if (hi - lo == 1)
return pieces[lo];
uint64_t mid = lo + (hi - lo) / 2;
ParseState *left = build(lang, pieces, lo, mid);
ParseState *right = build(lang, pieces, mid, hi);
ParseState *node = concat(lang, left, right);
release(lang, left);
release(lang, right);
return node;
}
ParseState *ParseState::splice(
Language &lang, ParseState *root, vase::Shard *vase,
uint64_t line, uint64_t original, uint64_t final
) {
if (!vase)
return nullptr;
if (!root || (line == 0 && root->lines() == original)) {
vase::Iterator it(vase, 0, Direction::Forward);
void *state = lang.none_state();
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
ParsePieceBuilder builder(lang, 0, state);
for (uint64_t at = 0; at < final; ++at) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
builder.add(state, at, events);
}
lang.destroy(state);
return builder.finish();
}
uint64_t at;
void *state;
ParseState *prefix;
{
uint64_t offset;
TreeCursor c = TreeCursor(lang, root, line, &offset);
prefix = c.prefix();
at = line - offset;
state = lang.copy(c.leaf->state);
}
vase::Iterator it(vase, at, Direction::Forward);
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
uint64_t end_in_tree = line + original;
uint64_t end_extra;
TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra);
uint64_t end_in_vase = line + final;
ParsePieceBuilder builder(lang, at, state);
while (at < end_in_vase + end_extra) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
builder.add(state, at, events);
++at;
}
c.next();
while (c.leaf) {
if (lang.equal(state, c.leaf->state))
break;
for (uint64_t i = 0; i < c.leaf->lines(); ++i) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
builder.add(state, at, events);
++at;
}
c.next();
}
lang.destroy(state);
ParseState *suffix = c.suffix();
ParseState *new_stuff = builder.finish();
auto a = concat(lang, prefix, new_stuff);
release(lang, prefix);
release(lang, new_stuff);
auto result = concat(lang, a, suffix);
release(lang, a);
release(lang, suffix);
return result;
}
ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *b) {
if (!a)
return (retain(b), b);
if (!b)
return (retain(a), a);
if (a->height > b->height + 1) {
ParseStateBranch *ba = (ParseStateBranch *)a;
ParseState *r = concat(lang, ba->right, b);
ParseState *out = balance(lang, new ParseStateBranch(ba->left, r));
release(lang, r);
return out;
}
if (b->height > a->height + 1) {
ParseStateBranch *bb = (ParseStateBranch *)b;
ParseState *l = concat(lang, a, bb->left);
ParseState *out = balance(lang, new ParseStateBranch(l, bb->right));
release(lang, l);
return out;
}
return balance(lang, new ParseStateBranch(a, b));
}
void *ParseState::state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line) {
uint64_t offset;
uint64_t at;
void *state;
if (!root) {
at = 0;
state = lang.none_state();
} else {
TreeCursor c = TreeCursor(lang, root, line, &offset);
at = line - offset;
state = lang.copy(c.leaf->state);
}
vase::Iterator it(vase, at, Direction::Forward);
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
for (; at < line; ++at) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
}
return state;
}
} // namespace bed::internal::syntax
+123 -112
View File
@@ -34,12 +34,13 @@ inline uint8_t utf8_codepoint_width(unsigned char c) {
return 1;
}
bool handle_escapes(RubyParser &p, std::vector<Token> *tokens, uint32_t &start, bool string = true) {
bool handle_escapes(RubyParser &p, std::vector<io::Token> *tokens, uint32_t &start, bool string = true) {
// TODO: properly handle escapes for %q ' etc.
if (p.peek() == '\\') {
if (string)
tokens->push_back({start, p.i, Token::String});
tokens->push_back({start, p.i, io::Token::String});
else
tokens->push_back({start, p.i, Token::Regexp});
tokens->push_back({start, p.i, io::Token::Regexp});
start = p.i;
p.advance();
if (p.peek() == 'x') {
@@ -95,14 +96,14 @@ bool handle_escapes(RubyParser &p, std::vector<Token> *tokens, uint32_t &start,
} else {
p.advance();
}
tokens->push_back({start, p.i, Token::Escape});
tokens->push_back({start, p.i, io::Token::Escape});
start = p.i;
return true;
}
return false;
};
bool handle_heredoc(RubyParser &p, std::vector<Token> *tokens) {
bool handle_heredoc(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) {
uint8_t *heredocs = p.state->heredocs();
uint32_t start = p.i;
if (start == 0) {
@@ -114,21 +115,22 @@ bool handle_heredoc(RubyParser &p, std::vector<Token> *tokens) {
&& memcmp(p.line.data() + start, heredocs + 1, heredoc_len) == 0) {
if (!p.dequeue_doc(heredoc_len))
p.current().state = RubyState::RubyInternalState::NONE;
tokens->push_back({p.i, p.len(), Token::Annotation});
events->push_back(true);
tokens->push_back({p.i, p.len(), io::Token::Annotation});
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return true;
}
}
if (!(heredocs[0] & RubyState::Heredocs::ALLOW_INTERPOLATION)) {
tokens->push_back({p.i, p.len(), Token::String});
tokens->push_back({p.i, p.len(), io::Token::String});
return true;
} else {
while (p.i < p.len()) {
if (handle_escapes(p, tokens, start))
continue;
if (p.peek_str(2) == "#{") {
tokens->push_back({start, p.i, Token::String});
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
tokens->push_back({start, p.i, io::Token::String});
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
p.advance(2);
p.push_state();
return false;
@@ -136,20 +138,20 @@ bool handle_heredoc(RubyParser &p, std::vector<Token> *tokens) {
p.advance();
}
if (p.i >= p.len())
tokens->push_back({start, p.len(), Token::String});
tokens->push_back({start, p.len(), io::Token::String});
return true;
}
}
void handle_string(RubyParser &p, std::vector<Token> *tokens) {
void handle_string(RubyParser &p, std::vector<io::Token> *tokens) {
uint32_t start = p.i;
while (p.i < p.len()) {
if (handle_escapes(p, tokens, start))
continue;
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
&& p.peek_str(2) == "#{") {
tokens->push_back({start, p.i, Token::String});
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
tokens->push_back({start, p.i, io::Token::String});
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
p.advance(2);
p.push_state();
return;
@@ -160,7 +162,7 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
if (p.peek() == p.current().delim_end) {
if (p.current().delim_start == p.current().delim_end) {
p.advance();
tokens->push_back({start, p.i, Token::String});
tokens->push_back({start, p.i, io::Token::String});
p.current().state = RubyState::RubyInternalState::NONE;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return;
@@ -168,7 +170,7 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
p.current().lit_brace_level--;
if (p.current().lit_brace_level == 0) {
p.advance();
tokens->push_back({start, p.i, Token::String});
tokens->push_back({start, p.i, io::Token::String});
p.current().state = RubyState::RubyInternalState::NONE;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return;
@@ -178,18 +180,18 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
p.advance();
}
if (p.i >= p.len())
tokens->push_back({start, p.len(), Token::String});
tokens->push_back({start, p.len(), io::Token::String});
}
void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
void handle_regex(RubyParser &p, std::vector<io::Token> *tokens) {
uint32_t start = p.i;
while (p.i < p.len()) {
if (handle_escapes(p, tokens, start, false))
continue;
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
&& p.peek_str(2) == "#{") {
tokens->push_back({start, p.i, Token::Regexp});
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
tokens->push_back({start, p.i, io::Token::Regexp});
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
p.advance(2);
p.push_state();
return;
@@ -200,7 +202,7 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
if (p.peek() == p.current().delim_end) {
if (p.current().delim_start == p.current().delim_end) {
p.advance();
tokens->push_back({start, p.i, Token::Regexp});
tokens->push_back({start, p.i, io::Token::Regexp});
p.current().state = RubyState::RubyInternalState::NONE;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return;
@@ -208,7 +210,7 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
p.current().lit_brace_level--;
if (p.current().lit_brace_level == 0) {
p.advance();
tokens->push_back({start, p.i, Token::Regexp});
tokens->push_back({start, p.i, io::Token::Regexp});
p.current().state = RubyState::RubyInternalState::NONE;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return;
@@ -218,15 +220,15 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
p.advance();
}
if (p.i >= p.len())
tokens->push_back({start, p.len(), Token::Regexp});
tokens->push_back({start, p.len(), io::Token::Regexp});
}
bool handle_line_markers(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseEvent> *events) {
bool handle_line_markers(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) {
if (p.len() == 6 && p.peek_str(6) == "=begin") {
p.current().state = RubyState::RubyInternalState::COMMENT;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
events->push_back(false);
tokens->push_back({0, p.len(), Token::Comment});
tokens->push_back({0, p.len(), io::Token::Comment});
return true;
}
if (p.len() == 7 && p.peek_str(7) == "__END__") {
@@ -237,20 +239,20 @@ bool handle_line_markers(RubyParser &p, std::vector<Token> *tokens, std::vector<
return false;
}
bool handle_comment(RubyParser &p, std::vector<Token> *tokens, bool first_line) {
bool handle_comment(RubyParser &p, std::vector<io::Token> *tokens, bool first_line) {
if (p.peek() == '#') {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
if (first_line && p.i == 0 && p.peek(1) == '!') {
tokens->push_back({0, p.len(), Token::Shebang});
tokens->push_back({0, p.len(), io::Token::Shebang});
return true;
}
tokens->push_back({p.i, p.len(), Token::Comment});
tokens->push_back({p.i, p.len(), io::Token::Comment});
return true;
}
return false;
}
bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseEvent> *events) {
bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) {
static const RubyTries tries = RubyTries();
if (p.peek() == ' ' || p.peek() == '\t') {
while (p.peek() == ' ' || p.peek() == '\t')
@@ -276,7 +278,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
j++;
while (identifier_char(p.peek(j)))
j++;
tokens->push_back({p.i, p.i + j, Token::Constant});
tokens->push_back({p.i, p.i + j, io::Token::Constant});
p.advance(j);
while (p.peek() == ' ' || p.peek() == '\t')
p.advance();
@@ -298,11 +300,11 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
if (p.peek(j) == '!' || p.peek(j) == '?')
j++;
if ('A' <= p.peek() && p.peek() <= 'Z')
tokens->push_back({p.i, p.i + j, Token::Constant});
tokens->push_back({p.i, p.i + j, io::Token::Constant});
else if (j == 4 && p.peek_str(4) == "self")
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
else
tokens->push_back({p.i, p.i + j, Token::Function});
tokens->push_back({p.i, p.i + j, io::Token::Function});
p.advance(j);
while (p.peek() == ' ' || p.peek() == '\t')
p.advance();
@@ -321,7 +323,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
j++;
while (identifier_char(p.peek(j)))
j++;
tokens->push_back({p.i, p.i + j, Token::Constant});
tokens->push_back({p.i, p.i + j, io::Token::Constant});
p.advance(j);
while (p.peek() == ' ' || p.peek() == '\t')
p.advance();
@@ -343,7 +345,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
indented = true;
if (p.peek(j) == '~' || p.peek(j) == '-')
j++;
tokens->push_back({p.i, p.i + j, Token::Operator});
tokens->push_back({p.i, p.i + j, io::Token::Operator});
if (p.i + j >= p.len())
return false;
std::string delim;
@@ -366,7 +368,8 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
if (!delim.empty()) {
tokens->push_back({s, p.i + j, Token::Annotation});
events->push_back(false);
tokens->push_back({s, p.i + j, io::Token::Annotation});
uint8_t header = delim.size();
if (interpolation)
header |= RubyState::Heredocs::ALLOW_INTERPOLATION;
@@ -380,7 +383,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
if (p.peek() == '/' && p.current().flags & RubyState::RubyInternalState::EXPECTING_EXPRESSION) {
tokens->push_back({p.i, p.i + 1, Token::Regexp});
tokens->push_back({p.i, p.i + 1, io::Token::Regexp});
p.current().state = RubyState::RubyInternalState::REGEXP;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
@@ -399,7 +402,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
if (p.peek() == '.')
p.advance();
}
tokens->push_back({start, p.i, Token::Operator});
tokens->push_back({start, p.i, io::Token::Operator});
return false;
}
case ':': {
@@ -407,17 +410,17 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
uint32_t start = p.i;
p.advance();
if (p.i >= p.len()) {
tokens->push_back({start, p.i, Token::Operator});
tokens->push_back({start, p.i, io::Token::Operator});
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
}
if (p.peek() == ':') {
p.advance();
tokens->push_back({start, p.i, Token::Operator});
tokens->push_back({start, p.i, io::Token::Operator});
return false;
}
if (p.peek() == '\'' || p.peek() == '"') {
tokens->push_back({start, p.i, Token::Label});
tokens->push_back({start, p.i, io::Token::Label});
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
}
@@ -428,12 +431,12 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance();
while (identifier_char(p.peek()))
p.advance();
tokens->push_back({start, p.i, Token::Label});
tokens->push_back({start, p.i, io::Token::Label});
return false;
}
uint32_t op_len = tries.operator_trie.longest_match(p.peek_str(p.len() - p.i));
if (op_len > 0) {
tokens->push_back({start, p.i + op_len, Token::Label});
tokens->push_back({start, p.i + op_len, io::Token::Label});
p.advance(op_len);
return false;
}
@@ -443,10 +446,10 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance();
if (p.peek() == '!' || p.peek() == '?')
p.advance();
tokens->push_back({start, p.i, Token::Label});
tokens->push_back({start, p.i, io::Token::Label});
return false;
}
tokens->push_back({start, p.i, Token::Operator});
tokens->push_back({start, p.i, io::Token::Operator});
return false;
}
case '@': {
@@ -463,7 +466,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
while (identifier_char(p.peek()))
p.advance();
tokens->push_back({start, p.i, Token::VariableInstance});
tokens->push_back({start, p.i, io::Token::VariableInstance});
return false;
}
case '$': {
@@ -510,7 +513,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
}
tokens->push_back({start, p.i, Token::VariableGlobal});
tokens->push_back({start, p.i, io::Token::VariableGlobal});
return false;
}
case '?': {
@@ -526,7 +529,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance();
if (is_hex(p.peek()))
p.advance();
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else if (p.peek() == 'u') {
p.advance();
@@ -546,7 +549,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
if (is_hex(p.peek()))
p.advance();
}
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else if ('0' <= p.peek() && p.peek() <= '7') {
p.advance();
@@ -554,7 +557,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else if (p.peek() == 'c') {
p.advance();
@@ -562,7 +565,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance();
else
goto combination;
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else if (p.peek() == 'M' || p.peek() == 'C') {
p.advance();
@@ -573,7 +576,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
else
goto combination;
}
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else if (p.peek() == 'N') {
p.advance();
@@ -584,28 +587,28 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
if (p.peek() == '}')
p.advance();
}
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else {
p.advance();
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
}
} else if (p.peek() != '\0' && p.peek() != ' ' && p.peek() != '\t') {
p.advance();
tokens->push_back({start, p.i, Token::Char});
tokens->push_back({start, p.i, io::Token::Char});
return false;
} else {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({start, p.i, Token::Operator});
tokens->push_back({start, p.i, io::Token::Operator});
return false;
}
}
case '{': {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
p.current().brace_level++;
p.advance();
return false;
@@ -614,11 +617,11 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
if (!--p.current().brace_level && p.state->top > 1) {
p.pop_state();
tokens->push_back({p.i, p.i + 1, Token::Interpolation});
tokens->push_back({p.i, p.i + 1, io::Token::Interpolation});
} else {
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
}
p.advance();
return false;
@@ -626,8 +629,8 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
case '(': {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
p.current().brace_level++;
p.advance();
return false;
@@ -636,16 +639,16 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.current().brace_level--;
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
p.advance();
return false;
}
case '[': {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
p.current().brace_level++;
p.advance();
return false;
@@ -654,14 +657,14 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.current().brace_level--;
uint8_t brace_color =
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
p.advance();
return false;
}
case '\'': {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + 1, Token::String});
tokens->push_back({p.i, p.i + 1, io::Token::String});
p.current().state = RubyState::RubyInternalState::STRING;
p.current().delim_start = '\'';
p.current().delim_end = '\'';
@@ -671,7 +674,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
}
case '"': {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + 1, Token::String});
tokens->push_back({p.i, p.i + 1, io::Token::String});
p.current().state = RubyState::RubyInternalState::STRING;
p.current().delim_start = '"';
p.current().delim_end = '"';
@@ -681,7 +684,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
}
case '`': {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + 1, Token::String});
tokens->push_back({p.i, p.i + 1, io::Token::String});
p.current().state = RubyState::RubyInternalState::STRING;
p.current().delim_start = '`';
p.current().delim_end = '`';
@@ -691,7 +694,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
}
case '%': {
if (p.i + 1 >= p.len()) {
tokens->push_back({p.i, p.i + 1, Token::Operator});
tokens->push_back({p.i, p.i + 1, io::Token::Operator});
p.advance();
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
@@ -729,14 +732,14 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
break;
}
if (p.i + prefix_len >= p.len()) {
tokens->push_back({p.i, p.i + 1, Token::Operator});
tokens->push_back({p.i, p.i + 1, io::Token::Operator});
p.advance(prefix_len);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
}
delim_start = p.peek(prefix_len);
if (identifier_char(delim_start) || delim_start == ' ') {
tokens->push_back({p.i, p.i + 1, Token::Operator});
tokens->push_back({p.i, p.i + 1, io::Token::Operator});
p.advance(prefix_len);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
@@ -758,7 +761,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
delim_end = delim_start;
break;
}
tokens->push_back({p.i, p.i + prefix_len + 1, (is_regexp ? Token::Regexp : Token::String)});
tokens->push_back({p.i, p.i + prefix_len + 1, (is_regexp ? io::Token::Regexp : io::Token::String)});
p.current().state = is_regexp
? RubyState::RubyInternalState::REGEXP
: RubyState::RubyInternalState::STRING;
@@ -780,11 +783,6 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.ending = false;
p.advance();
return false;
case '\0':
if (p.ending)
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.advance();
return false;
default:
if ('0' <= p.peek() && p.peek() <= '9') {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
@@ -860,7 +858,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
}
}
}
tokens->push_back({start, p.i, Token::Number});
tokens->push_back({start, p.i, io::Token::Number});
return false;
} else if (identifier_start_char(p.peek())) {
uint32_t j = 1;
@@ -870,93 +868,93 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
j++;
if (j == tries.base_keywords_trie.longest_match(p.peek_str(j))) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.expecting_keywords_trie.longest_match(p.peek_str(j))) {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.operator_keywords_trie.longest_match(p.peek_str(j))) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::KeywordOperator});
tokens->push_back({p.i, p.i + j, io::Token::KeywordOperator});
p.advance(j);
return false;
} else if (j == tries.expecting_operators_trie.longest_match(p.peek_str(j))) {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::KeywordOperator});
tokens->push_back({p.i, p.i + j, io::Token::KeywordOperator});
p.advance(j);
return false;
} else if (j == tries.types_trie.longest_match(p.peek_str(j))) {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Type});
tokens->push_back({p.i, p.i + j, io::Token::Type});
p.advance(j);
return false;
} else if (j == tries.methods_trie.longest_match(p.peek_str(j))) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Function});
tokens->push_back({p.i, p.i + j, io::Token::Function});
p.advance(j);
return false;
} else if (j == tries.expecting_end_keywords_trie.longest_match(p.peek_str(j))) {
events->push_back(false);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.conditional_keywords_trie.longest_match(p.peek_str(j))) {
if (p.current().flags & RubyState::RubyInternalState::NEWLINE || p.op_last)
events->push_back(false);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.end_keywords_trie.longest_match(p.peek_str(j))) {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
events->push_back(false);
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.builtins_trie.longest_match(p.peek_str(j))) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Constant});
tokens->push_back({p.i, p.i + j, io::Token::Constant});
p.advance(j);
return false;
} else if (j == tries.errors_trie.longest_match(p.peek_str(j))) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Error});
tokens->push_back({p.i, p.i + j, io::Token::Error});
p.advance(j);
return false;
} else if ('A' <= p.peek() && p.peek() <= 'Z' && !(p.peek(j) == '!' || p.peek(j) == '?')) {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
if (j >= 5 && p.peek_str(j).substr(j - 5) == "Error") {
tokens->push_back({p.i, p.i + j, Token::Error});
tokens->push_back({p.i, p.i + j, io::Token::Error});
p.advance(j);
return false;
}
tokens->push_back({p.i, p.i + j, Token::Constant});
tokens->push_back({p.i, p.i + j, io::Token::Constant});
p.advance(j);
return false;
} else {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
if (j == 4 && p.peek_str(4) == "true") {
tokens->push_back({p.i, p.i + j, Token::True});
tokens->push_back({p.i, p.i + j, io::Token::True});
p.advance(4);
return false;
}
if (j == 5 && p.peek_str(5) == "false") {
tokens->push_back({p.i, p.i + j, Token::False});
tokens->push_back({p.i, p.i + j, io::Token::False});
p.advance(5);
return false;
}
if (j == 3 && p.peek_str(3) == "end") {
events->push_back(true);
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(3);
return false;
}
if (j == 5 && p.peek_str(5) == "class") {
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(5);
p.current().flags =
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
@@ -964,7 +962,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
if (j == 6 && p.peek_str(6) == "module") {
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(6);
p.current().flags =
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
@@ -972,28 +970,33 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
if (j == 3 && p.peek_str(3) == "def") {
tokens->push_back({p.i, p.i + j, Token::Keyword});
tokens->push_back({p.i, p.i + j, io::Token::Keyword});
p.advance(3);
p.current().flags =
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
| RubyState::RubyInternalState::DEF_NAME;
return false;
}
if (j > 3 && p.peek_str(3) == "to_") {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, io::Token::Function});
p.advance(j);
}
uint32_t start = p.i;
if (p.peek(j) == ':') {
p.advance(j);
p.advance();
tokens->push_back({start, p.i, Token::Label});
tokens->push_back({start, p.i, io::Token::Label});
return false;
} else if (p.peek(j - 1) == '!' || p.peek(j - 1) == '?') {
p.advance(j);
tokens->push_back({start, p.i, Token::Function});
tokens->push_back({start, p.i, io::Token::Function});
return false;
} else {
p.advance(j);
j = 0;
if (p.peek(j) == '(' || p.peek(j) == '{') {
tokens->push_back({start, p.i, Token::Function});
tokens->push_back({start, p.i, io::Token::Function});
return false;
} else if (p.peek(j) == ' ' || p.peek(j) == '\t') {
j++;
@@ -1006,8 +1009,9 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
if (p.peek(j) == '&'
|| p.peek(j) == '%'
|| p.peek(j) == ':') {
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '>')
|| p.peek(j) == ':'
|| p.peek(j) == '?') {
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '\0')
return false;
} else if (p.peek(j) == '-') {
if (p.peek(j + 1) != '>')
@@ -1023,22 +1027,22 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|| p.peek(j) == '*'
|| p.peek(j) == '/'
|| p.peek(j) == '='
|| p.peek(j) == '?'
|| p.peek(j) == '|'
|| p.peek(j) == '^'
|| p.peek(j) == '<'
|| p.peek(j) == '>'
|| p.peek(j) == '#'
) {
return false;
}
tokens->push_back({start, p.i, Token::Function});
tokens->push_back({start, p.i, io::Token::Function});
return false;
}
}
} else {
uint32_t op_len;
if ((op_len = tries.operator_trie.longest_match(p.peek_str(p.len() - p.i)))) {
tokens->push_back({p.i, p.i + op_len, Token::Operator});
tokens->push_back({p.i, p.i + op_len, io::Token::Operator});
p.advance(op_len);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.set_op_last = true;
@@ -1054,17 +1058,17 @@ void ruby_parse(
void **v_state,
std::string_view line,
bool fl,
std::vector<Token> *tokens,
std::vector<io::Token> *tokens,
std::vector<ParseEvent> *events
) {
RubyParser p(v_state, line);
while (p.i <= p.len()) {
while (p.i < p.len()) {
p.op_last = p.set_op_last;
p.set_op_last = false;
if (p.current().state == RubyState::RubyInternalState::END)
return;
if (p.current().state == RubyState::RubyInternalState::COMMENT) {
tokens->push_back({p.i, p.len(), Token::Comment});
tokens->push_back({p.i, p.len(), io::Token::Comment});
if (p.i == 0 && p.peek_str(4) == "=end") {
p.current().state = RubyState::RubyInternalState::NONE;
events->push_back(true);
@@ -1073,7 +1077,7 @@ void ruby_parse(
}
if (!p.heredoc_start_line
&& p.current().state == RubyState::RubyInternalState::HEREDOC) {
if (handle_heredoc(p, tokens))
if (handle_heredoc(p, tokens, events))
return;
else
continue;
@@ -1096,6 +1100,13 @@ void ruby_parse(
return;
if (!handle_syntax(p, tokens, events))
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
if (p.peek() == '\0') {
if (p.ending)
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.advance();
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
break;
}
continue;
}
if (p.ending)
+51 -1
View File
@@ -1,7 +1,19 @@
#include "internal/syntax/decl.h"
namespace bed::internal::syntax {
TreeCursor::TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative) {
TreeCursor::~TreeCursor() {
ParseState::release(lang, root);
}
TreeCursor::TreeCursor(
Language &lang, ParseState *root,
uint64_t target_line, uint64_t *relative
) : lang(lang), root(root) {
if (!root) {
*relative = 0;
return;
}
ParseState::retain(root);
ParseState *node = root;
while (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
@@ -63,4 +75,42 @@ void TreeCursor::prev() {
}
leaf = nullptr;
}
ParseState *TreeCursor::prefix() {
ParseState *result = nullptr;
for (uint8_t i = 0; i < depth; ++i) {
if (went_left[i])
continue;
auto *branch = stack[i];
ParseState *piece = branch->left;
if (!result) {
ParseState::retain(piece);
result = piece;
} else {
ParseState *next = ParseState::concat(lang, result, piece);
ParseState::release(lang, result);
result = next;
}
}
return result;
}
ParseState *TreeCursor::suffix() {
ParseState *result = nullptr;
for (uint8_t i = depth; i-- > 0;) {
if (!went_left[i])
continue;
auto *branch = stack[i];
ParseState *piece = branch->right;
if (!result) {
ParseState::retain(piece);
result = piece;
} else {
ParseState *next = ParseState::concat(lang, result, piece);
ParseState::release(lang, result);
result = next;
}
}
return result;
}
} // namespace bed::internal::syntax
+174 -77
View File
@@ -5,150 +5,248 @@ Theme::Theme() {
hl.fill({
.fg = 0xF0F0F0,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
});
}
Highlight Theme::get(internal::syntax::Token token) const {
return hl[token.type];
io::Highlight Theme::get(const io::Token::Kind &token) const {
return hl[token];
}
Theme Theme::default_theme() {
Theme theme;
theme.hl[internal::syntax::Token::Shebang] = {
.fg = 0x7DCFFF,
constexpr uint32_t PINK = 0xECA1AE;
constexpr uint32_t GREY = 0xAFB7D3;
constexpr uint32_t BLUE = 0x799EDB;
constexpr uint32_t LIGHT_BLUE = 0xB5BFFE;
constexpr uint32_t RED = 0xF38CAA;
constexpr uint32_t YELLOW = 0xF9E3B1;
constexpr uint32_t PURPLE = 0xA286C7;
constexpr uint32_t CYAN = 0x89DBEA;
constexpr uint32_t LIGHT_GREEN = 0x98CE94;
constexpr uint32_t TURQUOISE = 0x00BDB7;
constexpr uint32_t BRIGHT_GREEN = 0x4AF6CA;
theme.hl[io::Token::TempCurrent] = {
.fg = PINK,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Error] = {
.fg = 0xEF5168,
theme.hl[io::Token::BufferName] = {
.fg = PURPLE,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Comment] = {
.fg = 0xAAAAAA,
theme.hl[io::Token::AddressSeperator] = {
.fg = GREY,
.bg = 0x000000,
.flags = Highlight::Italic,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::String] = {
theme.hl[io::Token::AddressSymbol] = {
.fg = BLUE,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Mark] = {
.fg = LIGHT_BLUE,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::RubyFunction] = {
.fg = RED,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::RubyArg] = {
.fg = LIGHT_GREEN,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Shell] = {
.fg = BRIGHT_GREEN,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::File] = {
.fg = GREY,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Suffix] = {
.fg = TURQUOISE,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Color1] = {
.fg = 0x7AA2F7,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Color2] = {
.fg = 0xAAD94C,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Escape] = {
theme.hl[io::Token::Color3] = {
.fg = 0xFF9E64,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Color4] = {
.fg = 0xBB9AF7,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Color5] = {
.fg = 0xFF757F,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Warning] = {
.fg = 0xF1C55A,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Data] = {
.fg = GREY,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Shebang] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Interpolation] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Regexp] = {
.fg = 0xD2A6FF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Number] = {
.fg = 0xE6C08A,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::True] = {
.fg = 0x7AE93C,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::False] = {
theme.hl[io::Token::Error] = {
.fg = 0xEF5168,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Char] = {
theme.hl[io::Token::Comment] = {
.fg = 0xAAAAAA,
.bg = 0x000000,
.flags = io::Highlight::Italic,
};
theme.hl[io::Token::String] = {
.fg = 0xAAD94C,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Escape] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Interpolation] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Regexp] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Number] = {
.fg = 0xE6C08A,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::True] = {
.fg = 0x7AE93C,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::False] = {
.fg = 0xEF5168,
.bg = 0x000000,
.flags = io::Highlight::None,
};
theme.hl[io::Token::Char] = {
.fg = 0xFFAF70,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Keyword] = {
theme.hl[io::Token::Keyword] = {
.fg = 0xFF8F40,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::KeywordOperator] = {
theme.hl[io::Token::KeywordOperator] = {
.fg = 0xF07178,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Operator] = {
theme.hl[io::Token::Operator] = {
.fg = 0xFFFFFF,
.bg = 0x000000,
.flags = Highlight::Italic,
.flags = io::Highlight::Italic,
};
theme.hl[internal::syntax::Token::Function] = {
theme.hl[io::Token::Function] = {
.fg = 0xFFAF70,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Type] = {
theme.hl[io::Token::Type] = {
.fg = 0xF07178,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Constant] = {
theme.hl[io::Token::Constant] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::VariableInstance] = {
theme.hl[io::Token::VariableInstance] = {
.fg = 0x95E6CB,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::VariableGlobal] = {
theme.hl[io::Token::VariableGlobal] = {
.fg = 0xF07178,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Annotation] = {
theme.hl[io::Token::Annotation] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Directive] = {
theme.hl[io::Token::Directive] = {
.fg = 0xFF8F40,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Label] = {
theme.hl[io::Token::Label] = {
.fg = 0xD2A6FF,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Brace1] = {
.fg = 0xD2A6FF,
theme.hl[io::Token::Brace1] = {
.fg = BRIGHT_GREEN,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Brace2] = {
.fg = 0xFFAFAF,
theme.hl[io::Token::Brace2] = {
.fg = YELLOW,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Brace3] = {
theme.hl[io::Token::Brace3] = {
.fg = 0xFFFF00,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Brace4] = {
.fg = 0x0FFF0F,
theme.hl[io::Token::Brace4] = {
.fg = CYAN,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
theme.hl[internal::syntax::Token::Brace5] = {
theme.hl[io::Token::Brace5] = {
.fg = 0xFF0F0F,
.bg = 0x000000,
.flags = Highlight::None,
.flags = io::Highlight::None,
};
return theme;
}
@@ -156,7 +254,6 @@ Theme Theme::default_theme() {
Theme Theme::from_name(std::string_view name) {
if (name == "default")
return default_theme();
throw std::runtime_error("Unknown theme: " + std::string(name));
throw ed_error("Unknown theme: " + std::string(name));
}
} // namespace bed::internal::theme
+28 -61
View File
@@ -1,67 +1,8 @@
#include "internal/ui/command.h"
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed::internal::ui {
/*template <typename F>
static void for_each_cluster(std::string_view s, F &&f) {
unicode_width_state_t state;
unicode_width_init(&state);
size_t i = 0;
while (i < s.size()) {
unsigned char c = static_cast<unsigned char>(s[i]);
size_t bytes = 1;
int width = 0;
if (c < 128) {
width = unicode_width_process(&state, c);
} else {
uint_least32_t cp;
size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp);
bytes = decoded > 0 ? decoded : 1;
width = unicode_width_process(&state, cp);
}
if (width < 0)
width = 0;
f(i, bytes, width);
i += bytes;
}
}
static int display_width(std::string_view s) {
int w = 0;
for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; });
return w;
}
static uint16_t count_clusters(std::string_view s) {
uint16_t n = 0;
for_each_cluster(s, [&](size_t, size_t, int) { ++n; });
return n;
}
static std::vector<uint16_t> wrap_offsets(std::string_view line, uint16_t avail) {
std::vector<uint16_t> offsets{0};
int col = 0;
for_each_cluster(line, [&](uint16_t i, uint16_t, int w) {
if (col + w > avail && col > 0) {
offsets.push_back(i);
col = 0;
}
col += w;
});
return offsets;
}
// The word under/before `byte_pos`, split on plain ASCII spaces. Used to
// pick what prefix to hand the suggestion trie.
// TODO: change to use libgrapheme word break here.
static std::string current_word(const std::string &line, size_t byte_pos) {
size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1);
start = (start == std::string::npos) ? 0 : start + 1;
if (byte_pos < start)
byte_pos = start;
return line.substr(start, byte_pos - start);
}*/
CommandIO::CommandIO(BEd &bed) : bed(bed) {
if (bed.prompt_mode)
prompt = bed.prompt(bed);
@@ -69,6 +10,17 @@ CommandIO::CommandIO(BEd &bed) : bed(bed) {
}
std::pair<std::string, bool> CommandIO::run() {
if (!bed.io.interactive())
return run_pipe();
return run_terminal();
}
std::pair<std::string, bool> CommandIO::run_pipe() {
bed.io.write(prompt);
return bed.io.read_pipe();
}
std::pair<std::string, bool> CommandIO::run_terminal() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
@@ -151,7 +103,22 @@ void CommandIO::redraw() {
bed.io.write("\x1b[2K", 4);
bed.io.move_cursor(start, 1);
bed.io.write(prompt);
bed.io.write(cmd);
const auto tokens = parser::Parser::get_highlight(cmd, bed);
uint32_t pos = 0;
for (const auto &token : tokens) {
const uint32_t tstart = token.start;
const uint32_t tend = token.end;
if (tstart > cmd.size())
break;
if (pos < tstart)
bed.io.write(cmd.data() + pos, tstart - pos);
bed.io.apply(token.type);
bed.io.write(cmd.data() + tstart, tend - tstart);
bed.io.reset();
pos = tend;
}
if (pos < cmd.size())
bed.io.write(cmd.data() + pos, cmd.size() - pos);
bed.io.move_cursor(start, prompt.size() + cursor + 1);
}
} // namespace bed::internal::ui
+344 -104
View File
@@ -1,26 +1,170 @@
#include "internal/ui/text_mode.h"
#include "bed.h"
namespace bed::internal::ui {
TextMode::TextMode(BEd &bed) : bed(bed) {
cursor = 0;
namespace bed::internal::ui::text_mode {
// TODO: make this into a proper layout engine, in order to avoid so many expensive calculations repeatedly.
template <typename F>
static void for_each_cluster(std::string_view s, F &&f) {
size_t cluster_start = 0;
while (cluster_start < s.size()) {
size_t cluster_len =
grapheme_next_character_break_utf8(
s.data() + cluster_start,
s.size() - cluster_start
);
if (cluster_len == 0)
break;
size_t cluster_end = cluster_start + cluster_len;
unicode_width_state_t state;
unicode_width_init(&state);
size_t i = cluster_start;
int width = 0;
while (i < cluster_end) {
uint_least32_t cp;
size_t decoded =
grapheme_decode_utf8(s.data() + i, cluster_end - i, &cp);
if (decoded == 0)
decoded = 1;
int w = unicode_width_process(&state, cp);
if (w > width)
width = w;
i += decoded;
}
if (width < 0)
width = 0;
if (!f(cluster_start, cluster_len, width))
return;
cluster_start = cluster_end;
}
}
static size_t previous_cluster(std::string_view line, size_t col) {
size_t previous = 0;
for_each_cluster(line, [&](size_t start, size_t len, int) {
if (start >= col)
return false;
previous = start;
return start + len < col;
});
return previous;
}
static size_t next_cluster(std::string_view line, size_t col) {
size_t next = line.size();
for_each_cluster(line, [&](size_t start, size_t len, int) {
if (start >= col) {
next = start + len;
return false;
}
return true;
});
return next;
}
static size_t visual_column(std::string_view line, size_t col) {
size_t width = 0;
for_each_cluster(line, [&](size_t start, size_t, int cluster_width) {
if (start >= col)
return false;
width += cluster_width;
return true;
});
return width;
}
static size_t byte_column(std::string_view line, size_t target_width) {
size_t width = 0;
size_t col = 0;
for_each_cluster(line, [&](size_t start, size_t len, int cluster_width) {
if (width + cluster_width > target_width)
return false;
width += cluster_width;
col = start + len;
if (width == target_width)
return false;
return true;
});
return col;
}
static std::vector<std::pair<size_t, size_t>> wrap_line(std::string_view line, size_t avail) {
std::vector<std::pair<size_t, size_t>> result;
if (avail == 0) {
result.push_back({0, line.size()});
return result;
}
size_t begin = 0;
size_t end = 0;
int col = 0;
for_each_cluster(line, [&](size_t i, size_t len, int width) {
if (col > 0 && col + width > int(avail)) {
result.push_back({begin, i});
begin = i;
col = 0;
}
col += width;
end = i + len;
return true;
});
if (begin < line.size() || result.empty())
result.push_back({begin, end});
return result;
}
void TextMode::layout_lines() {
vase::Iterator it(vase, 0, Direction::Forward);
lines.clear();
while (it.next()) {
auto wrapped = wrap_line(it.line, term_width);
lines.push_back({});
for (auto [begin, end] : wrapped)
lines.back().push_back({begin, end - begin});
}
}
TextMode::TextMode(
BEd &bed, vase::Shard *vase, syntax::Language *lang, void *state
) : vase(vase), bed(bed) {
if (lang)
parser.emplace(*lang, vase, state);
cursor = vase::eof_point(vase);
}
std::pair<vase::Shard *, bool> TextMode::run() {
if (!bed.io.interactive())
return run_pipe();
return run_terminal();
}
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
vase::Shard::release(vase);
vase = nullptr;
while (true) {
auto [str, eof] = bed.io.read_pipe();
if (str == "." || eof)
break;
vase = vase::insert(&bed.append, vase, &cursor, str.data(), str.length());
vase = vase::insert(&bed.append, vase, &cursor, '\n');
}
return {vase, false};
}
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
throw ed_error("Invalid cursor position.");
start = row;
height = rows - row + 1;
term_width = cols;
term_height = rows;
cmd.clear();
cursor = 0;
redraw();
bool running = true;
bool cancelled = false;
while (running) {
redraw();
io::KeyEvent res = bed.io.read_key();
switch (res.type) {
case io::KeyEvent::KeyType::EOF_:
@@ -35,104 +179,161 @@ std::pair<vase::Shard *, bool> TextMode::run() {
case io::KeyEvent::Modifier::ALT:
case io::KeyEvent::Modifier::CTRL_ALT:
case io::KeyEvent::Modifier::CTRL:
if (res.text[0] == 'c') {
cancelled = true;
running = false;
}
break;
case io::KeyEvent::Modifier::NONE:
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
if (cursor > 0) {
cmd.erase(--cursor, 1);
if (!vase)
break;
if (cursor.row || cursor.col) {
auto last = cursor;
if (last.col) {
vase::Iterator it(vase, last.row, Direction::Forward);
if (!it.next())
throw ed_error("Invalid cursor position.");
last.col = previous_cluster(it.line, last.col);
} else if (last.row) {
--last.row;
vase::Iterator it(vase, last.row, Direction::Forward);
if (!it.next())
throw ed_error("Invalid cursor position.");
last.col = it.line.size();
} else {
break;
}
vase::Range r = {last, cursor};
vase = vase::erase(vase, r);
if (parser) {
if (cursor.row == last.row) {
parser->dirty(vase, cursor.row, 1);
} else {
parser->erase(vase, cursor.row, 1);
parser->dirty(vase, last.row, 1);
}
}
cursor = last;
}
} else if (res.text[0] == '\n') {
size_t lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
grow(lines + 1);
cmd.insert(cursor++, 1, '\n');
if (vase && cursor.row == vase->lines) {
vase::Iterator it(vase, vase->lines, Direction::Backward);
if (it.next() && it.line == ".") {
vase = vase::erase(vase, vase->lines + 1, vase->lines + 1);
running = false;
break;
}
}
vase = vase::insert(&bed.append, vase, &cursor, '\n');
if (parser)
parser->insert(vase, cursor.row - 1, 1);
} else {
cmd.insert(cursor, res.text);
cursor += res.text.size();
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
if (parser)
parser->dirty(vase, cursor.row, 1);
}
break;
}
break;
case io::KeyEvent::KeyType::PASTE:
cmd.insert(cursor, res.text);
cursor += res.text.size();
break;
case io::KeyEvent::KeyType::PASTE: {
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
if (parser) {
if (lines) {
parser->insert(vase, cursor.row - lines, lines);
parser->dirty(vase, cursor.row - lines, 1);
} else {
parser->dirty(vase, cursor.row, 1);
}
}
} break;
case io::KeyEvent::KeyType::SPECIAL:
if (!vase)
break;
switch (res.special_key) {
case io::KeyEvent::SpecialKey::UNKNOWN:
break;
case io::KeyEvent::SpecialKey::RIGHT:
if (cursor < cmd.size())
++cursor;
break;
case io::KeyEvent::SpecialKey::LEFT:
if (cursor > 0)
--cursor;
break;
case io::KeyEvent::SpecialKey::RIGHT: {
vase::Iterator it(vase, cursor.row, Direction::Forward);
if (!it.next())
throw ed_error("Invalid cursor position.");
cursor.col = next_cluster(it.line, cursor.col);
} break;
case io::KeyEvent::SpecialKey::LEFT: {
vase::Iterator it(vase, cursor.row, Direction::Forward);
if (!it.next())
throw ed_error("Invalid cursor position.");
cursor.col = previous_cluster(it.line, cursor.col);
} break;
case io::KeyEvent::SpecialKey::UP: {
size_t line_start =
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
if (line_start == std::string::npos)
line_start = 0;
else
++line_start;
size_t col = cursor - line_start;
if (line_start == 0)
if (!cursor.row)
break;
size_t prev_end = line_start - 1;
size_t prev_start =
cmd.rfind('\n', prev_end == 0 ? 0 : prev_end - 1);
if (prev_start == std::string::npos)
prev_start = 0;
else
++prev_start;
size_t prev_len = prev_end - prev_start;
cursor = prev_start + std::min(col, prev_len);
break;
}
vase::Iterator current(vase, cursor.row, Direction::Forward);
if (!current.next())
throw ed_error("Invalid cursor position.");
size_t wanted = visual_column(current.line, cursor.col);
--cursor.row;
vase::Iterator previous(vase, cursor.row, Direction::Forward);
if (!previous.next())
throw ed_error("Invalid cursor position.");
cursor.col = byte_column(previous.line, wanted);
} break;
case io::KeyEvent::SpecialKey::DOWN: {
size_t line_start =
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
if (line_start == std::string::npos)
line_start = 0;
else
++line_start;
size_t col = cursor - line_start;
size_t line_end = cmd.find('\n', cursor);
if (line_end == std::string::npos)
line_end = cmd.size();
if (line_end == cmd.size())
if (cursor.row >= vase->lines)
break;
size_t next_start = line_end + 1;
size_t next_end = cmd.find('\n', next_start);
if (next_end == std::string::npos)
next_end = cmd.size();
size_t next_len = next_end - next_start;
cursor = next_start + std::min(col, next_len);
break;
}
vase::Iterator current(vase, cursor.row, Direction::Forward);
if (!current.next())
throw ed_error("Invalid cursor position.");
size_t wanted = visual_column(current.line, cursor.col);
++cursor.row;
vase::Iterator previous(vase, cursor.row, Direction::Forward);
if (!previous.next())
throw ed_error("Invalid cursor position.");
cursor.col = byte_column(previous.line, wanted);
} break;
case io::KeyEvent::SpecialKey::DELETE:
if (cursor < cmd.size())
cmd.erase(cursor, 1);
auto next = cursor;
vase::Iterator it(vase, cursor.row, Direction::Forward);
if (!it.next())
break;
if (cursor.col < it.line.size()) {
vase::Iterator it(vase, next.row, Direction::Forward);
if (!it.next())
throw ed_error("Invalid cursor position.");
next.col = next_cluster(it.line, cursor.col);
} else if (cursor.row < vase->lines) {
++next.row;
next.col = 0;
} else {
break;
}
break;
vase::Range r = {cursor, next};
vase = vase::erase(vase, r);
if (parser) {
if (cursor.row != next.row)
parser->erase(vase, cursor.row + 1, 1);
parser->dirty(vase, cursor.row, 1);
}
redraw();
if (cmd.size() >= 3 && cmd.compare(cmd.size() - 3, 3, "\n.\n") == 0) {
cmd.erase(cmd.size() - 3);
cursor = cmd.size();
running = false;
}
break;
}
}
size_t total_lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
uint16_t last_row = start + total_lines;
layout_lines();
size_t required_height = 0;
for (const auto &elem : lines)
required_height += elem.size();
uint16_t last_row = start + required_height;
bed.io.move_cursor(last_row, 1);
bed.io.write("\n", 1);
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
bed.io.write("\n");
return {vase, cancelled};
}
void TextMode::grow(size_t required_height) {
void TextMode::grow() {
size_t required_height = 0;
for (const auto &elem : lines)
required_height += elem.size();
auto [rows, cols] = bed.io.terminal_size();
term_height = rows;
term_width = cols;
@@ -143,46 +344,85 @@ void TextMode::grow(size_t required_height) {
return;
bed.io.move_cursor(rows, 1);
for (long i = 0; i < overflow; ++i)
bed.io.write("\n", 1);
bed.io.write("\n");
start -= overflow;
if (start < 1)
start = 1;
}
void TextMode::redraw() {
auto [rows, cols] = bed.io.terminal_size();
term_height = rows;
term_width = cols;
layout_lines();
grow();
bed.io.write("\x1b[?25l", 6);
for (uint16_t i = 0; i < height; ++i) {
bed.io.move_cursor(start + i, 1);
bed.io.write("\x1b[2K", 4);
}
size_t line = 0;
size_t line_start = 0;
for (size_t i = 0; i < cursor; ++i) {
if (cmd[i] == '\n') {
++line;
line_start = i + 1;
}
}
size_t col = cursor - line_start;
size_t pos = 0;
uint16_t screen_line = start;
while (pos <= cmd.size()) {
size_t end = cmd.find('\n', pos);
if (end == std::string::npos)
end = cmd.size();
if (screen_line < start + height) {
size_t len = std::min(end - pos, size_t(term_width));
bed.io.move_cursor(screen_line, 1);
bed.io.write(cmd.substr(pos, len));
}
if (end == cmd.size())
uint64_t line = 0;
size_t cursor_screen_row = start;
size_t cursor_screen_col = 0;
vase::Iterator it(vase, line, Direction::Forward);
while (it.next()) {
if (screen_line >= start + height)
break;
pos = end + 1;
std::string_view l(it.line);
const std::vector<io::Token> *tokens = nullptr;
if (parser && parser->lines.size() > line)
tokens = &parser->lines[line].second;
size_t token_index = 0;
for (auto &row : lines[line]) {
const size_t row_start = row.start;
const size_t row_end = row.start + row.length;
bed.io.move_cursor(screen_line, 1);
if (tokens) {
while (token_index < tokens->size() && (*tokens)[token_index].end <= row_start)
++token_index;
size_t pos = row_start;
for (size_t i = token_index; i < tokens->size(); ++i) {
const auto &token = (*tokens)[i];
if (token.start >= row_end)
break;
const size_t begin = std::max<size_t>(
token.start,
row_start
);
const size_t end = std::min<size_t>(
token.end,
row_end
);
if (begin > pos)
bed.io.write(l.data() + pos, begin - pos);
if (begin < end) {
bed.io.apply(token.type);
bed.io.write(l.data() + begin, end - begin);
bed.io.reset();
pos = end;
}
}
if (pos < row_end)
bed.io.write(l.data() + pos, row_end - pos);
bed.io.reset();
} else {
bed.io.write(l.substr(row_start, row.length));
}
if (line == cursor.row && cursor.col >= row_start && cursor.col <= row_end) {
cursor_screen_row = screen_line;
cursor_screen_col = 0;
for_each_cluster(
l.substr(row_start, cursor.col - row_start),
[&](size_t, size_t, int width) {
cursor_screen_col += width;
return true;
}
);
}
++screen_line;
}
size_t vis_col = std::min(col, size_t(term_width - 1));
bed.io.move_cursor(start + line, vis_col + 1);
++line;
}
cursor_screen_col = std::min(cursor_screen_col, size_t(term_width - 1));
bed.io.move_cursor(cursor_screen_row, cursor_screen_col + 1);
bed.io.write("\x1b[?25h", 6);
}
} // namespace bed::internal::ui
} // namespace bed::internal::ui::text_mode
+4 -11
View File
@@ -90,12 +90,7 @@ Shard *substitute(
if (matches.empty())
return root;
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
struct Edit {
uint64_t line;
uint64_t old_lines;
uint64_t new_lines;
};
uint64_t orig_line = start;
uint64_t current_line = 1;
int64_t line_delta = 0;
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
@@ -109,7 +104,7 @@ Shard *substitute(
Shard::release(remaining);
pieces.push_back(keep);
remaining = rest;
orig_line += keep ? keep->lines : 0;
current_line += keep ? keep->lines : 0;
}
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
Shard::release(remaining);
@@ -152,13 +147,11 @@ Shard *substitute(
}
}
Shard::release(dropped);
if (old_lines || new_lines) {
uint64_t report_line = (uint64_t)((int64_t)orig_line + line_delta);
uint64_t report_line = (uint64_t)((int64_t)current_line + line_delta);
if (on_edit)
on_edit(report_line, old_lines, new_lines);
line_delta += (int64_t)new_lines - (int64_t)old_lines;
}
orig_line += old_lines;
current_line += old_lines;
cursor = match.end;
}
pieces.push_back(remaining);
+2
View File
@@ -289,6 +289,8 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
last = nullptr;
if (!pieces.empty())
last = (Petal *)pieces.back();
else
return nullptr;
}
if (last && ending[0] == '\r')
last->length--;
+45 -8
View File
@@ -74,14 +74,7 @@ void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, ui
++lines;
last_line = ++data;
}
uint64_t col = 0;
uint64_t remaining = end - last_line;
while (remaining) {
uint64_t n = grapheme_next_character_break_utf8(last_line, remaining);
last_line += n;
remaining -= n;
++col;
}
uint64_t col = end - last_line;
if (lines) {
point->row += lines;
point->col = col;
@@ -122,6 +115,22 @@ Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, ui
return insert(ap, root, &range.start, data, len);
}
Point eof_point(Shard *root) {
if (!root)
return {0, 0};
Point result{
.row = root->lines,
.col = 0
};
PetalIterator it(root, Direction::Forward);
it.seek_line(root->lines);
const char *data;
uint64_t len;
while (it.next(&data, &len))
result.col += len;
return result;
}
uint64_t offset_of(Shard *root, Point point) {
return offset_of(root, point.row) + point.col;
}
@@ -201,6 +210,34 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
return result;
}
Shard *replace(Shard *root, Shard *text, uint64_t start, uint64_t end) {
if (!start || !end)
throw ed_error("Invalid range.");
start--;
end--;
if (!root)
throw ed_error("line range out of bounds");
uint64_t line_count = root->lines + 1;
if (start > end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1) - 1;
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard *a = Shard::concat(left, text);
Shard *new_root = Shard::concat(a, right);
Shard::release(left);
Shard::release(rest);
Shard::release(middle);
Shard::release(right);
Shard::release(a);
Shard::release(root);
return new_root;
}
Shard *erase(Shard *root, uint64_t start, uint64_t end) {
if (!start || !end)
throw ed_error("Invalid range.");
+1 -2
View File
@@ -4,8 +4,7 @@
int main(int argc, char *argv[]) {
std::vector<std::string> args(argv, argv + argc);
try {
bed::internal::io::IO io = bed::internal::io::IO();
bed::BEd ed(args, io);
bed::BEd ed(args);
ed.run();
} catch (bed::fatal_error &e) {
if (e.code)