Compare commits
13
Commits
main
..
a276bf60da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a276bf60da
|
||
|
|
3bfebe30ce
|
||
|
|
46bdb8cd0f
|
||
|
|
662df68ab4
|
||
|
|
cd9583ec34
|
||
|
|
0104f96d51
|
||
|
|
abbfcc352a
|
||
|
|
f8975a60d8
|
||
|
|
33dc9ad507
|
||
|
|
956bdd6039
|
||
|
|
151817973f
|
||
|
|
25f4d8efa8
|
||
|
|
569b69263a
|
+4
-3
@@ -18,8 +18,8 @@ struct BEd {
|
|||||||
internal::functions::Function eof_op;
|
internal::functions::Function eof_op;
|
||||||
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
|
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
|
||||||
internal::theme::Theme theme;
|
internal::theme::Theme theme;
|
||||||
std::unordered_map<std::string, internal::syntax::Language> languages;
|
std::unordered_map<std::string, internal::syntax::Language *> languages;
|
||||||
internal::io::IO &io;
|
internal::io::IO io;
|
||||||
internal::vase::AppendStorage append{"/tmp"};
|
internal::vase::AppendStorage append{"/tmp"};
|
||||||
|
|
||||||
bool help_mode = false;
|
bool help_mode = false;
|
||||||
@@ -39,7 +39,7 @@ struct BEd {
|
|||||||
internal::buffer::Range prev_2;
|
internal::buffer::Range prev_2;
|
||||||
internal::marks::MarksEngine marks;
|
internal::marks::MarksEngine marks;
|
||||||
|
|
||||||
BEd(std::vector<std::string> args, internal::io::IO &io);
|
BEd(std::vector<std::string> args);
|
||||||
~BEd();
|
~BEd();
|
||||||
|
|
||||||
internal::buffer::Buffer &buffer(const std::string &);
|
internal::buffer::Buffer &buffer(const std::string &);
|
||||||
@@ -47,6 +47,7 @@ struct BEd {
|
|||||||
internal::buffer::Range &prev();
|
internal::buffer::Range &prev();
|
||||||
void mark(uint8_t, internal::buffer::Line);
|
void mark(uint8_t, internal::buffer::Line);
|
||||||
|
|
||||||
|
void print_help();
|
||||||
void handle(std::string_view cmd, bool eof);
|
void handle(std::string_view cmd, bool eof);
|
||||||
void run();
|
void run();
|
||||||
void suffix_handle(char s);
|
void suffix_handle(char s);
|
||||||
|
|||||||
@@ -15,17 +15,4 @@ struct fatal_error : std::runtime_error {
|
|||||||
struct ed_error : std::runtime_error {
|
struct ed_error : std::runtime_error {
|
||||||
ed_error(std::string msg) : std::runtime_error(msg) {}
|
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
|
} // namespace bed
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "clip.h"
|
#include "./types/clip.h"
|
||||||
|
#include "./types/generic.h"
|
||||||
#include "decl.h"
|
#include "decl.h"
|
||||||
#include "generic.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace bed::internal::buffer {
|
|||||||
struct Buffer {
|
struct Buffer {
|
||||||
enum struct Kind : uint8_t {
|
enum struct Kind : uint8_t {
|
||||||
Generic,
|
Generic,
|
||||||
Null,
|
History,
|
||||||
Clip,
|
Clip,
|
||||||
Cancel,
|
Cancel,
|
||||||
Shell,
|
Shell,
|
||||||
@@ -26,7 +26,7 @@ struct Buffer {
|
|||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
explicit Buffer(std::string name, Kind kind)
|
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 ~Buffer() = default;
|
||||||
|
|
||||||
virtual bool waste() = 0;
|
virtual bool waste() = 0;
|
||||||
@@ -43,6 +43,7 @@ struct Buffer {
|
|||||||
virtual void join(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
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 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 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 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 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;
|
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
|||||||
@@ -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 ®ex, 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
|
#pragma once
|
||||||
|
|
||||||
#include "decl.h"
|
#include "../decl.h"
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace bed::internal::buffer {
|
namespace bed::internal::buffer {
|
||||||
struct ClipBuffer : Buffer {
|
struct ClipBuffer : Buffer {
|
||||||
@@ -24,6 +23,7 @@ struct ClipBuffer : Buffer {
|
|||||||
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) 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 remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
void append(BEd &ctx, vase::Shard *text, uint64_t 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 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 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;
|
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.h"
|
||||||
|
#include "history.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::string action;
|
||||||
|
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 list_history(BEd &ctx);
|
||||||
|
HistoryBuffer *get_history(uint64_t version);
|
||||||
|
void snapshot(std::string action);
|
||||||
|
bool undo(BEd &ctx);
|
||||||
|
bool redo(BEd &ctx);
|
||||||
|
uint64_t prune(int);
|
||||||
|
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 ®ex, 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
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.h"
|
||||||
|
#include "shard.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct HistoryBuffer : ShardBuffer {
|
||||||
|
HistoryBuffer(
|
||||||
|
std::string name, vase::Shard *root,
|
||||||
|
const syntax::ParserSnapshot &snapshot
|
||||||
|
) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {}
|
||||||
|
|
||||||
|
bool waste() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void load(BEd &, vase::Shard *) override {
|
||||||
|
throw ed_error("History buffers are 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("History buffers are read-only.");
|
||||||
|
}
|
||||||
|
void join(BEd &, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("History buffers are read-only.");
|
||||||
|
}
|
||||||
|
void remove(BEd &, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("History buffers are read-only.");
|
||||||
|
}
|
||||||
|
void append(BEd &, vase::Shard *, uint64_t) override {
|
||||||
|
throw ed_error("History buffers are read-only.");
|
||||||
|
}
|
||||||
|
void replace(BEd &, vase::Shard *, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("History buffers are read-only.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -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
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "definitions.h"
|
#include "definitions.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "tokens.h"
|
||||||
|
|
||||||
namespace bed::internal::io {
|
namespace bed::internal::io {
|
||||||
struct KeyEvent {
|
struct KeyEvent {
|
||||||
@@ -57,13 +58,27 @@ struct IO {
|
|||||||
static void enable_raw();
|
static void enable_raw();
|
||||||
static volatile std::atomic_bool resized;
|
static volatile std::atomic_bool resized;
|
||||||
static void handle_sigwinch(int);
|
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();
|
||||||
|
|
||||||
IO(const IO &) = delete;
|
IO(const IO &) = delete;
|
||||||
IO &operator=(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 enable_mouse();
|
||||||
void disable_mouse();
|
void disable_mouse();
|
||||||
|
|
||||||
@@ -71,14 +86,14 @@ struct IO {
|
|||||||
std::pair<uint16_t, uint16_t> cursor_position();
|
std::pair<uint16_t, uint16_t> cursor_position();
|
||||||
void move_cursor(uint16_t row, uint16_t col);
|
void move_cursor(uint16_t row, uint16_t col);
|
||||||
|
|
||||||
|
std::pair<std::string, bool> read_pipe();
|
||||||
|
|
||||||
KeyEvent read_key();
|
KeyEvent read_key();
|
||||||
void write(const char *, uint64_t);
|
void write(const char *, uint64_t);
|
||||||
void write(std::string_view);
|
void write(std::string_view);
|
||||||
void write_line(std::string_view);
|
void write_line(std::string_view);
|
||||||
void run_pty(const std::string &);
|
void run_pty(const std::string &);
|
||||||
|
|
||||||
std::deque<char> input_queue;
|
|
||||||
|
|
||||||
KeyEvent::ReadResult get_next_byte(char &out);
|
KeyEvent::ReadResult get_next_byte(char &out);
|
||||||
void enqueue_bytes(const std::string &bytes);
|
void enqueue_bytes(const std::string &bytes);
|
||||||
static int utf8_seq_len(uint8_t byte);
|
static int utf8_seq_len(uint8_t byte);
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#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,
|
||||||
|
Address,
|
||||||
|
Offset,
|
||||||
|
AddressRegex,
|
||||||
|
AddressSymbol,
|
||||||
|
Number,
|
||||||
|
Mark,
|
||||||
|
RubyFunction,
|
||||||
|
RubyArg,
|
||||||
|
Function,
|
||||||
|
Any,
|
||||||
|
Shell,
|
||||||
|
Ruby,
|
||||||
|
File,
|
||||||
|
Replacement,
|
||||||
|
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
|
||||||
@@ -22,7 +22,7 @@ struct MarksEngine {
|
|||||||
continue;
|
continue;
|
||||||
if (marks[i].number == UINT64_MAX)
|
if (marks[i].number == UINT64_MAX)
|
||||||
continue;
|
continue;
|
||||||
if (marks[i].number >= start)
|
if (marks[i].number > start)
|
||||||
marks[i].number += count;
|
marks[i].number += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,12 +85,12 @@ struct Parser {
|
|||||||
std::string_view cmd;
|
std::string_view cmd;
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
Command *command;
|
Command *command;
|
||||||
std::vector<ui::Token> *tokens;
|
std::vector<io::Token> *tokens;
|
||||||
CompletionContext *completion;
|
CompletionContext *completion;
|
||||||
|
|
||||||
explicit Parser(
|
explicit Parser(
|
||||||
std::string_view cmd, BEd &bed, Command *command,
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
std::vector<ui::Token> *tokens, CompletionContext *completion
|
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||||
);
|
);
|
||||||
|
|
||||||
char peek(uint16_t = 0); // == \0 if at eof.
|
char peek(uint16_t = 0); // == \0 if at eof.
|
||||||
@@ -107,7 +107,7 @@ struct Parser {
|
|||||||
void operation();
|
void operation();
|
||||||
|
|
||||||
void parse();
|
void parse();
|
||||||
|
static std::vector<io::Token> get_highlight(std::string_view cmd, BEd &bed);
|
||||||
static Command get_command(std::string_view, BEd &);
|
static Command get_command(std::string_view, BEd &);
|
||||||
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
|
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
|
||||||
};
|
};
|
||||||
|
|||||||
+102
-70
@@ -1,73 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/io/tokens.h"
|
||||||
#include "internal/trie/trie.h"
|
#include "internal/trie/trie.h"
|
||||||
|
#include "internal/vase/vase.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
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 {
|
struct ParseEvent {
|
||||||
uint8_t closing;
|
uint8_t closing;
|
||||||
ParseEvent(uint8_t t) : closing(t) {}
|
ParseEvent(uint8_t t) : closing(t) {}
|
||||||
@@ -75,7 +13,11 @@ struct ParseEvent {
|
|||||||
|
|
||||||
struct Language {
|
struct Language {
|
||||||
std::function<void *()> none_state;
|
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<void *(void *)> copy;
|
||||||
std::function<bool(void *, void *)> equal;
|
std::function<bool(void *, void *)> equal;
|
||||||
std::function<void(void *)> destroy;
|
std::function<void(void *)> destroy;
|
||||||
@@ -85,35 +27,125 @@ struct ParseState {
|
|||||||
static constexpr uint64_t BRANCH_BIT = 1ull << 63;
|
static constexpr uint64_t BRANCH_BIT = 1ull << 63;
|
||||||
static constexpr uint64_t LINES_MASK = ~BRANCH_BIT;
|
static constexpr uint64_t LINES_MASK = ~BRANCH_BIT;
|
||||||
uint64_t header;
|
uint64_t header;
|
||||||
|
uint16_t height;
|
||||||
|
std::atomic_uint16_t refs;
|
||||||
bool is_branch() const {
|
bool is_branch() const {
|
||||||
return header & BRANCH_BIT;
|
return header & BRANCH_BIT;
|
||||||
}
|
}
|
||||||
uint64_t lines() const {
|
uint64_t lines() const {
|
||||||
return header & LINES_MASK;
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ParseStateBranch : ParseState {
|
struct ParseStateBranch : ParseState {
|
||||||
ParseState *left;
|
ParseState *left;
|
||||||
ParseState *right;
|
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 {
|
struct ParseStateLeaf : ParseState {
|
||||||
void *state;
|
static constexpr uint64_t MAX_CHUNK = 512;
|
||||||
uint32_t n;
|
uint32_t n{0};
|
||||||
uint32_t cap;
|
|
||||||
uint16_t *blocks;
|
|
||||||
static constexpr uint16_t IS_CLOSING = 0x8000;
|
static constexpr uint16_t IS_CLOSING = 0x8000;
|
||||||
static constexpr uint16_t LINE_MASK = 0x7fff;
|
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 ⟨
|
||||||
|
std::vector<ParseState *> pieces;
|
||||||
|
std::vector<uint16_t> blocks;
|
||||||
|
void *piece_state{nullptr};
|
||||||
|
uint64_t chunk_start{0};
|
||||||
|
uint64_t chunk_lines{0};
|
||||||
|
ParsePieceBuilder(Language &lang, uint64_t first_line)
|
||||||
|
: lang(lang), chunk_start(first_line) {}
|
||||||
|
void add(
|
||||||
|
void *state,
|
||||||
|
uint64_t line,
|
||||||
|
const std::vector<ParseEvent> &events
|
||||||
|
) {
|
||||||
|
if (chunk_lines == 0) {
|
||||||
|
chunk_start = line;
|
||||||
|
piece_state = lang.copy(state);
|
||||||
|
}
|
||||||
|
for (const auto &ev : events) {
|
||||||
|
blocks.push_back(
|
||||||
|
(ev.closing ? ParseStateLeaf::IS_CLOSING : 0)
|
||||||
|
| (line - chunk_start)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++chunk_lines;
|
||||||
|
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 {
|
struct TreeCursor {
|
||||||
|
Language ⟨
|
||||||
|
ParseState *root;
|
||||||
ParseStateLeaf *leaf = nullptr;
|
ParseStateLeaf *leaf = nullptr;
|
||||||
ParseStateBranch *stack[64];
|
ParseStateBranch *stack[64];
|
||||||
uint8_t depth = 0;
|
uint8_t depth = 0;
|
||||||
bool went_left[64];
|
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 next();
|
||||||
void prev();
|
void prev();
|
||||||
|
ParseState *prefix();
|
||||||
|
ParseState *suffix();
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::syntax
|
} // namespace bed::internal::syntax
|
||||||
|
|||||||
@@ -1,57 +1,54 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "decl.h"
|
#include "decl.h"
|
||||||
#include "internal/vase/vase.h"
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
namespace bed::internal::syntax {
|
||||||
struct Parser {
|
struct ParserSnapshot {
|
||||||
static constexpr uint64_t MAX_CHUNK = 512;
|
ParseState *root = nullptr;
|
||||||
|
Language *lang = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
ParseState *root;
|
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
|
||||||
Language lang;
|
ParserSnapshot retain(const ParserSnapshot &snap);
|
||||||
bool in_edit = false;
|
void release(ParserSnapshot &snap);
|
||||||
|
|
||||||
|
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line);
|
||||||
|
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line);
|
||||||
|
|
||||||
|
struct Iterator {
|
||||||
|
ParserSnapshot snap;
|
||||||
|
std::optional<vase::Iterator> it;
|
||||||
|
void *state;
|
||||||
|
uint64_t at;
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
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(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;
|
bool dirty = false;
|
||||||
uint64_t dirty_start = 0;
|
uint64_t dirty_start = 0;
|
||||||
uint64_t dirty_end = 0;
|
uint64_t dirty_end = 0;
|
||||||
|
int64_t edit_delta = 0;
|
||||||
|
|
||||||
Parser(vase::Shard *, uint64_t, Language);
|
explicit Edit(ParserSnapshot &snap) : target(&snap) {}
|
||||||
~Parser();
|
|
||||||
Parser(const Parser &) = delete;
|
|
||||||
Parser &operator=(const Parser &) = delete;
|
|
||||||
|
|
||||||
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);
|
void mark_dirty(uint64_t start, uint64_t end);
|
||||||
|
void insert(uint64_t start, uint64_t count);
|
||||||
uint64_t next_closing(uint64_t line);
|
void erase(uint64_t start, uint64_t count);
|
||||||
uint64_t prev_opening(uint64_t line);
|
void commit(vase::Shard *vase);
|
||||||
|
|
||||||
std::pair<ParseState *, ParseState *> split_tree(ParseState *node, uint64_t line);
|
|
||||||
ParseState *join_tree(ParseState *a, ParseState *b);
|
|
||||||
|
|
||||||
struct Iterator {
|
|
||||||
Parser *p;
|
|
||||||
std::optional<vase::Iterator> it;
|
|
||||||
void *state;
|
|
||||||
uint64_t at;
|
|
||||||
std::vector<Token> tokens;
|
|
||||||
std::vector<ParseEvent> events;
|
|
||||||
Iterator(uint64_t, Parser *, 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);
|
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::syntax
|
} // namespace bed::internal::syntax
|
||||||
|
|||||||
@@ -104,6 +104,6 @@ struct RubyParser {
|
|||||||
|
|
||||||
void ruby_parse(
|
void ruby_parse(
|
||||||
void **v_state, std::string_view line, bool first_line,
|
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
|
} // namespace bed::internal::syntax::ruby
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
namespace bed::internal::theme {
|
namespace bed::internal::theme {
|
||||||
struct Theme {
|
struct Theme {
|
||||||
std::array<Highlight, internal::syntax::Token::Count> hl;
|
std::array<io::Highlight, io::Token::Count> hl;
|
||||||
|
|
||||||
Theme();
|
Theme();
|
||||||
|
|
||||||
Highlight get(internal::syntax::Token token) const;
|
io::Highlight get(const io::Token::Kind &token) const;
|
||||||
|
|
||||||
static Theme default_theme();
|
static Theme default_theme();
|
||||||
static Theme from_name(std::string_view name);
|
static Theme from_name(std::string_view name);
|
||||||
|
|||||||
@@ -4,31 +4,6 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::ui {
|
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 {
|
struct CommandIO {
|
||||||
std::string cmd;
|
std::string cmd;
|
||||||
uint16_t cursor;
|
uint16_t cursor;
|
||||||
@@ -41,6 +16,8 @@ struct CommandIO {
|
|||||||
|
|
||||||
CommandIO(BEd &);
|
CommandIO(BEd &);
|
||||||
std::pair<std::string, bool> run();
|
std::pair<std::string, bool> run();
|
||||||
|
std::pair<std::string, bool> run_pipe();
|
||||||
|
std::pair<std::string, bool> run_terminal();
|
||||||
void redraw();
|
void redraw();
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::ui
|
} // namespace bed::internal::ui
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ struct TextMode {
|
|||||||
|
|
||||||
TextMode(BEd &);
|
TextMode(BEd &);
|
||||||
std::pair<vase::Shard *, bool> run();
|
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 redraw();
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::ui
|
} // namespace bed::internal::ui
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ struct Shard {
|
|||||||
Petal
|
Petal
|
||||||
} kind;
|
} kind;
|
||||||
|
|
||||||
uint8_t height;
|
uint16_t height;
|
||||||
std::atomic_uint32_t refs;
|
std::atomic_uint32_t refs;
|
||||||
|
|
||||||
uint64_t length;
|
uint64_t length;
|
||||||
uint64_t lines;
|
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) {};
|
: kind(kind), height(height), refs(1), length(length), lines(lines) {};
|
||||||
|
|
||||||
static void retain(Shard *n);
|
static void retain(Shard *n);
|
||||||
@@ -62,5 +62,4 @@ struct Petal : Shard {
|
|||||||
source->retain();
|
source->retain();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ std::string to_string(Shard *root, Range range);
|
|||||||
|
|
||||||
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
||||||
Shard *erase(Shard *root, uint64_t start, uint64_t end);
|
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 *join(Shard *root, uint64_t start, uint64_t end);
|
||||||
Shard *copy(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);
|
void write_file(std::filesystem::path path, Shard *text);
|
||||||
|
|||||||
+58
-8
@@ -2,11 +2,8 @@
|
|||||||
#include "internal/parser/parser.h"
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
BEd::BEd(std::vector<std::string> args)
|
||||||
: theme(internal::theme::Theme::default_theme()), io(io) {
|
: theme(internal::theme::Theme::default_theme()), io(*this) {
|
||||||
internal::functions::Function::register_posix(*this);
|
|
||||||
internal::functions::Function::register_extented(*this);
|
|
||||||
internal::functions::Suffix::register_suffixes(*this);
|
|
||||||
std::string prompt_ = "";
|
std::string prompt_ = "";
|
||||||
std::string file = "";
|
std::string file = "";
|
||||||
bool suppress = false;
|
bool suppress = false;
|
||||||
@@ -20,6 +17,9 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
|||||||
suppress = true;
|
suppress = true;
|
||||||
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
||||||
help_mode = true;
|
help_mode = true;
|
||||||
|
} else if (args[i] == "-h" || args[i] == "--help") {
|
||||||
|
print_help();
|
||||||
|
throw fatal_error("", 0);
|
||||||
} else {
|
} else {
|
||||||
if (file.size())
|
if (file.size())
|
||||||
throw fatal_error("Invalid arguments given.", 1);
|
throw fatal_error("Invalid arguments given.", 1);
|
||||||
@@ -31,6 +31,10 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
|||||||
else
|
else
|
||||||
prompt_mode = false;
|
prompt_mode = false;
|
||||||
suppress_mode = suppress;
|
suppress_mode = suppress;
|
||||||
|
internal::functions::Function::register_posix(*this);
|
||||||
|
internal::functions::Function::register_extented(*this);
|
||||||
|
internal::functions::Suffix::register_suffixes(*this);
|
||||||
|
languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby());
|
||||||
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
|
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
|
||||||
current() = {"default", 0};
|
current() = {"default", 0};
|
||||||
try {
|
try {
|
||||||
@@ -47,6 +51,12 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
|||||||
BEd::~BEd() {
|
BEd::~BEd() {
|
||||||
for (auto &[_, buffer] : buffers)
|
for (auto &[_, buffer] : buffers)
|
||||||
delete buffer;
|
delete buffer;
|
||||||
|
for (auto &[_, lang] : languages)
|
||||||
|
delete lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BEd::print_help() {
|
||||||
|
io.write("BEd - A line editor.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void BEd::run() {
|
void BEd::run() {
|
||||||
@@ -56,9 +66,11 @@ void BEd::run() {
|
|||||||
try {
|
try {
|
||||||
handle(cmd, eof);
|
handle(cmd, eof);
|
||||||
} catch (ed_error &e) {
|
} catch (ed_error &e) {
|
||||||
|
io.apply(internal::io::Token::Warning);
|
||||||
io.write_line("?");
|
io.write_line("?");
|
||||||
if (help_mode)
|
if (help_mode)
|
||||||
io.write_line(e.what());
|
io.write_line(e.what());
|
||||||
|
io.reset();
|
||||||
last_help = e.what();
|
last_help = e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,9 +174,47 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
|||||||
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
||||||
if (name.empty())
|
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())
|
auto it = buffers.find(name);
|
||||||
return *it->second;
|
if (it != buffers.end())
|
||||||
|
return *it->second;
|
||||||
|
}
|
||||||
|
constexpr std::string_view prefix = "history/";
|
||||||
|
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);
|
auto *buf = new internal::buffer::GenericBuffer(name);
|
||||||
buffers.emplace(name, buf);
|
buffers.emplace(name, buf);
|
||||||
return *buf;
|
return *buf;
|
||||||
|
|||||||
+259
-182
@@ -3,27 +3,211 @@
|
|||||||
|
|
||||||
namespace bed::internal::buffer {
|
namespace bed::internal::buffer {
|
||||||
GenericBuffer::~GenericBuffer() {
|
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();
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
tm.tm_year + 1900,
|
||||||
|
tm.tm_mon + 1,
|
||||||
|
tm.tm_mday,
|
||||||
|
tm.tm_hour,
|
||||||
|
tm.tm_min,
|
||||||
|
tm.tm_sec,
|
||||||
|
item.summary
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryBuffer *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 HistoryBuffer(name, item.text, item.parse_state);
|
||||||
|
}
|
||||||
|
if (version == current)
|
||||||
|
return new HistoryBuffer(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 HistoryBuffer(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);
|
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() {
|
bool GenericBuffer::waste() {
|
||||||
return save_path.empty()
|
return save_path.empty()
|
||||||
&& root == nullptr;
|
&& root == nullptr
|
||||||
}
|
&& undo_stack.empty();
|
||||||
|
|
||||||
uint64_t GenericBuffer::lines() {
|
|
||||||
if (root)
|
|
||||||
return root->lines + 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t GenericBuffer::bytes() {
|
|
||||||
if (root)
|
|
||||||
return root->length + 1;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||||
|
snapshot("Load file.");
|
||||||
if (lines())
|
if (lines())
|
||||||
ctx.marks.erase(name, 1, lines());
|
ctx.marks.erase(name, 1, lines());
|
||||||
vase::Shard::release(root);
|
vase::Shard::release(root);
|
||||||
@@ -39,7 +223,9 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
|||||||
ctx.prev().start = 1;
|
ctx.prev().start = 1;
|
||||||
ctx.prev().end = text->lines + 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(), ctx.languages["ruby"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericBuffer::set_filename(std::filesystem::path path) {
|
void GenericBuffer::set_filename(std::filesystem::path path) {
|
||||||
@@ -51,35 +237,73 @@ std::filesystem::path GenericBuffer::filename() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
snapshot(std::format("Insert {} lines after line {}", text->lines + 1, line));
|
||||||
ctx.prev().buffername = name;
|
ctx.prev().buffername = name;
|
||||||
ctx.prev().start = line + 1;
|
ctx.prev().start = line + 1;
|
||||||
ctx.prev().end = line + text->lines + 1;
|
ctx.prev().end = line + text->lines + 1;
|
||||||
|
ctx.current() = {name, line + text->lines + 1};
|
||||||
root = vase::insert(&ctx.append, root, text, line);
|
root = vase::insert(&ctx.append, root, text, line);
|
||||||
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
|
ctx.marks.insert(name, line, text->lines + 1);
|
||||||
if (parser)
|
if (parse.lang)
|
||||||
parser->insert(root, ctx.prev().start, ctx.prev().end);
|
syntax::insert(parse, root, line, text->lines + 1);
|
||||||
state = Modified;
|
state = Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
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);
|
root = vase::erase(root, start_line, end_line);
|
||||||
ctx.prev().buffername = name;
|
ctx.prev().buffername = name;
|
||||||
ctx.prev().start = std::min(start_line, lines());
|
uint64_t l = std::min(start_line, lines());
|
||||||
ctx.prev().end = 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);
|
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||||
if (parser)
|
if (parse.lang)
|
||||||
parser->erase(root, start_line, end_line - start_line + 1);
|
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;
|
state = Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
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);
|
root = vase::join(root, start_line, end_line);
|
||||||
ctx.prev().buffername = name;
|
ctx.prev().buffername = name;
|
||||||
ctx.prev().start = start_line;
|
ctx.prev().start = start_line;
|
||||||
ctx.prev().end = start_line;
|
ctx.prev().end = start_line;
|
||||||
|
ctx.current() = {name, start_line};
|
||||||
ctx.marks.collapse(name, start_line, end_line - start_line);
|
ctx.marks.collapse(name, start_line, end_line - start_line);
|
||||||
if (parser)
|
if (parse.lang)
|
||||||
parser->erase(root, start_line, end_line - start_line);
|
syntax::erase(parse, root, start_line, end_line - start_line);
|
||||||
state = Modified;
|
state = Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +311,13 @@ void GenericBuffer::substitute(
|
|||||||
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
std::string ®ex, std::string &replacement, std::string &options
|
std::string ®ex, 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().buffername = name;
|
||||||
ctx.prev().start = start_line;
|
ctx.prev().start = start_line;
|
||||||
ctx.prev().end = end_line;
|
ctx.prev().end = end_line;
|
||||||
if (parser)
|
std::optional<syntax::Edit> edit;
|
||||||
parser->begin_edit();
|
if (parse.lang)
|
||||||
|
edit.emplace(parse);
|
||||||
root = vase::substitute(
|
root = vase::substitute(
|
||||||
&ctx.append,
|
&ctx.append,
|
||||||
root,
|
root,
|
||||||
@@ -103,168 +329,19 @@ void GenericBuffer::substitute(
|
|||||||
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
|
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
|
||||||
if (old_lines) {
|
if (old_lines) {
|
||||||
ctx.marks.erase(name, line, old_lines);
|
ctx.marks.erase(name, line, old_lines);
|
||||||
if (parser)
|
if (edit)
|
||||||
parser->erase(line, old_lines);
|
edit->erase(line, old_lines);
|
||||||
}
|
}
|
||||||
if (new_lines) {
|
if (new_lines) {
|
||||||
ctx.marks.insert(name, line, line + new_lines - 1);
|
ctx.marks.insert(name, line, new_lines);
|
||||||
if (parser)
|
if (edit)
|
||||||
parser->insert(line, line + new_lines - 1);
|
edit->insert(line, new_lines);
|
||||||
}
|
}
|
||||||
|
ctx.current() = {name, line + new_lines};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (parser)
|
if (edit)
|
||||||
parser->end_edit(root);
|
edit->commit(root);
|
||||||
ctx.prev().buffername = name;
|
|
||||||
state = Modified;
|
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
|
} // namespace bed::internal::buffer
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,12 +54,12 @@ std::filesystem::path ClipBuffer::filename() {
|
|||||||
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||||
ctx.prev().buffername = name;
|
ctx.prev().buffername = name;
|
||||||
ctx.prev().start = line + 1;
|
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);
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
s = vase::insert(&ctx.append, s, text, line);
|
s = vase::insert(&ctx.append, s, text, line);
|
||||||
clip_write(s);
|
clip_write(s);
|
||||||
vase::Shard::release(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) {
|
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
@@ -73,6 +73,30 @@ void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
|||||||
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
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);
|
||||||
|
}
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
s = vase::join(s, start_line, end_line);
|
s = vase::join(s, start_line, end_line);
|
||||||
@@ -104,7 +128,7 @@ void ClipBuffer::substitute(
|
|||||||
if (old_lines)
|
if (old_lines)
|
||||||
ctx.marks.erase(name, line, old_lines);
|
ctx.marks.erase(name, line, old_lines);
|
||||||
if (new_lines)
|
if (new_lines)
|
||||||
ctx.marks.insert(name, line, line + new_lines - 1);
|
ctx.marks.insert(name, line, new_lines);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
clip_write(s);
|
clip_write(s);
|
||||||
|
|||||||
@@ -3,18 +3,278 @@
|
|||||||
#include "internal/functions/suffixes.h"
|
#include "internal/functions/suffixes.h"
|
||||||
|
|
||||||
namespace bed::internal::functions {
|
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) {
|
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 ¤t = 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::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Ruby,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Execute some ruby code",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
// TODO: connect up to mruby.
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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 < 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(
|
ctx.functions.insert(
|
||||||
"cd",
|
"cd",
|
||||||
Function{
|
Function{
|
||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::Any,
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Change directory.",
|
.desc = "Change directory",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto path = std::get<std::string>(arg_);
|
||||||
const auto first = path.find_first_not_of(" \t");
|
const auto first = path.find_first_not_of(" \t");
|
||||||
const auto last = path.find_last_not_of(" \t");
|
const auto last = path.find_last_not_of(" \t");
|
||||||
@@ -42,11 +302,17 @@ void Function::register_extented(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Print directory.",
|
.desc = "Print the current working directory",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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];
|
char cwd[PATH_MAX];
|
||||||
if (!getcwd(cwd, sizeof(cwd)))
|
if (!getcwd(cwd, sizeof(cwd)))
|
||||||
throw ed_error("Can't determine current directory.");
|
throw ed_error("Can't determine current directory.");
|
||||||
@@ -54,5 +320,117 @@ 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::functions
|
} // namespace bed::internal::functions
|
||||||
|
|||||||
+278
-85
@@ -34,14 +34,19 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Line,
|
.address_kind = Function::AddressKind::Line,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::Text,
|
.input_mode = Function::InputMode::Text,
|
||||||
.desc = "Append text to a line.",
|
.desc = "Append text to a line",
|
||||||
.default_address = ".",
|
.default_address = ".",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.pre_text_mode = nullptr,
|
||||||
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
auto addr = std::get<buffer::Line>(addr_);
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
ctx.buffer(addr.buffername).append(ctx, text, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,15 +57,19 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::Text,
|
.input_mode = Function::InputMode::Text,
|
||||||
.desc = "Change set of lines.",
|
.desc = "Change a range of lines",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.pre_text_mode = nullptr,
|
||||||
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
auto addr = std::get<buffer::Range>(addr_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).replace(ctx, text, 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);
|
vase::Shard::release(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,14 +80,19 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Delete set of lines.",
|
.desc = "Delete a range of lines",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
||||||
ctx.current() = {addr.buffername, addr.start};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -88,11 +102,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::File,
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
.input_mode = Function::InputMode::None,
|
.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 = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<std::string>(addr_);
|
||||||
auto &buf = ctx.buffer(addr);
|
auto &buf = ctx.buffer(addr);
|
||||||
if (buf.state == buffer::Buffer::Modified) {
|
if (buf.state == buffer::Buffer::Modified) {
|
||||||
@@ -121,8 +141,8 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
vase::Shard::release(s);
|
vase::Shard::release(s);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
ctx.io.write_line(std::format("{}", buf.bytes()));
|
if (!ctx.suppress_mode)
|
||||||
ctx.current() = {addr, buf.lines()};
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -132,11 +152,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::File,
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
.input_mode = Function::InputMode::None,
|
.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 = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<std::string>(addr_);
|
||||||
auto &buf = ctx.buffer(addr);
|
auto &buf = ctx.buffer(addr);
|
||||||
vase::Shard *s = nullptr;
|
vase::Shard *s = nullptr;
|
||||||
@@ -161,8 +187,8 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
vase::Shard::release(s);
|
vase::Shard::release(s);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
ctx.io.write_line(std::format("{}", buf.bytes()));
|
if (!ctx.suppress_mode)
|
||||||
ctx.current() = {addr, buf.lines()};
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -172,16 +198,19 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::File,
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Set a save path.",
|
.desc = "Set/print a save path",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<std::string>(addr_);
|
||||||
auto &buf_ = ctx.buffer(addr);
|
auto &buf = ctx.buffer(addr);
|
||||||
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
|
||||||
return;
|
|
||||||
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
|
||||||
if (std::holds_alternative<std::filesystem::path>(arg))
|
if (std::holds_alternative<std::filesystem::path>(arg))
|
||||||
buf.set_filename(std::get<std::filesystem::path>(arg));
|
buf.set_filename(std::get<std::filesystem::path>(arg));
|
||||||
else if (std::holds_alternative<ShellArg>(arg))
|
else if (std::holds_alternative<ShellArg>(arg))
|
||||||
@@ -202,8 +231,16 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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.write_line(ctx.last_help);
|
||||||
|
ctx.io.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -213,14 +250,22 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Toggle help mode.",
|
.desc = "Toggle help mode",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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.help_mode = !ctx.help_mode;
|
||||||
|
ctx.io.apply(internal::io::Token::Warning);
|
||||||
if (ctx.help_mode)
|
if (ctx.help_mode)
|
||||||
ctx.io.write_line(ctx.last_help);
|
ctx.io.write_line(ctx.last_help);
|
||||||
|
ctx.io.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -230,16 +275,21 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Line,
|
.address_kind = Function::AddressKind::Line,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::Text,
|
.input_mode = Function::InputMode::Text,
|
||||||
.desc = "Insert text before a line.",
|
.desc = "Insert text before a line",
|
||||||
.default_address = ".",
|
.default_address = ".",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.pre_text_mode = nullptr,
|
||||||
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
auto addr = std::get<buffer::Line>(addr_);
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
if (addr.number)
|
if (addr.number)
|
||||||
addr.number--;
|
addr.number--;
|
||||||
ctx.buffer(addr.buffername).append(ctx, text, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,14 +300,19 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Join a set of lines.",
|
.desc = "Join a range of lines",
|
||||||
.default_address = ".,.+1",
|
.default_address = ".,.+1",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
|
||||||
ctx.current() = {addr.buffername, addr.start};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -267,11 +322,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Line,
|
.address_kind = Function::AddressKind::Line,
|
||||||
.argument_kind = Function::ArgumentKind::Mark,
|
.argument_kind = Function::ArgumentKind::Mark,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Mark a line.",
|
.desc = "Mark a line",
|
||||||
.default_address = ".",
|
.default_address = ".",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<buffer::Line>(addr_);
|
||||||
ctx.mark(std::get<char>(arg), addr);
|
ctx.mark(std::get<char>(arg), addr);
|
||||||
}
|
}
|
||||||
@@ -283,11 +344,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "List (print unambiguous) range",
|
.desc = "Print a range, showing nonprinting characters unambiguously",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
|
||||||
ctx.current() = {addr.buffername, addr.end};
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
@@ -300,11 +367,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::Line,
|
.argument_kind = Function::ArgumentKind::Line,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Move a range of lines.",
|
.desc = "Move a range of lines",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 addr = std::get<buffer::Range>(addr_);
|
||||||
auto arg = std::get<buffer::Line>(arg_);
|
auto arg = std::get<buffer::Line>(arg_);
|
||||||
if (arg.buffername == addr.buffername
|
if (arg.buffername == addr.buffername
|
||||||
@@ -329,7 +402,6 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
vase::Shard::release(text);
|
vase::Shard::release(text);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -339,11 +411,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Print range with line numbers",
|
.desc = "Print a range with line numbers",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
|
||||||
ctx.current() = {addr.buffername, addr.end};
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
@@ -356,11 +434,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Print range",
|
.desc = "Print a range",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
|
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
|
||||||
ctx.current() = {addr.buffername, addr.end};
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
@@ -371,17 +455,28 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
"P",
|
"P",
|
||||||
Function{
|
Function{
|
||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Toggle prompt.",
|
.desc = "Toggle/set prompt string",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.pre_text_mode = nullptr,
|
||||||
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
.handle = [](
|
||||||
ctx.prompt_mode = !ctx.prompt_mode;
|
BEd &ctx,
|
||||||
if (ctx.prompt_mode && !ctx.prompt) {
|
const buffer::Address &,
|
||||||
ctx.prompt = [](BEd &) { return "*"; };
|
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 +486,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Try quitting.",
|
.desc = "Quit, warn once if unsaved changes exist",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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;
|
std::string modified_buffers;
|
||||||
for (auto &[name, buffer] : ctx.buffers) {
|
for (auto &[name, buffer] : ctx.buffers) {
|
||||||
if (buffer->state == buffer::Buffer::Modified) {
|
if (buffer->state == buffer::Buffer::Modified) {
|
||||||
@@ -416,11 +517,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Force quit.",
|
.desc = "Quit without saving, discarding unsaved changes",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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);
|
throw fatal_error("Force Quitting", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,11 +538,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Line,
|
.address_kind = Function::AddressKind::Line,
|
||||||
.argument_kind = Function::ArgumentKind::File,
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
.input_mode = Function::InputMode::None,
|
.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 = "$",
|
.default_address = "$",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<buffer::Line>(addr_);
|
||||||
auto &buf = ctx.buffer(addr.buffername);
|
auto &buf = ctx.buffer(addr.buffername);
|
||||||
vase::Shard *s = nullptr;
|
vase::Shard *s = nullptr;
|
||||||
@@ -456,13 +569,13 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
buf.append(ctx, s, addr.number);
|
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);
|
vase::Shard::release(s);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
vase::Shard::release(s);
|
vase::Shard::release(s);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -472,11 +585,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::Regex,
|
.argument_kind = Function::ArgumentKind::Regex,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Substitute regex.",
|
.desc = "Substitute pattern in range for replacement",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<buffer::Range>(addr_);
|
||||||
auto arg = std::get<RegexArg>(arg_);
|
auto arg = std::get<RegexArg>(arg_);
|
||||||
if (arg.expression == "")
|
if (arg.expression == "")
|
||||||
@@ -505,11 +624,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::Line,
|
.argument_kind = Function::ArgumentKind::Line,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Copy a range of lines.",
|
.desc = "Copy a range of lines",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 addr = std::get<buffer::Range>(addr_);
|
||||||
auto arg = std::get<buffer::Line>(arg_);
|
auto arg = std::get<buffer::Line>(arg_);
|
||||||
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
||||||
@@ -517,29 +642,56 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
|
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
|
||||||
vase::Shard::release(text);
|
vase::Shard::release(text);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
if (!addr.start) {
|
|
||||||
vase::Shard::release(text);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
|
|
||||||
vase::Shard::release(text);
|
vase::Shard::release(text);
|
||||||
throw;
|
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(
|
ctx.functions.insert(
|
||||||
"w",
|
"w",
|
||||||
Function{
|
Function{
|
||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::File,
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Write buffer to disk.",
|
.desc = "Write a range of lines to disk",
|
||||||
.default_address = "0,$",
|
.default_address = "0,$",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.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 addr = std::get<buffer::Range>(addr_);
|
||||||
auto &buf = ctx.buffer(addr.buffername);
|
auto &buf = ctx.buffer(addr.buffername);
|
||||||
vase::Shard *text;
|
vase::Shard *text;
|
||||||
@@ -565,12 +717,13 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
throw ed_error("Need filename.");
|
throw ed_error("Need filename.");
|
||||||
vase::write_file(path, text);
|
vase::write_file(path, text);
|
||||||
};
|
};
|
||||||
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
|
|
||||||
vase::Shard::release(text);
|
vase::Shard::release(text);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
vase::Shard::release(text);
|
vase::Shard::release(text);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -580,16 +733,34 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Range,
|
.address_kind = Function::AddressKind::Range,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Print line numbers",
|
.desc = "Print the line number of the given address",
|
||||||
.default_address = "$",
|
.default_address = "$",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto &addr = std::get<buffer::Range>(addr_);
|
||||||
if (addr.start == addr.end)
|
ctx.io.apply(io::Token::BufferName);
|
||||||
ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start));
|
ctx.io.write(":" + addr.buffername + ":");
|
||||||
else
|
ctx.io.apply(io::Token::Number);
|
||||||
ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end));
|
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};
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -600,11 +771,17 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::Shell,
|
.argument_kind = Function::ArgumentKind::Shell,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Run a shell command.",
|
.desc = "Run a shell command",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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 &addr = std::get<std::string>(addr_);
|
||||||
auto filename = ctx.buffer(addr).filename();
|
auto filename = ctx.buffer(addr).filename();
|
||||||
auto &arg = std::get<ShellArg>(arg_);
|
auto &arg = std::get<ShellArg>(arg_);
|
||||||
@@ -612,7 +789,8 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
if (ctx.escape_command(cmd, filename.string()))
|
if (ctx.escape_command(cmd, filename.string()))
|
||||||
ctx.io.write(cmd + "\n");
|
ctx.io.write(cmd + "\n");
|
||||||
ctx.io.run_pty(cmd);
|
ctx.io.run_pty(cmd);
|
||||||
ctx.io.write("!\n");
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write("!\n");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -620,26 +798,41 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.address_kind = Function::AddressKind::Line,
|
.address_kind = Function::AddressKind::Line,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::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",
|
.default_address = ".+1",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
if (addr.number != 0)
|
if (addr.number != 0)
|
||||||
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
|
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
|
||||||
ctx.current() = addr;
|
ctx.current() = addr;
|
||||||
|
ctx.prev().buffername = addr.buffername;
|
||||||
|
ctx.prev().start = addr.number;
|
||||||
|
ctx.prev().end = addr.number;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.eof_op = Function{
|
ctx.eof_op = Function{
|
||||||
.address_kind = Function::AddressKind::None,
|
.address_kind = Function::AddressKind::None,
|
||||||
.argument_kind = Function::ArgumentKind::None,
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
.input_mode = Function::InputMode::None,
|
.input_mode = Function::InputMode::None,
|
||||||
.desc = "Try quitting.",
|
.desc = "Quit, warn once if unsaved changes exist",
|
||||||
.default_address = "",
|
.default_address = "",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.pre_text_mode = nullptr,
|
.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;
|
std::string modified_buffers;
|
||||||
for (auto &[name, buffer] : ctx.buffers) {
|
for (auto &[name, buffer] : ctx.buffers) {
|
||||||
if (buffer->state == buffer::Buffer::Modified) {
|
if (buffer->state == buffer::Buffer::Modified) {
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
#include "internal/io/io.h"
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
namespace bed::internal::io {
|
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) {
|
||||||
|
std::string line = std::move(pipe_input);
|
||||||
|
pipe_input.clear();
|
||||||
|
return {line, true};
|
||||||
|
}
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
throw fatal_error("Can't read stdin.", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
KeyEvent::ReadResult IO::get_next_byte(char &out) {
|
KeyEvent::ReadResult IO::get_next_byte(char &out) {
|
||||||
if (!input_queue.empty()) {
|
if (!input_queue.empty()) {
|
||||||
out = input_queue.front();
|
out = input_queue.front();
|
||||||
|
|||||||
+51
-3
@@ -1,12 +1,17 @@
|
|||||||
#include "internal/io/io.h"
|
#include "internal/io/io.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
namespace bed::internal::io {
|
namespace bed::internal::io {
|
||||||
termios IO::orig_termios{};
|
termios IO::orig_termios{};
|
||||||
termios IO::raw_termios{};
|
termios IO::raw_termios{};
|
||||||
bool IO::cleaned = true;
|
bool IO::cleaned = true;
|
||||||
volatile std::atomic_bool IO::resized(false);
|
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)
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
throw fatal_error("Can't get terminal state.", 1);
|
throw fatal_error("Can't get terminal state.", 1);
|
||||||
struct sigaction sa{};
|
struct sigaction sa{};
|
||||||
@@ -29,17 +34,54 @@ IO::~IO() {
|
|||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IO::apply(const io::Token::Kind &t) {
|
||||||
|
if (bed.suppress_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.suppress_mode)
|
||||||
|
return;
|
||||||
|
write("\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
void IO::enable_mouse() {
|
void IO::enable_mouse() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no mouse in pipe mode.", 1);
|
||||||
const char *seq = "\x1b[?1000h";
|
const char *seq = "\x1b[?1000h";
|
||||||
write_all(STDOUT_FILENO, seq, 8);
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IO::disable_mouse() {
|
void IO::disable_mouse() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no mouse in pipe mode.", 1);
|
||||||
const char *seq = "\x1b[?1000l";
|
const char *seq = "\x1b[?1000l";
|
||||||
write_all(STDOUT_FILENO, seq, 8);
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<uint16_t, uint16_t> IO::terminal_size() {
|
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{};
|
struct winsize ws{};
|
||||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
throw fatal_error("Can't get terminal size.", 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() {
|
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);
|
write_all(STDOUT_FILENO, "\x1b[6n", 4);
|
||||||
std::string response;
|
std::string response;
|
||||||
char c;
|
char c;
|
||||||
@@ -69,7 +113,7 @@ std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IO::enable_raw() {
|
void IO::enable_raw() {
|
||||||
if (!cleaned)
|
if (!cleaned || mode == Mode::PIPE)
|
||||||
return;
|
return;
|
||||||
std::string os = "\x1b[?2004h";
|
std::string os = "\x1b[?2004h";
|
||||||
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
@@ -79,7 +123,7 @@ void IO::enable_raw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IO::cleanup() {
|
void IO::cleanup() {
|
||||||
if (cleaned)
|
if (cleaned || mode == Mode::PIPE)
|
||||||
return;
|
return;
|
||||||
std::string os = "\x1b[?1000l\x1b[?2004l";
|
std::string os = "\x1b[?1000l\x1b[?2004l";
|
||||||
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
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) {
|
void IO::move_cursor(uint16_t row, uint16_t col) {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
return;
|
||||||
char buf[32];
|
char buf[32];
|
||||||
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
|
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
|
||||||
write_all(STDOUT_FILENO, buf, n);
|
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) {
|
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;
|
int master_fd = -1;
|
||||||
struct winsize ws{};
|
struct winsize ws{};
|
||||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
|||||||
@@ -6,34 +6,74 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
addr.base = AddressPromise::None{};
|
addr.base = AddressPromise::None{};
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case '.':
|
case '.':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
break;
|
break;
|
||||||
case '$':
|
case '$':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Last{};
|
addr.base = AddressPromise::Last{};
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::LastRange{};
|
addr.base = AddressPromise::LastRange{};
|
||||||
break;
|
break;
|
||||||
case '[':
|
case '[':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Block{Direction::Backward};
|
addr.base = AddressPromise::Block{Direction::Backward};
|
||||||
break;
|
break;
|
||||||
case ']':
|
case ']':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Block{Direction::Forward};
|
addr.base = AddressPromise::Block{Direction::Forward};
|
||||||
break;
|
break;
|
||||||
case '^':
|
case '^':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
||||||
break;
|
break;
|
||||||
case '~':
|
case '~':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
||||||
break;
|
break;
|
||||||
case '\'':
|
case '\'':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)2,
|
||||||
|
.type = io::Token::Mark}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
if (('a' <= peek() && peek() <= 'z')
|
if (('a' <= peek() && peek() <= 'z')
|
||||||
|| ('A' <= peek() && peek() <= 'Z'))
|
|| ('A' <= peek() && peek() <= 'Z'))
|
||||||
@@ -43,6 +83,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
advance();
|
advance();
|
||||||
break;
|
break;
|
||||||
case '{': {
|
case '{': {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
std::string func;
|
std::string func;
|
||||||
@@ -64,8 +109,24 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
arg = peek_str(j);
|
arg = peek_str(j);
|
||||||
else
|
else
|
||||||
func = peek_str(j);
|
func = peek_str(j);
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)func.size(),
|
||||||
|
.type = io::Token::RubyFunction}
|
||||||
|
);
|
||||||
|
if (arg.size())
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i + (uint64_t)func.size() + 1,
|
||||||
|
.end = i + (uint64_t)func.size() + 1 + (uint64_t)arg.size(),
|
||||||
|
.type = io::Token::RubyFunction}
|
||||||
|
);
|
||||||
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
|
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i - (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
} break;
|
} break;
|
||||||
case '/': {
|
case '/': {
|
||||||
advance();
|
advance();
|
||||||
@@ -87,9 +148,19 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
Direction::Forward,
|
Direction::Forward,
|
||||||
std::string(peek_str(j))
|
std::string(peek_str(j))
|
||||||
);
|
);
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i - 1,
|
||||||
|
.end = (uint64_t)i + j + 1,
|
||||||
|
.type = io::Token::Regexp}
|
||||||
|
);
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
} break;
|
} break;
|
||||||
case '?': {
|
case '?': {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -109,6 +180,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
Direction::Backward,
|
Direction::Backward,
|
||||||
std::string(peek_str(j))
|
std::string(peek_str(j))
|
||||||
);
|
);
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i - 1,
|
||||||
|
.end = (uint64_t)i + j + 1,
|
||||||
|
.type = io::Token::Regexp}
|
||||||
|
);
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
} break;
|
} break;
|
||||||
case '<': {
|
case '<': {
|
||||||
@@ -132,6 +208,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i - 1,
|
||||||
|
.end = (uint64_t)i + j + 1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
} break;
|
} break;
|
||||||
case '>': {
|
case '>': {
|
||||||
@@ -149,9 +230,19 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i - 1,
|
||||||
|
.end = (uint64_t)i + j + 1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
} break;
|
} break;
|
||||||
case '+': {
|
case '+': {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
@@ -162,10 +253,20 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
}
|
}
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i,
|
||||||
|
.end = (uint64_t)i + j,
|
||||||
|
.type = io::Token::Number}
|
||||||
|
);
|
||||||
advance(j);
|
advance(j);
|
||||||
addr.offset += num;
|
addr.offset += num;
|
||||||
} break;
|
} break;
|
||||||
case '-': {
|
case '-': {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
@@ -176,17 +277,28 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
}
|
}
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i,
|
||||||
|
.end = (uint64_t)i + j,
|
||||||
|
.type = io::Token::Number}
|
||||||
|
);
|
||||||
advance(j);
|
advance(j);
|
||||||
addr.offset -= num;
|
addr.offset -= num;
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
if ('0' <= peek() && peek() <= '9') {
|
if ('0' <= peek() && peek() <= '9') {
|
||||||
|
uint16_t start = i;
|
||||||
uint64_t num = 0;
|
uint64_t num = 0;
|
||||||
while ('0' <= peek() && peek() <= '9') {
|
while ('0' <= peek() && peek() <= '9') {
|
||||||
num = num * 10 + (peek() - '0');
|
num = num * 10 + (peek() - '0');
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
addr.base = AddressPromise::Number{num};
|
addr.base = AddressPromise::Number{num};
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)start,
|
||||||
|
.end = (uint64_t)i,
|
||||||
|
.type = io::Token::Number}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,14 +308,25 @@ int64_t Parser::offset() {
|
|||||||
while (peek() == '+' || peek() == '-'
|
while (peek() == '+' || peek() == '-'
|
||||||
|| ('0' <= peek() && peek() <= '9')) {
|
|| ('0' <= peek() && peek() <= '9')) {
|
||||||
bool positive = peek() != '-';
|
bool positive = peek() != '-';
|
||||||
if (peek() == '+' || peek() == '-')
|
if (peek() == '+' || peek() == '-') {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSymbol}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
|
}
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
int64_t num = 0;
|
int64_t num = 0;
|
||||||
while ('0' <= peek(j) && peek(j) <= '9')
|
while ('0' <= peek(j) && peek(j) <= '9')
|
||||||
num = num * 10 + (peek(j++) - '0');
|
num = num * 10 + (peek(j++) - '0');
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = (uint64_t)i,
|
||||||
|
.end = (uint64_t)i + j,
|
||||||
|
.type = io::Token::Number}
|
||||||
|
);
|
||||||
advance(j);
|
advance(j);
|
||||||
offset += positive ? num : -num;
|
offset += positive ? num : -num;
|
||||||
skip_ws();
|
skip_ws();
|
||||||
@@ -214,6 +337,7 @@ int64_t Parser::offset() {
|
|||||||
|
|
||||||
void Parser::address(AddressPromise &addr) {
|
void Parser::address(AddressPromise &addr) {
|
||||||
if (peek() == ':') {
|
if (peek() == ':') {
|
||||||
|
uint16_t start = i;
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (peek(j) != ':' && peek(j) != '\0')
|
while (peek(j) != ':' && peek(j) != '\0')
|
||||||
@@ -222,6 +346,11 @@ void Parser::address(AddressPromise &addr) {
|
|||||||
advance(j);
|
advance(j);
|
||||||
if (peek() == ':')
|
if (peek() == ':')
|
||||||
advance();
|
advance();
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = start,
|
||||||
|
.end = i,
|
||||||
|
.type = io::Token::BufferName}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
skip_ws();
|
skip_ws();
|
||||||
if (peek() == '\0')
|
if (peek() == '\0')
|
||||||
@@ -238,6 +367,11 @@ void Parser::addresses(std::vector<AddressPromise> &addresses) {
|
|||||||
address(*addr);
|
address(*addr);
|
||||||
while (peek() == ',' || peek() == ';') {
|
while (peek() == ',' || peek() == ';') {
|
||||||
addr->jumping = peek() == ';';
|
addr->jumping = peek() == ';';
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::AddressSeperator}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
skip_ws();
|
skip_ws();
|
||||||
addresses.push_back({});
|
addresses.push_back({});
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ void Parser::operation() {
|
|||||||
if (len == 0)
|
if (len == 0)
|
||||||
throw ed_error("Function not found.");
|
throw ed_error("Function not found.");
|
||||||
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + len,
|
||||||
|
.type = io::Token::Function}
|
||||||
|
);
|
||||||
advance(len);
|
advance(len);
|
||||||
command->function = function;
|
command->function = function;
|
||||||
char suffix = '\0';
|
char suffix = '\0';
|
||||||
@@ -27,10 +32,20 @@ void Parser::operation() {
|
|||||||
command->argument = peek();
|
command->argument = peek();
|
||||||
else
|
else
|
||||||
throw ed_error("Valid mark needed.");
|
throw ed_error("Valid mark needed.");
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::Mark}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
break;
|
break;
|
||||||
case functions::Function::ArgumentKind::Any:
|
case functions::Function::ArgumentKind::Any:
|
||||||
command->argument = std::string(peek_str());
|
command->argument = std::string(peek_str());
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + peek_str().size(),
|
||||||
|
.type = io::Token::Any}
|
||||||
|
);
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
break;
|
break;
|
||||||
case functions::Function::ArgumentKind::Global: {
|
case functions::Function::ArgumentKind::Global: {
|
||||||
@@ -108,8 +123,18 @@ void Parser::operation() {
|
|||||||
skip_ws();
|
skip_ws();
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case '!':
|
case '!':
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = (uint64_t)i + 1,
|
||||||
|
.type = io::Token::Error}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + peek_str().size(),
|
||||||
|
.type = io::Token::Shell}
|
||||||
|
);
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
break;
|
break;
|
||||||
case '\0':
|
case '\0':
|
||||||
@@ -117,6 +142,11 @@ void Parser::operation() {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
command->argument = std::filesystem::path(peek_str());
|
command->argument = std::filesystem::path(peek_str());
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + peek_str().size(),
|
||||||
|
.type = io::Token::File}
|
||||||
|
);
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -133,6 +163,7 @@ void Parser::operation() {
|
|||||||
char delim = peek();
|
char delim = peek();
|
||||||
if (delim == '\0')
|
if (delim == '\0')
|
||||||
throw ed_error("regex expected");
|
throw ed_error("regex expected");
|
||||||
|
uint16_t start = i;
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -165,14 +196,25 @@ void Parser::operation() {
|
|||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
std::string replacement(peek_str(j));
|
std::string replacement(peek_str(j));
|
||||||
|
std::string options;
|
||||||
if (peek(j) != '\0') {
|
if (peek(j) != '\0') {
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
} else {
|
} else {
|
||||||
advance(j);
|
advance(j);
|
||||||
|
options = "p";
|
||||||
}
|
}
|
||||||
std::string options;
|
tokens->push_back(
|
||||||
|
{.start = start,
|
||||||
|
.end = i,
|
||||||
|
.type = io::Token::Regexp}
|
||||||
|
);
|
||||||
if (peek() != '\0') {
|
if (peek() != '\0') {
|
||||||
options = std::string(peek_str());
|
options = std::string(peek_str());
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + peek_str().size(),
|
||||||
|
.type = io::Token::Suffix}
|
||||||
|
);
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
}
|
}
|
||||||
std::erase_if(options, [&](char c) {
|
std::erase_if(options, [&](char c) {
|
||||||
@@ -184,17 +226,38 @@ void Parser::operation() {
|
|||||||
});
|
});
|
||||||
command->argument = functions::Function::RegexArg(expression, replacement, options);
|
command->argument = functions::Function::RegexArg(expression, replacement, options);
|
||||||
} break;
|
} break;
|
||||||
case functions::Function::ArgumentKind::Ruby:
|
case functions::Function::ArgumentKind::Ruby: {
|
||||||
command->argument = functions::Function::RubyArg(std::string(peek_str()));
|
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());
|
advance(peek_str().size());
|
||||||
break;
|
} break;
|
||||||
case functions::Function::ArgumentKind::Shell:
|
case functions::Function::ArgumentKind::Shell:
|
||||||
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + peek_str().size(),
|
||||||
|
.type = io::Token::Shell}
|
||||||
|
);
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!suffix) {
|
if (!suffix) {
|
||||||
suffix = peek();
|
suffix = peek();
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = i,
|
||||||
|
.end = i + (uint64_t)1,
|
||||||
|
.type = io::Token::Suffix}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ void Parser::skip_ws() {
|
|||||||
void Parser::parse() {
|
void Parser::parse() {
|
||||||
skip_ws();
|
skip_ws();
|
||||||
if (peek() == '@') {
|
if (peek() == '@') {
|
||||||
|
tokens->push_back(
|
||||||
|
{.start = 0,
|
||||||
|
.end = 1,
|
||||||
|
.type = io::Token::TempCurrent}
|
||||||
|
);
|
||||||
advance();
|
advance();
|
||||||
command->temp_address = true;
|
command->temp_address = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -37,14 +42,26 @@ void Parser::parse() {
|
|||||||
|
|
||||||
Parser::Parser(
|
Parser::Parser(
|
||||||
std::string_view cmd, BEd &bed, Command *command,
|
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) {
|
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
|
||||||
i = 0;
|
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 Parser::get_command(std::string_view cmd, BEd &bed) {
|
||||||
Command c;
|
Command c;
|
||||||
std::vector<ui::Token> tokens;
|
std::vector<io::Token> tokens;
|
||||||
CompletionContext completion;
|
CompletionContext completion;
|
||||||
Parser p(cmd, bed, &c, &tokens, &completion);
|
Parser p(cmd, bed, &c, &tokens, &completion);
|
||||||
p.parse();
|
p.parse();
|
||||||
@@ -53,7 +70,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> Parser::get_addresses(std::string_view cmd, BEd &bed) {
|
||||||
std::vector<AddressPromise> result;
|
std::vector<AddressPromise> result;
|
||||||
std::vector<ui::Token> tokens;
|
std::vector<io::Token> tokens;
|
||||||
CompletionContext completion;
|
CompletionContext completion;
|
||||||
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
||||||
p.addresses(result);
|
p.addresses(result);
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
|
||||||
|
: snap(p) {
|
||||||
|
uint64_t offset;
|
||||||
|
TreeCursor c = TreeCursor(*p.lang, p.root, target, &offset);
|
||||||
|
at = target - offset;
|
||||||
|
state = p.lang->copy(c.leaf->state);
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
it->next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
snap.lang->parse(&state, it->line, at++ == 0, &tokens, &events);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
+68
-312
@@ -1,265 +1,32 @@
|
|||||||
#include "internal/syntax/parser.h"
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
namespace bed::internal::syntax {
|
||||||
static void destroy_tree(ParseState *node, Language &lang) {
|
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang) {
|
||||||
if (!node)
|
ParserSnapshot snap{nullptr, lang};
|
||||||
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;
|
|
||||||
if (lines == 0)
|
if (lines == 0)
|
||||||
return;
|
return snap;
|
||||||
lang = std::move(lang_);
|
if (lang)
|
||||||
std::vector<ParseStateLeaf *> leaves;
|
snap.root = ParseState::splice(*lang, nullptr, vase, 0, 0, lines);
|
||||||
leaves.reserve((lines + MAX_CHUNK - 1) / MAX_CHUNK);
|
return snap;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<ParseState *, ParseState *> Parser::split_tree(ParseState *node, uint64_t line) {
|
ParserSnapshot retain(const ParserSnapshot &snap) {
|
||||||
if (!node)
|
if (snap.root)
|
||||||
return {nullptr, nullptr};
|
ParseState::retain(snap.root);
|
||||||
if (line == 0)
|
return snap;
|
||||||
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};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
|
void release(ParserSnapshot &snap) {
|
||||||
// TODO: balance
|
if (snap.root && snap.lang)
|
||||||
return make_branch(a, b);
|
ParseState::release(*snap.lang, snap.root);
|
||||||
|
snap.root = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
|
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) {
|
||||||
begin_edit();
|
if (!snap.root || !snap.lang)
|
||||||
erase(start, count);
|
return line + 10;
|
||||||
end_edit(vase);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 relative = 0;
|
uint64_t relative = 0;
|
||||||
TreeCursor c(root, line, &relative);
|
TreeCursor c(*snap.lang, snap.root, line, &relative);
|
||||||
uint64_t line_offset = line - relative;
|
uint64_t line_offset = line - relative;
|
||||||
int level = 0;
|
int level = 0;
|
||||||
bool first_leaf = true;
|
bool first_leaf = true;
|
||||||
@@ -283,14 +50,14 @@ uint64_t Parser::next_closing(uint64_t line) {
|
|||||||
c.next();
|
c.next();
|
||||||
first_leaf = false;
|
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) {
|
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line) {
|
||||||
if (!root)
|
if (!snap.root || !snap.lang)
|
||||||
return 0;
|
return 0;
|
||||||
uint64_t relative = 0;
|
uint64_t relative = 0;
|
||||||
TreeCursor c(root, line, &relative);
|
TreeCursor c(*snap.lang, snap.root, line, &relative);
|
||||||
uint64_t line_offset = line - relative;
|
uint64_t line_offset = line - relative;
|
||||||
int level = 0;
|
int level = 0;
|
||||||
bool first_leaf = true;
|
bool first_leaf = true;
|
||||||
@@ -315,77 +82,66 @@ uint64_t Parser::prev_opening(uint64_t line) {
|
|||||||
if (c.leaf)
|
if (c.leaf)
|
||||||
line_offset -= c.leaf->lines();
|
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) {
|
std::optional<Iterator> get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target) {
|
||||||
if (!root)
|
if (!snap.root || !snap.lang)
|
||||||
return std::nullopt;
|
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) {
|
void Edit::mark_dirty(uint64_t start, uint64_t end) {
|
||||||
uint64_t offset;
|
if (!dirty) {
|
||||||
TreeCursor c = TreeCursor(p->root, target, &offset);
|
dirty_start = start;
|
||||||
at = target - offset;
|
dirty_end = end;
|
||||||
if (c.leaf->state) {
|
dirty = true;
|
||||||
state = p->lang.copy(c.leaf->state);
|
|
||||||
} else {
|
} else {
|
||||||
while (!c.leaf->state) {
|
dirty_start = std::min(dirty_start, start);
|
||||||
c.prev();
|
dirty_end = std::max(dirty_end, end);
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Iterator::~Iterator() {
|
void Edit::insert(uint64_t start, uint64_t count) {
|
||||||
if (state)
|
if (count == 0)
|
||||||
p->lang.destroy(state);
|
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)
|
void Edit::erase(uint64_t start, uint64_t count) {
|
||||||
: p(other.p),
|
if (count == 0 || !target->root)
|
||||||
it(std::move(other.it)),
|
return;
|
||||||
state(other.state),
|
uint64_t orig_start = (uint64_t)((int64_t)start - edit_delta);
|
||||||
tokens(std::move(other.tokens)) {
|
uint64_t orig_end = orig_start + count;
|
||||||
other.state = nullptr;
|
mark_dirty(orig_start, orig_end);
|
||||||
|
edit_delta -= (int64_t)count;
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Iterator &Parser::Iterator::operator=(Iterator &&other) {
|
void Edit::commit(vase::Shard *vase) {
|
||||||
if (this == &other)
|
if (!dirty || !target->lang)
|
||||||
return *this;
|
return;
|
||||||
if (state)
|
uint64_t line = dirty_start;
|
||||||
p->lang.destroy(state);
|
uint64_t original = dirty_end - dirty_start;
|
||||||
p = other.p;
|
uint64_t final = (uint64_t)((int64_t)original + edit_delta);
|
||||||
it = std::move(other.it);
|
ParseState *new_root =
|
||||||
state = other.state;
|
ParseState::splice(*target->lang, target->root, vase, line, original, final);
|
||||||
tokens = std::move(other.tokens);
|
release(*target);
|
||||||
other.state = nullptr;
|
target->root = new_root;
|
||||||
return *this;
|
dirty = false;
|
||||||
|
edit_delta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::Iterator::next() {
|
void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
it->next();
|
Edit e(snap);
|
||||||
tokens.clear();
|
e.insert(start, count);
|
||||||
events.clear();
|
e.commit(vase);
|
||||||
p->lang.parse(&state, it->line, at++ == 0, &tokens, &events);
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace bed::internal::syntax
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#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);
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
+105
-103
@@ -34,12 +34,12 @@ inline uint8_t utf8_codepoint_width(unsigned char c) {
|
|||||||
return 1;
|
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) {
|
||||||
if (p.peek() == '\\') {
|
if (p.peek() == '\\') {
|
||||||
if (string)
|
if (string)
|
||||||
tokens->push_back({start, p.i, Token::String});
|
tokens->push_back({start, p.i, io::Token::String});
|
||||||
else
|
else
|
||||||
tokens->push_back({start, p.i, Token::Regexp});
|
tokens->push_back({start, p.i, io::Token::Regexp});
|
||||||
start = p.i;
|
start = p.i;
|
||||||
p.advance();
|
p.advance();
|
||||||
if (p.peek() == 'x') {
|
if (p.peek() == 'x') {
|
||||||
@@ -95,14 +95,14 @@ bool handle_escapes(RubyParser &p, std::vector<Token> *tokens, uint32_t &start,
|
|||||||
} else {
|
} else {
|
||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Escape});
|
tokens->push_back({start, p.i, io::Token::Escape});
|
||||||
start = p.i;
|
start = p.i;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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();
|
uint8_t *heredocs = p.state->heredocs();
|
||||||
uint32_t start = p.i;
|
uint32_t start = p.i;
|
||||||
if (start == 0) {
|
if (start == 0) {
|
||||||
@@ -114,21 +114,22 @@ bool handle_heredoc(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
&& memcmp(p.line.data() + start, heredocs + 1, heredoc_len) == 0) {
|
&& memcmp(p.line.data() + start, heredocs + 1, heredoc_len) == 0) {
|
||||||
if (!p.dequeue_doc(heredoc_len))
|
if (!p.dequeue_doc(heredoc_len))
|
||||||
p.current().state = RubyState::RubyInternalState::NONE;
|
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;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(heredocs[0] & RubyState::Heredocs::ALLOW_INTERPOLATION)) {
|
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;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
while (p.i < p.len()) {
|
while (p.i < p.len()) {
|
||||||
if (handle_escapes(p, tokens, start))
|
if (handle_escapes(p, tokens, start))
|
||||||
continue;
|
continue;
|
||||||
if (p.peek_str(2) == "#{") {
|
if (p.peek_str(2) == "#{") {
|
||||||
tokens->push_back({start, p.i, Token::String});
|
tokens->push_back({start, p.i, io::Token::String});
|
||||||
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
|
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
|
||||||
p.advance(2);
|
p.advance(2);
|
||||||
p.push_state();
|
p.push_state();
|
||||||
return false;
|
return false;
|
||||||
@@ -136,20 +137,20 @@ bool handle_heredoc(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
if (p.i >= p.len())
|
if (p.i >= p.len())
|
||||||
tokens->push_back({start, p.len(), Token::String});
|
tokens->push_back({start, p.len(), io::Token::String});
|
||||||
return true;
|
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;
|
uint32_t start = p.i;
|
||||||
while (p.i < p.len()) {
|
while (p.i < p.len()) {
|
||||||
if (handle_escapes(p, tokens, start))
|
if (handle_escapes(p, tokens, start))
|
||||||
continue;
|
continue;
|
||||||
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
|
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
|
||||||
&& p.peek_str(2) == "#{") {
|
&& p.peek_str(2) == "#{") {
|
||||||
tokens->push_back({start, p.i, Token::String});
|
tokens->push_back({start, p.i, io::Token::String});
|
||||||
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
|
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
|
||||||
p.advance(2);
|
p.advance(2);
|
||||||
p.push_state();
|
p.push_state();
|
||||||
return;
|
return;
|
||||||
@@ -160,7 +161,7 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
if (p.peek() == p.current().delim_end) {
|
if (p.peek() == p.current().delim_end) {
|
||||||
if (p.current().delim_start == p.current().delim_end) {
|
if (p.current().delim_start == p.current().delim_end) {
|
||||||
p.advance();
|
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().state = RubyState::RubyInternalState::NONE;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return;
|
return;
|
||||||
@@ -168,7 +169,7 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
p.current().lit_brace_level--;
|
p.current().lit_brace_level--;
|
||||||
if (p.current().lit_brace_level == 0) {
|
if (p.current().lit_brace_level == 0) {
|
||||||
p.advance();
|
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().state = RubyState::RubyInternalState::NONE;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return;
|
return;
|
||||||
@@ -178,18 +179,18 @@ void handle_string(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
if (p.i >= p.len())
|
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;
|
uint32_t start = p.i;
|
||||||
while (p.i < p.len()) {
|
while (p.i < p.len()) {
|
||||||
if (handle_escapes(p, tokens, start, false))
|
if (handle_escapes(p, tokens, start, false))
|
||||||
continue;
|
continue;
|
||||||
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
|
if ((p.current().flags & RubyState::RubyInternalState::ALLOW_INTERPOLATION)
|
||||||
&& p.peek_str(2) == "#{") {
|
&& p.peek_str(2) == "#{") {
|
||||||
tokens->push_back({start, p.i, Token::Regexp});
|
tokens->push_back({start, p.i, io::Token::Regexp});
|
||||||
tokens->push_back({p.i, p.i + 2, Token::Interpolation});
|
tokens->push_back({p.i, p.i + 2, io::Token::Interpolation});
|
||||||
p.advance(2);
|
p.advance(2);
|
||||||
p.push_state();
|
p.push_state();
|
||||||
return;
|
return;
|
||||||
@@ -200,7 +201,7 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
if (p.peek() == p.current().delim_end) {
|
if (p.peek() == p.current().delim_end) {
|
||||||
if (p.current().delim_start == p.current().delim_end) {
|
if (p.current().delim_start == p.current().delim_end) {
|
||||||
p.advance();
|
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().state = RubyState::RubyInternalState::NONE;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return;
|
return;
|
||||||
@@ -208,7 +209,7 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
p.current().lit_brace_level--;
|
p.current().lit_brace_level--;
|
||||||
if (p.current().lit_brace_level == 0) {
|
if (p.current().lit_brace_level == 0) {
|
||||||
p.advance();
|
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().state = RubyState::RubyInternalState::NONE;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return;
|
return;
|
||||||
@@ -218,15 +219,15 @@ void handle_regex(RubyParser &p, std::vector<Token> *tokens) {
|
|||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
if (p.i >= p.len())
|
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") {
|
if (p.len() == 6 && p.peek_str(6) == "=begin") {
|
||||||
p.current().state = RubyState::RubyInternalState::COMMENT;
|
p.current().state = RubyState::RubyInternalState::COMMENT;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
events->push_back(false);
|
events->push_back(false);
|
||||||
tokens->push_back({0, p.len(), Token::Comment});
|
tokens->push_back({0, p.len(), io::Token::Comment});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (p.len() == 7 && p.peek_str(7) == "__END__") {
|
if (p.len() == 7 && p.peek_str(7) == "__END__") {
|
||||||
@@ -237,20 +238,20 @@ bool handle_line_markers(RubyParser &p, std::vector<Token> *tokens, std::vector<
|
|||||||
return false;
|
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() == '#') {
|
if (p.peek() == '#') {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
if (first_line && p.i == 0 && p.peek(1) == '!') {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
tokens->push_back({p.i, p.len(), Token::Comment});
|
tokens->push_back({p.i, p.len(), io::Token::Comment});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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();
|
static const RubyTries tries = RubyTries();
|
||||||
if (p.peek() == ' ' || p.peek() == '\t') {
|
if (p.peek() == ' ' || p.peek() == '\t') {
|
||||||
while (p.peek() == ' ' || p.peek() == '\t')
|
while (p.peek() == ' ' || p.peek() == '\t')
|
||||||
@@ -276,7 +277,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
j++;
|
j++;
|
||||||
while (identifier_char(p.peek(j)))
|
while (identifier_char(p.peek(j)))
|
||||||
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);
|
p.advance(j);
|
||||||
while (p.peek() == ' ' || p.peek() == '\t')
|
while (p.peek() == ' ' || p.peek() == '\t')
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -298,11 +299,11 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
if (p.peek(j) == '!' || p.peek(j) == '?')
|
if (p.peek(j) == '!' || p.peek(j) == '?')
|
||||||
j++;
|
j++;
|
||||||
if ('A' <= p.peek() && p.peek() <= 'Z')
|
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")
|
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
|
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);
|
p.advance(j);
|
||||||
while (p.peek() == ' ' || p.peek() == '\t')
|
while (p.peek() == ' ' || p.peek() == '\t')
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -321,7 +322,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
j++;
|
j++;
|
||||||
while (identifier_char(p.peek(j)))
|
while (identifier_char(p.peek(j)))
|
||||||
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);
|
p.advance(j);
|
||||||
while (p.peek() == ' ' || p.peek() == '\t')
|
while (p.peek() == ' ' || p.peek() == '\t')
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -343,7 +344,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
indented = true;
|
indented = true;
|
||||||
if (p.peek(j) == '~' || p.peek(j) == '-')
|
if (p.peek(j) == '~' || p.peek(j) == '-')
|
||||||
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())
|
if (p.i + j >= p.len())
|
||||||
return false;
|
return false;
|
||||||
std::string delim;
|
std::string delim;
|
||||||
@@ -366,7 +367,8 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
if (!delim.empty()) {
|
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();
|
uint8_t header = delim.size();
|
||||||
if (interpolation)
|
if (interpolation)
|
||||||
header |= RubyState::Heredocs::ALLOW_INTERPOLATION;
|
header |= RubyState::Heredocs::ALLOW_INTERPOLATION;
|
||||||
@@ -380,7 +382,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p.peek() == '/' && p.current().flags & RubyState::RubyInternalState::EXPECTING_EXPRESSION) {
|
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().state = RubyState::RubyInternalState::REGEXP;
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
|
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
|
||||||
@@ -399,7 +401,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
if (p.peek() == '.')
|
if (p.peek() == '.')
|
||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Operator});
|
tokens->push_back({start, p.i, io::Token::Operator});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case ':': {
|
case ':': {
|
||||||
@@ -407,17 +409,17 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
uint32_t start = p.i;
|
uint32_t start = p.i;
|
||||||
p.advance();
|
p.advance();
|
||||||
if (p.i >= p.len()) {
|
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;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p.peek() == ':') {
|
if (p.peek() == ':') {
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Operator});
|
tokens->push_back({start, p.i, io::Token::Operator});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p.peek() == '\'' || p.peek() == '"') {
|
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;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -428,12 +430,12 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.advance();
|
p.advance();
|
||||||
while (identifier_char(p.peek()))
|
while (identifier_char(p.peek()))
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Label});
|
tokens->push_back({start, p.i, io::Token::Label});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t op_len = tries.operator_trie.longest_match(p.peek_str(p.len() - p.i));
|
uint32_t op_len = tries.operator_trie.longest_match(p.peek_str(p.len() - p.i));
|
||||||
if (op_len > 0) {
|
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);
|
p.advance(op_len);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -443,10 +445,10 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.advance();
|
p.advance();
|
||||||
if (p.peek() == '!' || p.peek() == '?')
|
if (p.peek() == '!' || p.peek() == '?')
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Label});
|
tokens->push_back({start, p.i, io::Token::Label});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Operator});
|
tokens->push_back({start, p.i, io::Token::Operator});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '@': {
|
case '@': {
|
||||||
@@ -463,7 +465,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
while (identifier_char(p.peek()))
|
while (identifier_char(p.peek()))
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::VariableInstance});
|
tokens->push_back({start, p.i, io::Token::VariableInstance});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '$': {
|
case '$': {
|
||||||
@@ -510,7 +512,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::VariableGlobal});
|
tokens->push_back({start, p.i, io::Token::VariableGlobal});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '?': {
|
case '?': {
|
||||||
@@ -526,7 +528,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.advance();
|
p.advance();
|
||||||
if (is_hex(p.peek()))
|
if (is_hex(p.peek()))
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else if (p.peek() == 'u') {
|
} else if (p.peek() == 'u') {
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -546,7 +548,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
if (is_hex(p.peek()))
|
if (is_hex(p.peek()))
|
||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else if ('0' <= p.peek() && p.peek() <= '7') {
|
} else if ('0' <= p.peek() && p.peek() <= '7') {
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -554,7 +556,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.advance();
|
p.advance();
|
||||||
if ('0' <= p.peek() && p.peek() <= '7')
|
if ('0' <= p.peek() && p.peek() <= '7')
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else if (p.peek() == 'c') {
|
} else if (p.peek() == 'c') {
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -562,7 +564,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.advance();
|
p.advance();
|
||||||
else
|
else
|
||||||
goto combination;
|
goto combination;
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else if (p.peek() == 'M' || p.peek() == 'C') {
|
} else if (p.peek() == 'M' || p.peek() == 'C') {
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -573,7 +575,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
else
|
else
|
||||||
goto combination;
|
goto combination;
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else if (p.peek() == 'N') {
|
} else if (p.peek() == 'N') {
|
||||||
p.advance();
|
p.advance();
|
||||||
@@ -584,28 +586,28 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
if (p.peek() == '}')
|
if (p.peek() == '}')
|
||||||
p.advance();
|
p.advance();
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (p.peek() != '\0' && p.peek() != ' ' && p.peek() != '\t') {
|
} else if (p.peek() != '\0' && p.peek() != ' ' && p.peek() != '\t') {
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Char});
|
tokens->push_back({start, p.i, io::Token::Char});
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case '{': {
|
case '{': {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
p.current().brace_level++;
|
p.current().brace_level++;
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
@@ -614,11 +616,11 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
if (!--p.current().brace_level && p.state->top > 1) {
|
if (!--p.current().brace_level && p.state->top > 1) {
|
||||||
p.pop_state();
|
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 {
|
} else {
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
}
|
}
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
@@ -626,8 +628,8 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
case '(': {
|
case '(': {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
p.current().brace_level++;
|
p.current().brace_level++;
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
@@ -636,16 +638,16 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
p.current().brace_level--;
|
p.current().brace_level--;
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '[': {
|
case '[': {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
p.current().brace_level++;
|
p.current().brace_level++;
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
@@ -654,14 +656,14 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
p.current().brace_level--;
|
p.current().brace_level--;
|
||||||
uint8_t brace_color =
|
uint8_t brace_color =
|
||||||
(uint8_t)Token::Brace1 + (p.current().brace_level % 5);
|
(uint8_t)io::Token::Brace1 + (p.current().brace_level % 5);
|
||||||
tokens->push_back({p.i, p.i + 1, (Token::Kind)brace_color});
|
tokens->push_back({p.i, p.i + 1, (io::Token::Kind)brace_color});
|
||||||
p.advance();
|
p.advance();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '\'': {
|
case '\'': {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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().state = RubyState::RubyInternalState::STRING;
|
||||||
p.current().delim_start = '\'';
|
p.current().delim_start = '\'';
|
||||||
p.current().delim_end = '\'';
|
p.current().delim_end = '\'';
|
||||||
@@ -671,7 +673,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
}
|
}
|
||||||
case '"': {
|
case '"': {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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().state = RubyState::RubyInternalState::STRING;
|
||||||
p.current().delim_start = '"';
|
p.current().delim_start = '"';
|
||||||
p.current().delim_end = '"';
|
p.current().delim_end = '"';
|
||||||
@@ -681,7 +683,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
}
|
}
|
||||||
case '`': {
|
case '`': {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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().state = RubyState::RubyInternalState::STRING;
|
||||||
p.current().delim_start = '`';
|
p.current().delim_start = '`';
|
||||||
p.current().delim_end = '`';
|
p.current().delim_end = '`';
|
||||||
@@ -691,7 +693,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
}
|
}
|
||||||
case '%': {
|
case '%': {
|
||||||
if (p.i + 1 >= p.len()) {
|
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.advance();
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return false;
|
return false;
|
||||||
@@ -729,14 +731,14 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (p.i + prefix_len >= p.len()) {
|
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.advance(prefix_len);
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delim_start = p.peek(prefix_len);
|
delim_start = p.peek(prefix_len);
|
||||||
if (identifier_char(delim_start) || delim_start == ' ') {
|
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.advance(prefix_len);
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
return false;
|
return false;
|
||||||
@@ -758,7 +760,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
delim_end = delim_start;
|
delim_end = delim_start;
|
||||||
break;
|
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
|
p.current().state = is_regexp
|
||||||
? RubyState::RubyInternalState::REGEXP
|
? RubyState::RubyInternalState::REGEXP
|
||||||
: RubyState::RubyInternalState::STRING;
|
: RubyState::RubyInternalState::STRING;
|
||||||
@@ -860,7 +862,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;
|
return false;
|
||||||
} else if (identifier_start_char(p.peek())) {
|
} else if (identifier_start_char(p.peek())) {
|
||||||
uint32_t j = 1;
|
uint32_t j = 1;
|
||||||
@@ -870,93 +872,93 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
j++;
|
j++;
|
||||||
if (j == tries.base_keywords_trie.longest_match(p.peek_str(j))) {
|
if (j == tries.base_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.expecting_keywords_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.expecting_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.operator_keywords_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.operator_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.expecting_operators_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.expecting_operators_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.types_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.types_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.methods_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.methods_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.expecting_end_keywords_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.expecting_end_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
events->push_back(false);
|
events->push_back(false);
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.conditional_keywords_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.conditional_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
if (p.current().flags & RubyState::RubyInternalState::NEWLINE || p.op_last)
|
if (p.current().flags & RubyState::RubyInternalState::NEWLINE || p.op_last)
|
||||||
events->push_back(false);
|
events->push_back(false);
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.end_keywords_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.end_keywords_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
events->push_back(false);
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.builtins_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.builtins_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if (j == tries.errors_trie.longest_match(p.peek_str(j))) {
|
} else if (j == tries.errors_trie.longest_match(p.peek_str(j))) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else if ('A' <= p.peek() && p.peek() <= 'Z' && !(p.peek(j) == '!' || p.peek(j) == '?')) {
|
} else if ('A' <= p.peek() && p.peek() <= 'Z' && !(p.peek(j) == '!' || p.peek(j) == '?')) {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
if (j >= 5 && p.peek_str(j).substr(j - 5) == "Error") {
|
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);
|
p.advance(j);
|
||||||
return false;
|
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);
|
p.advance(j);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
if (j == 4 && p.peek_str(4) == "true") {
|
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);
|
p.advance(4);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 5 && p.peek_str(5) == "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);
|
p.advance(5);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 3 && p.peek_str(3) == "end") {
|
if (j == 3 && p.peek_str(3) == "end") {
|
||||||
events->push_back(true);
|
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);
|
p.advance(3);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 5 && p.peek_str(5) == "class") {
|
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.advance(5);
|
||||||
p.current().flags =
|
p.current().flags =
|
||||||
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
||||||
@@ -964,7 +966,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 6 && p.peek_str(6) == "module") {
|
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.advance(6);
|
||||||
p.current().flags =
|
p.current().flags =
|
||||||
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
||||||
@@ -972,7 +974,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (j == 3 && p.peek_str(3) == "def") {
|
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.advance(3);
|
||||||
p.current().flags =
|
p.current().flags =
|
||||||
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
(p.current().flags & ~RubyState::RubyInternalState::NAME_MASK)
|
||||||
@@ -983,17 +985,17 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
if (p.peek(j) == ':') {
|
if (p.peek(j) == ':') {
|
||||||
p.advance(j);
|
p.advance(j);
|
||||||
p.advance();
|
p.advance();
|
||||||
tokens->push_back({start, p.i, Token::Label});
|
tokens->push_back({start, p.i, io::Token::Label});
|
||||||
return false;
|
return false;
|
||||||
} else if (p.peek(j - 1) == '!' || p.peek(j - 1) == '?') {
|
} else if (p.peek(j - 1) == '!' || p.peek(j - 1) == '?') {
|
||||||
p.advance(j);
|
p.advance(j);
|
||||||
tokens->push_back({start, p.i, Token::Function});
|
tokens->push_back({start, p.i, io::Token::Function});
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
p.advance(j);
|
p.advance(j);
|
||||||
j = 0;
|
j = 0;
|
||||||
if (p.peek(j) == '(' || p.peek(j) == '{') {
|
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;
|
return false;
|
||||||
} else if (p.peek(j) == ' ' || p.peek(j) == '\t') {
|
} else if (p.peek(j) == ' ' || p.peek(j) == '\t') {
|
||||||
j++;
|
j++;
|
||||||
@@ -1031,14 +1033,14 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
tokens->push_back({start, p.i, Token::Function});
|
tokens->push_back({start, p.i, io::Token::Function});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint32_t op_len;
|
uint32_t op_len;
|
||||||
if ((op_len = tries.operator_trie.longest_match(p.peek_str(p.len() - p.i)))) {
|
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.advance(op_len);
|
||||||
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
|
||||||
p.set_op_last = true;
|
p.set_op_last = true;
|
||||||
@@ -1054,7 +1056,7 @@ void ruby_parse(
|
|||||||
void **v_state,
|
void **v_state,
|
||||||
std::string_view line,
|
std::string_view line,
|
||||||
bool fl,
|
bool fl,
|
||||||
std::vector<Token> *tokens,
|
std::vector<io::Token> *tokens,
|
||||||
std::vector<ParseEvent> *events
|
std::vector<ParseEvent> *events
|
||||||
) {
|
) {
|
||||||
RubyParser p(v_state, line);
|
RubyParser p(v_state, line);
|
||||||
@@ -1064,7 +1066,7 @@ void ruby_parse(
|
|||||||
if (p.current().state == RubyState::RubyInternalState::END)
|
if (p.current().state == RubyState::RubyInternalState::END)
|
||||||
return;
|
return;
|
||||||
if (p.current().state == RubyState::RubyInternalState::COMMENT) {
|
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") {
|
if (p.i == 0 && p.peek_str(4) == "=end") {
|
||||||
p.current().state = RubyState::RubyInternalState::NONE;
|
p.current().state = RubyState::RubyInternalState::NONE;
|
||||||
events->push_back(true);
|
events->push_back(true);
|
||||||
@@ -1073,7 +1075,7 @@ void ruby_parse(
|
|||||||
}
|
}
|
||||||
if (!p.heredoc_start_line
|
if (!p.heredoc_start_line
|
||||||
&& p.current().state == RubyState::RubyInternalState::HEREDOC) {
|
&& p.current().state == RubyState::RubyInternalState::HEREDOC) {
|
||||||
if (handle_heredoc(p, tokens))
|
if (handle_heredoc(p, tokens, events))
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
#include "internal/syntax/decl.h"
|
#include "internal/syntax/decl.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
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;
|
ParseState *node = root;
|
||||||
while (node->is_branch()) {
|
while (node->is_branch()) {
|
||||||
auto *branch = (ParseStateBranch *)node;
|
auto *branch = (ParseStateBranch *)node;
|
||||||
@@ -63,4 +75,42 @@ void TreeCursor::prev() {
|
|||||||
}
|
}
|
||||||
leaf = nullptr;
|
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
|
} // namespace bed::internal::syntax
|
||||||
|
|||||||
+201
-90
@@ -5,150 +5,262 @@ Theme::Theme() {
|
|||||||
hl.fill({
|
hl.fill({
|
||||||
.fg = 0xF0F0F0,
|
.fg = 0xF0F0F0,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Highlight Theme::get(internal::syntax::Token token) const {
|
io::Highlight Theme::get(const io::Token::Kind &token) const {
|
||||||
return hl[token.type];
|
return hl[token];
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme Theme::default_theme() {
|
Theme Theme::default_theme() {
|
||||||
Theme theme;
|
Theme theme;
|
||||||
theme.hl[internal::syntax::Token::Shebang] = {
|
|
||||||
.fg = 0x7DCFFF,
|
theme.hl[io::Token::TempCurrent] = {
|
||||||
|
.fg = 0x7AA2F7,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Error] = {
|
theme.hl[io::Token::BufferName] = {
|
||||||
.fg = 0xEF5168,
|
.fg = 0xBB9AF7,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Comment] = {
|
theme.hl[io::Token::AddressSeperator] = {
|
||||||
.fg = 0xAAAAAA,
|
.fg = 0xA9B1D6,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::Italic,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::String] = {
|
theme.hl[io::Token::Address] = {
|
||||||
.fg = 0xAAD94C,
|
.fg = 0xE0AF68,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Escape] = {
|
theme.hl[io::Token::Offset] = {
|
||||||
.fg = 0x7DCFFF,
|
.fg = 0xFFAF70,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Interpolation] = {
|
theme.hl[io::Token::AddressRegex] = {
|
||||||
.fg = 0x7DCFFF,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Regexp] = {
|
|
||||||
.fg = 0xD2A6FF,
|
.fg = 0xD2A6FF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Number] = {
|
theme.hl[io::Token::AddressSymbol] = {
|
||||||
.fg = 0xE6C08A,
|
.fg = 0xFF9E64,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::True] = {
|
theme.hl[io::Token::Mark] = {
|
||||||
.fg = 0x7AE93C,
|
.fg = 0xFF757F,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::False] = {
|
theme.hl[io::Token::RubyFunction] = {
|
||||||
.fg = 0xEF5168,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Char] = {
|
|
||||||
.fg = 0xFFAF70,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Keyword] = {
|
|
||||||
.fg = 0xFF8F40,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::KeywordOperator] = {
|
|
||||||
.fg = 0xF07178,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Operator] = {
|
|
||||||
.fg = 0xFFFFFF,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::Italic,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Function] = {
|
|
||||||
.fg = 0xFFAF70,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Type] = {
|
|
||||||
.fg = 0xF07178,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[internal::syntax::Token::Constant] = {
|
|
||||||
.fg = 0x7DCFFF,
|
.fg = 0x7DCFFF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::VariableInstance] = {
|
theme.hl[io::Token::RubyArg] = {
|
||||||
|
.fg = 0xA9B1D6,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Any] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Shell] = {
|
||||||
|
.fg = 0x89DDFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Ruby] = {
|
||||||
.fg = 0x95E6CB,
|
.fg = 0x95E6CB,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::VariableGlobal] = {
|
theme.hl[io::Token::File] = {
|
||||||
|
.fg = 0xAAD94C,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Replacement] = {
|
||||||
|
.fg = 0x9ECE6A,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Suffix] = {
|
||||||
.fg = 0xF07178,
|
.fg = 0xF07178,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Annotation] = {
|
theme.hl[io::Token::Color1] = {
|
||||||
|
.fg = 0x7AA2F7,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color2] = {
|
||||||
|
.fg = 0xAAD94C,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
// Data is terminal default.
|
||||||
|
theme.hl[io::Token::Shebang] = {
|
||||||
.fg = 0x7DCFFF,
|
.fg = 0x7DCFFF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Directive] = {
|
theme.hl[io::Token::Error] = {
|
||||||
|
.fg = 0xEF5168,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
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 = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Keyword] = {
|
||||||
.fg = 0xFF8F40,
|
.fg = 0xFF8F40,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Label] = {
|
theme.hl[io::Token::KeywordOperator] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Operator] = {
|
||||||
|
.fg = 0xFFFFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::Italic,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Function] = {
|
||||||
|
.fg = 0xFFAF70,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Type] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Constant] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::VariableInstance] = {
|
||||||
|
.fg = 0x95E6CB,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::VariableGlobal] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Annotation] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Directive] = {
|
||||||
|
.fg = 0xFF8F40,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Label] = {
|
||||||
.fg = 0xD2A6FF,
|
.fg = 0xD2A6FF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Brace1] = {
|
theme.hl[io::Token::Brace1] = {
|
||||||
.fg = 0xD2A6FF,
|
.fg = 0xD2A6FF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Brace2] = {
|
theme.hl[io::Token::Brace2] = {
|
||||||
.fg = 0xFFAFAF,
|
.fg = 0xFFAFAF,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Brace3] = {
|
theme.hl[io::Token::Brace3] = {
|
||||||
.fg = 0xFFFF00,
|
.fg = 0xFFFF00,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Brace4] = {
|
theme.hl[io::Token::Brace4] = {
|
||||||
.fg = 0x0FFF0F,
|
.fg = 0x0FFF0F,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[internal::syntax::Token::Brace5] = {
|
theme.hl[io::Token::Brace5] = {
|
||||||
.fg = 0xFF0F0F,
|
.fg = 0xFF0F0F,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
@@ -156,7 +268,6 @@ Theme Theme::default_theme() {
|
|||||||
Theme Theme::from_name(std::string_view name) {
|
Theme Theme::from_name(std::string_view name) {
|
||||||
if (name == "default")
|
if (name == "default")
|
||||||
return default_theme();
|
return default_theme();
|
||||||
throw std::runtime_error("Unknown theme: " + std::string(name));
|
throw ed_error("Unknown theme: " + std::string(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace bed::internal::theme
|
} // namespace bed::internal::theme
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "internal/ui/command.h"
|
#include "internal/ui/command.h"
|
||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
namespace bed::internal::ui {
|
namespace bed::internal::ui {
|
||||||
/*template <typename F>
|
/*template <typename F>
|
||||||
@@ -69,6 +70,17 @@ CommandIO::CommandIO(BEd &bed) : bed(bed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::string, bool> CommandIO::run() {
|
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 [row, col] = bed.io.cursor_position();
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
if (row > rows)
|
if (row > rows)
|
||||||
@@ -151,7 +163,22 @@ void CommandIO::redraw() {
|
|||||||
bed.io.write("\x1b[2K", 4);
|
bed.io.write("\x1b[2K", 4);
|
||||||
bed.io.move_cursor(start, 1);
|
bed.io.move_cursor(start, 1);
|
||||||
bed.io.write(prompt);
|
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);
|
bed.io.move_cursor(start, prompt.size() + cursor + 1);
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::ui
|
} // namespace bed::internal::ui
|
||||||
|
|||||||
@@ -7,6 +7,23 @@ TextMode::TextMode(BEd &bed) : bed(bed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<vase::Shard *, bool> TextMode::run() {
|
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() {
|
||||||
|
cmd.clear();
|
||||||
|
while (true) {
|
||||||
|
auto [str, eof] = bed.io.read_pipe();
|
||||||
|
if (str == "." || eof)
|
||||||
|
break;
|
||||||
|
cmd += str + "\n";
|
||||||
|
}
|
||||||
|
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||||
auto [row, col] = bed.io.cursor_position();
|
auto [row, col] = bed.io.cursor_position();
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
if (row > rows)
|
if (row > rows)
|
||||||
@@ -42,9 +59,8 @@ std::pair<vase::Shard *, bool> TextMode::run() {
|
|||||||
cmd.erase(--cursor, 1);
|
cmd.erase(--cursor, 1);
|
||||||
}
|
}
|
||||||
} else if (res.text[0] == '\n') {
|
} 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');
|
cmd.insert(cursor++, 1, '\n');
|
||||||
|
grow();
|
||||||
} else {
|
} else {
|
||||||
cmd.insert(cursor, res.text);
|
cmd.insert(cursor, res.text);
|
||||||
cursor += res.text.size();
|
cursor += res.text.size();
|
||||||
@@ -55,6 +71,7 @@ std::pair<vase::Shard *, bool> TextMode::run() {
|
|||||||
case io::KeyEvent::KeyType::PASTE:
|
case io::KeyEvent::KeyType::PASTE:
|
||||||
cmd.insert(cursor, res.text);
|
cmd.insert(cursor, res.text);
|
||||||
cursor += res.text.size();
|
cursor += res.text.size();
|
||||||
|
grow();
|
||||||
break;
|
break;
|
||||||
case io::KeyEvent::KeyType::SPECIAL:
|
case io::KeyEvent::KeyType::SPECIAL:
|
||||||
switch (res.special_key) {
|
switch (res.special_key) {
|
||||||
@@ -123,16 +140,22 @@ std::pair<vase::Shard *, bool> TextMode::run() {
|
|||||||
cursor = cmd.size();
|
cursor = cmd.size();
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
|
if (cmd.size() == 2 && cmd.compare(0, 2, ".\n") == 0) {
|
||||||
|
cmd.clear();
|
||||||
|
cursor = 0;
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t total_lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
|
size_t total_lines = cmd.size() ? 1 + std::count(cmd.begin(), cmd.end(), '\n') : 0;
|
||||||
uint16_t last_row = start + total_lines;
|
uint16_t last_row = start + total_lines;
|
||||||
bed.io.move_cursor(last_row, 1);
|
bed.io.move_cursor(last_row, 1);
|
||||||
bed.io.write("\n", 1);
|
bed.io.write("\n", 1);
|
||||||
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextMode::grow(size_t required_height) {
|
void TextMode::grow() {
|
||||||
|
size_t required_height = 1 + std::count(cmd.begin(), cmd.end(), '\n');
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
term_height = rows;
|
term_height = rows;
|
||||||
term_width = cols;
|
term_width = cols;
|
||||||
|
|||||||
@@ -90,12 +90,7 @@ Shard *substitute(
|
|||||||
if (matches.empty())
|
if (matches.empty())
|
||||||
return root;
|
return root;
|
||||||
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
|
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
|
||||||
struct Edit {
|
uint64_t current_line = 1;
|
||||||
uint64_t line;
|
|
||||||
uint64_t old_lines;
|
|
||||||
uint64_t new_lines;
|
|
||||||
};
|
|
||||||
uint64_t orig_line = start;
|
|
||||||
int64_t line_delta = 0;
|
int64_t line_delta = 0;
|
||||||
std::vector<Shard *> pieces;
|
std::vector<Shard *> pieces;
|
||||||
pieces.reserve(matches.size() * 2 + 1);
|
pieces.reserve(matches.size() * 2 + 1);
|
||||||
@@ -109,7 +104,7 @@ Shard *substitute(
|
|||||||
Shard::release(remaining);
|
Shard::release(remaining);
|
||||||
pieces.push_back(keep);
|
pieces.push_back(keep);
|
||||||
remaining = rest;
|
remaining = rest;
|
||||||
orig_line += keep ? keep->lines : 0;
|
current_line += keep ? keep->lines : 0;
|
||||||
}
|
}
|
||||||
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
||||||
Shard::release(remaining);
|
Shard::release(remaining);
|
||||||
@@ -152,13 +147,11 @@ Shard *substitute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shard::release(dropped);
|
Shard::release(dropped);
|
||||||
if (old_lines || new_lines) {
|
uint64_t report_line = (uint64_t)((int64_t)current_line + line_delta);
|
||||||
uint64_t report_line = (uint64_t)((int64_t)orig_line + line_delta);
|
if (on_edit)
|
||||||
if (on_edit)
|
on_edit(report_line, old_lines, new_lines);
|
||||||
on_edit(report_line, old_lines, new_lines);
|
line_delta += (int64_t)new_lines - (int64_t)old_lines;
|
||||||
line_delta += (int64_t)new_lines - (int64_t)old_lines;
|
current_line += old_lines;
|
||||||
}
|
|
||||||
orig_line += old_lines;
|
|
||||||
cursor = match.end;
|
cursor = match.end;
|
||||||
}
|
}
|
||||||
pieces.push_back(remaining);
|
pieces.push_back(remaining);
|
||||||
|
|||||||
@@ -289,6 +289,8 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
|||||||
last = nullptr;
|
last = nullptr;
|
||||||
if (!pieces.empty())
|
if (!pieces.empty())
|
||||||
last = (Petal *)pieces.back();
|
last = (Petal *)pieces.back();
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (last && ending[0] == '\r')
|
if (last && ending[0] == '\r')
|
||||||
last->length--;
|
last->length--;
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
|
|||||||
Shard::retain(text);
|
Shard::retain(text);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
if (!text)
|
||||||
|
return root;
|
||||||
if (line > root->lines + 1)
|
if (line > root->lines + 1)
|
||||||
throw ed_error("line out of range");
|
throw ed_error("line out of range");
|
||||||
Shard::retain(text);
|
Shard::retain(text);
|
||||||
@@ -201,6 +203,34 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
|
|||||||
return result;
|
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) {
|
Shard *erase(Shard *root, uint64_t start, uint64_t end) {
|
||||||
if (!start || !end)
|
if (!start || !end)
|
||||||
throw ed_error("Invalid range.");
|
throw ed_error("Invalid range.");
|
||||||
|
|||||||
+1
-2
@@ -4,8 +4,7 @@
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
std::vector<std::string> args(argv, argv + argc);
|
std::vector<std::string> args(argv, argv + argc);
|
||||||
try {
|
try {
|
||||||
bed::internal::io::IO io = bed::internal::io::IO();
|
bed::BEd ed(args);
|
||||||
bed::BEd ed(args, io);
|
|
||||||
ed.run();
|
ed.run();
|
||||||
} catch (bed::fatal_error &e) {
|
} catch (bed::fatal_error &e) {
|
||||||
if (e.code)
|
if (e.code)
|
||||||
|
|||||||
Reference in New Issue
Block a user