Compare commits

...
59 Commits
Author SHA1 Message Date
syedm c0f9c6a234 Make r fucntion allow zero. 2026-08-31 10:16:25 +01:00
syedm 4b0f3b8fb7 Fix first == npos bug. 2026-08-31 10:15:07 +01:00
syedm 24914028c0 Fix incorrect error message. 2026-08-31 10:13:46 +01:00
syedm 0d126021d0 Seperate cd and pwd semantics to mimic shells 2026-08-31 10:13:14 +01:00
syedm e44267a6fe Cleanup io calls and add cd function. 2026-08-31 10:11:29 +01:00
syedm a996beb4aa Add all basic ed functions
- except global and history ones
2026-08-31 08:14:12 +01:00
syedm 010f916d35 Rename buffer.cc. 2026-08-30 19:19:14 +01:00
syedm 81bb65b7cf Add clipboard buffer. 2026-08-30 19:18:26 +01:00
syedm 49bf5a2e2d Make substitutions work and other minor fixes. 2026-08-30 17:02:34 +01:00
syedm 41049b1ab6 Make % address work with @ mode
- add `d` function.
2026-08-30 14:56:08 +01:00
syedm 4a87a1a834 Fix incorrect start of range. 2026-08-30 14:38:49 +01:00
syedm 44e58b3041 Fix off by one error. 2026-08-30 14:36:36 +01:00
syedm 7a18aa0db2 Add text mode handling. 2026-08-30 14:33:39 +01:00
syedm 87d9148b82 Fix segv on uninitialized command fields. 2026-08-30 13:08:48 +01:00
syedm e19a8e6bba Fix compile issue (due to incorrect include) 2026-08-30 12:54:35 +01:00
syedm afa60dd12f Fixes. 2026-08-30 12:51:11 +01:00
syedm b8eac7b2da Major updates:
- A lotta cleanup
- BEd command parser rewrite
- Vase class removed
- Proper IO system.
- A lot more.
2026-08-29 22:30:01 +01:00
syedm d7278251ec Update trie to return better results for search. 2026-08-23 16:01:27 +01:00
syedm c8a270378a Improve suffix handling for commands. 2026-08-23 13:16:33 +01:00
syedm 6f1c87400a Improve command input and adding resize handling. 2026-08-23 13:16:08 +01:00
syedm f4e3a190b8 Fix one off error in regex reverse search. 2026-08-23 11:10:11 +01:00
syedm dba279b036 Fix nix compilation issue with marks system. 2026-08-22 23:06:19 +01:00
syedm 2ac71085db Add mouse mode cleanup in IO. 2026-08-22 22:58:01 +01:00
syedm 3563324a1c Major update
- Add an IO system to hijack a bit of the terminal.
- Other minor fixes.
2026-08-22 22:57:18 +01:00
syedm 1f3b53753a Cleanup. 2026-08-22 15:31:21 +01:00
syedm 387ea61efd Add a sample ruby file 2026-08-22 15:05:07 +01:00
syedm bea8ff18c0 Add marks and regex addressing systems
- Hook up parser to buffer edits.
- Other minor fixes.
2026-08-22 15:01:48 +01:00
syedm 2a903f761a Minor fixes in address resolving. 2026-08-21 21:57:43 +01:00
syedm 9680a18e05 Decrease memory footprint of storing blocks. 2026-08-21 21:36:28 +01:00
syedm 556bb631f6 Fixing addressing
- Fix double resolve bug in address handler
- Fix multiple major issues with next_closing and prev_opening functions.
- add `=` ed command.
- Other minor fixes.
2026-08-21 20:35:20 +01:00
syedm 3e3fb6c39d Major updates
- Improve ruby block detection heuristics
- Add block information storage and block aware movements.
- Other minor fixes
2026-08-21 18:34:22 +01:00
syedm 739d2e450e Improve ruby parsing heuristics. 2026-08-21 12:28:16 +01:00
syedm 6ea03d651a Update syntax parser system. 2026-08-20 19:33:45 +01:00
syedm 1489b08d57 Make parser optional. 2026-08-20 13:12:48 +01:00
syedm 3a41af01f9 Implement syntax highlighting
- Create a generic incremental syntax highlighting system.
- Implement a ruby syntax highlighter for it.
2026-08-20 02:23:11 +01:00
syedm 48fb8e3501 Add a few more ed commands. 2026-08-13 16:38:01 +01:00
syedm cd13557e15 Reqrite project as BEd specific. 2026-08-13 16:17:32 +01:00
syedm cc5d475ab8 Fix incorrect parsing of quit commands. 2026-08-10 20:38:57 +01:00
syedm c077ba2dc8 Reformat newline 2026-08-10 18:16:54 +01:00
syedm e2a79c60d9 Rename ed to BEd 2026-08-10 18:15:55 +01:00
syedm 31a43e17bb Update readme.md 2026-08-10 17:44:43 +01:00
syedm 20ac202f88 Rewrite plans for BEd. 2026-08-10 17:13:01 +01:00
syedm 512bdd522c Update readme.md 2026-08-10 16:56:17 +01:00
syedm b852c604c8 Update readme with plans for BEd. 2026-08-10 16:52:35 +01:00
syedm f6d4987b8c Add some more ed commands. 2026-08-10 16:29:33 +01:00
syedm 23b7b8c8a8 Fix iteration and petal append bugs. 2026-08-10 14:21:46 +01:00
syedm e56f784c9a Add more ed commands. 2026-08-09 21:44:02 +01:00
syedm f044f69f1c Setup ed the posix line editor. 2026-08-09 16:31:09 +01:00
syedm 717c048d6f Setup routing. 2026-08-07 14:21:44 +01:00
syedm fdb67b449d Setup cli routing & other cleanup. 2026-08-06 23:21:38 +01:00
syedm 04a27b30aa namespace vase. 2026-08-06 20:36:14 +01:00
syedm 0720a254a3 Rearrange project files. 2026-08-06 20:16:40 +01:00
syedm 9841c2cf15 Update readme. 2026-08-06 19:40:54 +01:00
syedm 189b9ab6ca Make append buffer contiguous and update iterators 2026-08-06 19:39:59 +01:00
syedm 5722731c6a Cleanup namespace and api. 2026-08-04 23:41:31 +01:00
syedm 1f57c40eef Cleanup 2026-08-04 09:41:57 +01:00
syedm e253fa0efb Remove debugging line 2026-08-04 07:50:28 +01:00
syedm 0bfa9300db Perfect
- Make original buffer use a disk backed mapping to save memory.
- rearrange api function headers for search etc.
- fix bugs with iterators.
- support uint64_t for all byte and line offsets to allow for much larger files to be opened.
- and more minor bug fixes.
2026-08-04 07:48:26 +01:00
syedm 72d0ff85a3 Fix searching api. 2026-08-03 20:34:24 +01:00
82 changed files with 8743 additions and 1459 deletions
+1
View File
@@ -7,3 +7,4 @@ build/
.cache/ .cache/
sample.txt sample.txt
old*
+1 -1
View File
@@ -627,7 +627,7 @@ attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found. "copyright" line and a pointer to where the full notice is found.
Crib - Text editing tools. BEd - Better Ed.
Copyright (C) 2026 Syed (me@syedm.dev) Copyright (C) 2026 Syed (me@syedm.dev)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
+3 -3
View File
@@ -6,8 +6,8 @@ INCLUDE_DIR := include
CXX ?= g++ CXX ?= g++
CC ?= gcc CC ?= gcc
TARGET_DEBUG := $(BIN_DIR)/crib-dbg TARGET_DEBUG := $(BIN_DIR)/bed-dbg
TARGET_RELEASE := $(BIN_DIR)/crib TARGET_RELEASE := $(BIN_DIR)/bed
PCH_DIR_DEBUG := $(OBJ_DIR)/debug/pch PCH_DIR_DEBUG := $(OBJ_DIR)/debug/pch
PCH_DIR_RELEASE := $(OBJ_DIR)/release/pch PCH_DIR_RELEASE := $(OBJ_DIR)/release/pch
@@ -26,7 +26,7 @@ MRUBY_LIBS ?= -L./libs/mruby/build/host/lib -lmruby
CFLAGS_DEBUG :=\ CFLAGS_DEBUG :=\
-std=c++23 -Wall -Wextra -fno-rtti \ -std=c++23 -Wall -Wextra -fno-rtti \
-Og -fno-inline -gsplit-dwarf \ -Og -fno-inline -gsplit-dwarf -DDEBUG \
-g -fno-omit-frame-pointer -ffast-math \ -g -fno-omit-frame-pointer -ffast-math \
-I./include -I./libs/unicode_width -I./include -I./libs/unicode_width
+43 -22
View File
@@ -1,26 +1,47 @@
# Crib ## BEd
A set of text editing tools (still in early development). Better ed.<br/>
An ed implementation (mostly posix compliant) but with:
- Lsp support for autocomplete suggetions when in ed text mode.
- Autocomplete for language snippets + other words found near the cursor etc.
- text mode where arrow keys etc. work as expected.
- All code printing done with syntax highlighting.
- Lsp diagnostics command to list global errors/warnings, to print thir lines, jump to their lines, edit them etc.
- Addressing modes that work by addressing ->
- symbol definitions (lsp)
- diagnostic points (lsp)
- Most lsp stuff (like rename symbol, symbol reference count etc.)
- Inlay hints while editing and printing.
- A read write command to open a set of lines similar to c command but with the text from before already filled.
- Code formatters (lsp and others).
- Ruby based extentions.
- Custom functions, live ruby code running.
- Debuggers (dap).
- Better shell commands/integration.
- Ruby based configs. (embedded Mruby)
- Remappable commands/macros.
- Better history & session saves.
- Multiple buffers per session support.
- Indentation engine (with editorconfig loading.).
- Build systems, npm, nix, make etc integration.
- More complete & fast regex.
- Better current line semantics.
- Full unicode + graphemes support.
- SSH support for loading files/lsp etc (and fully remote sessions.).
- Git commands / file diffs show in editor.
- File operations, directory & files listing.
- Nerdfonts & themable colorized outputs.
- Better address marking with ranges.
- text copy/paste & registers.
- & A lot more cool stuff.
### Modules ### Implementation status:
#### `Vase` - Internal buffer:
- Super fast and memory efficient buffer implementation done.
Vase is a avl piece tree like structure which handles loading, insertion, erasure, regex searching, regex replacing etc. - Loading from files/subshell commands done.
<br/> - Syntax highlighter and block parser system done.
almost done. - All ed addressing modes and block addressing done (also `%`).
#### Ed
Ed is an ed (the posix line editor) implementation using `Vase`.
<br/>
Not done yet.
#### `hl`
`hl` is a stateful super fast syntax highlighter.
<br/>
Not done yet.
#### `lsp` #### `lsp`
@@ -34,6 +55,6 @@ It should support:
- Loading lsp features etc. automatically. - Loading lsp features etc. automatically.
- Error handling. - Error handling.
- And more. - And more.
<br/>
<br/>
Not done yet. Not done yet.
+8 -10
View File
@@ -1,5 +1,5 @@
{ {
description = "Crib - An IDE."; description = "BEd - Better Ed.";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -47,17 +47,16 @@
''; '';
}; };
crib = pkgs.stdenv.mkDerivation { bed = pkgs.stdenv.mkDerivation {
pname = "crib"; pname = "bed";
version = "0.1.0"; version = "0.1.0";
src = ./.; src = ./.;
nativeBuildInputs = with hostPkgs; [ nativeBuildInputs = with hostPkgs; [
bash
pkg-config pkg-config
gnumake gnumake
xxd
bash
findutils findutils
binutils binutils
]; ];
@@ -78,19 +77,19 @@
installPhase = '' installPhase = ''
mkdir -p $out/bin mkdir -p $out/bin
cp bin/crib $out/bin/crib cp bin/bed $out/bin/bed
''; '';
}; };
in in
{ {
packages.${system} = { packages.${system} = {
default = crib; default = bed;
crib = crib; bed = bed;
mruby = mruby; mruby = mruby;
}; };
devShells.${system}.default = hostPkgs.mkShell { devShells.${system}.default = hostPkgs.mkShell {
inputsFrom = [ crib ]; inputsFrom = [ bed ];
packages = [ packages = [
hostPkgs.clang-tools hostPkgs.clang-tools
@@ -99,7 +98,6 @@
hostPkgs.gnumake hostPkgs.gnumake
hostPkgs.bear hostPkgs.bear
hostPkgs.ruby hostPkgs.ruby
hostPkgs.xxd
hostPkgs.gdb hostPkgs.gdb
hostPkgs.binutils hostPkgs.binutils
hostPkgs.findutils hostPkgs.findutils
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include "definitions.h"
#include "internal/buffer/buffer.h"
#include "internal/functions/functions.h"
#include "internal/functions/suffixes.h"
#include "internal/io/io.h"
#include "internal/marks/marks.h"
#include "internal/theme/theme.h"
#include "internal/ui/command.h"
#include "internal/ui/text_mode.h"
#include "pch.h"
namespace bed {
struct BEd {
internal::trie::Trie<internal::functions::Function> functions;
internal::functions::Function no_op;
internal::functions::Function eof_op;
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
internal::theme::Theme theme;
std::unordered_map<std::string, internal::syntax::Language> languages;
internal::io::IO &io;
internal::vase::AppendStorage append{"/tmp"};
bool help_mode = false;
bool prompt_mode = true;
std::function<std::string(BEd &)> prompt = nullptr;
bool suppress_mode = false;
bool temporary_current = false;
std::string last_help = "";
std::string last_regex = "";
std::string last_symbol = "";
std::string last_replacement = "";
std::string last_shell = "";
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
internal::buffer::Range prev_1;
internal::buffer::Range prev_2;
internal::marks::MarksEngine marks;
BEd(std::vector<std::string> args, internal::io::IO &io);
~BEd();
internal::buffer::Buffer &buffer(const std::string &);
internal::buffer::Line &current();
internal::buffer::Range &prev();
void mark(uint8_t, internal::buffer::Line);
void handle(std::string_view cmd, bool eof);
void run();
void suffix_handle(char s);
bool escape_command(std::string &cmd, std::string_view filename);
};
} // namespace bed
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "internal/vase/vase.h"
#include "pch.h"
namespace bed {
struct BEd;
struct fatal_error : std::runtime_error {
uint8_t code;
fatal_error(std::string msg, uint8_t code)
: std::runtime_error(msg), code(code) {}
};
struct ed_error : std::runtime_error {
ed_error(std::string msg) : std::runtime_error(msg) {}
};
struct Highlight {
enum : uint8_t {
None = 0,
Bold = 1 << 0,
Italic = 1 << 1,
Strikethrough = 1 << 2,
Underline = 1 << 3,
};
uint32_t fg;
uint32_t bg;
uint8_t flags;
};
} // namespace bed
View File
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include "clip.h"
#include "decl.h"
#include "generic.h"
#include "pch.h"
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "decl.h"
#include "pch.h"
namespace bed::internal::buffer {
struct ClipBuffer : Buffer {
ClipBuffer(std::string name)
: Buffer(name, Kind::Clip) {};
~ClipBuffer();
void clip_write(vase::Shard *text);
bool waste() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) override;
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
};
} // namespace bed::internal::buffer
+146
View File
@@ -0,0 +1,146 @@
#pragma once
#include "definitions.h"
#include "internal/syntax/parser.h"
#include "internal/syntax/ruby/parser.h"
#include "internal/theme/theme.h"
#include "pch.h"
namespace bed::internal::buffer {
struct Buffer {
enum struct Kind : uint8_t {
Generic,
Null,
Clip,
Cancel,
Shell,
} kind;
enum : uint8_t {
Special,
Unmodified,
Modified,
Warned
} state;
std::string name;
explicit Buffer(std::string name, Kind kind)
: kind(kind), state(Unmodified), name(name) {};
virtual ~Buffer() = default;
virtual bool waste() = 0;
virtual uint64_t lines() = 0;
virtual uint64_t bytes() = 0;
virtual void set_filename(std::filesystem::path path) = 0;
virtual std::filesystem::path filename() = 0;
virtual void load(BEd &ctx, vase::Shard *text) = 0;
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
virtual void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) = 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 append(BEd &ctx, vase::Shard *text, uint64_t line) = 0;
virtual void print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual uint64_t find_next(std::string_view pattern, uint64_t start) = 0;
virtual uint64_t find_prev(std::string_view pattern, uint64_t start) = 0;
virtual uint64_t next_closing(uint64_t start) = 0;
virtual uint64_t prev_closing(uint64_t start) = 0;
};
struct Line {
std::string buffername;
uint64_t number;
};
struct Range {
std::string buffername;
uint64_t start;
uint64_t end;
explicit Range(const Line &a, const Line &b) {
if (a.buffername != b.buffername)
throw ed_error("Invalid range.");
if (a.number > b.number)
throw ed_error("Invalid range.");
buffername = a.buffername;
start = a.number;
end = b.number;
};
explicit Range(std::string buffername, uint64_t start, uint64_t end)
: buffername(buffername), start(start), end(end) {
if (start > end)
throw ed_error("Invalid range.");
};
explicit Range() : buffername("default"), start(0), end(0) {};
};
using Address = std::variant<
std::string,
buffer::Range,
buffer::Line>;
inline static std::string list_string(std::string_view s) {
uint32_t width = 80;
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
width = ws.ws_col;
std::string out;
out.reserve(s.size());
const uint32_t max_width = width > 1 ? width - 1 : 1;
uint32_t column = 0;
auto append = [&](std::string_view text) {
if (column + text.size() > max_width) {
out += "\\\n";
column = 0;
}
out += text;
column += text.size();
};
for (unsigned char c : s) {
switch (c) {
case '\\':
append("\\\\");
break;
case '$':
append("\\$");
break;
case '\a':
append("\\a");
break;
case '\b':
append("\\b");
break;
case '\f':
append("\\f");
break;
case '\r':
append("\\r");
break;
case '\t':
append("\\t");
break;
case '\v':
append("\\v");
break;
default:
if (!std::isprint(c)) {
char buf[5];
std::snprintf(buf, sizeof(buf), "\\%03o", c);
append(buf);
} else {
append(std::string_view((const char *)&c, 1));
}
break;
}
}
out += '$';
return out;
}
} // namespace bed::internal::buffer
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "decl.h"
#include "pch.h"
namespace bed::internal::buffer {
struct GenericBuffer : Buffer {
vase::Shard *root;
std::filesystem::path save_path{};
std::string language{};
std::optional<syntax::Parser> parser{};
GenericBuffer(std::string name)
: Buffer(name, Kind::Generic), root(nullptr) {};
~GenericBuffer();
bool waste() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) override;
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
};
} // namespace bed::internal::buffer
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include "definitions.h"
#include "internal/buffer/buffer.h"
#include "internal/trie/trie.h"
#include "pch.h"
namespace bed::internal::functions {
struct Function {
struct GlobalArg {
char delim;
std::string str;
};
struct RegexArg {
std::string expression;
std::string replacement;
std::string options;
};
struct ShellArg {
std::string cmd;
};
struct RubyArg {
std::string cmd;
};
using Argument = std::variant<
std::monostate,
GlobalArg,
RegexArg,
ShellArg,
RubyArg,
std::string,
std::filesystem::path,
int64_t,
char,
buffer::Range,
buffer::Line>;
enum struct AddressKind {
None,
Line,
Range
} address_kind;
enum struct ArgumentKind {
None,
Regex,
Shell,
Ruby,
Any,
File,
Number,
Mark,
Range,
Line,
Global
} argument_kind;
enum struct InputMode {
None,
Text,
Interactive,
CommandList
} input_mode;
std::string desc;
std::string default_address;
bool accept_zero;
std::function<
std::tuple<vase::Shard *, syntax::Language *, void *>(
BEd &ctx, const buffer::Address &addr, const Argument &arg
)>
pre_text_mode;
std::function<
void(
BEd &ctx, const buffer::Address &addr,
vase::Shard *text, const Argument &arg,
std::vector<buffer::Line> *marked
)>
handle;
static void register_posix(BEd &ctx);
static void register_extented(BEd &ctx);
};
} // namespace bed::internal::functions
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "definitions.h"
#include "internal/buffer/buffer.h"
#include "pch.h"
namespace bed::internal::functions {
struct Suffix {
std::string desc;
std::function<void(BEd &)> handle;
static void register_suffixes(BEd &ctx);
};
} // namespace bed::internal::functions
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "pch.h"
namespace bed::internal {
enum struct Direction : uint8_t {
Forward,
Backward
};
inline static bool write_all(int fd, const void *data, size_t len) {
const char *p = (const char *)data;
while (len > 0) {
ssize_t n = write(fd, p, len);
if (n > 0) {
p += n;
len -= (size_t)n;
continue;
}
if (n == -1 && errno == EINTR)
continue;
return false;
}
return true;
}
} // namespace bed::internal
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::io {
struct KeyEvent {
enum struct ReadResult {
SUCCESS,
EOF_,
RESIZE
};
enum struct KeyType {
EOF_,
CHAR,
SPECIAL,
MOUSE,
PASTE,
RESIZE
};
enum struct SpecialKey {
UP,
DOWN,
LEFT,
RIGHT,
DELETE,
UNKNOWN
};
enum struct Modifier {
NONE,
SHIFT,
ALT,
CTRL,
CTRL_ALT
};
enum struct MouseState {
PRESS,
RELEASE
};
KeyType type = KeyType::EOF_;
std::string text;
SpecialKey special_key = SpecialKey::UNKNOWN;
Modifier modifier = Modifier::NONE;
MouseState mouse_state = MouseState::PRESS;
uint16_t mouse_x = 0;
uint16_t mouse_y = 0;
};
struct IO {
static termios orig_termios;
static termios raw_termios;
static bool cleaned;
static void cleanup();
static void enable_raw();
static volatile std::atomic_bool resized;
static void handle_sigwinch(int);
IO();
~IO();
IO(const IO &) = delete;
IO &operator=(const IO &) = delete;
void enable_mouse();
void disable_mouse();
std::pair<uint16_t, uint16_t> terminal_size();
std::pair<uint16_t, uint16_t> cursor_position();
void move_cursor(uint16_t row, uint16_t col);
KeyEvent read_key();
void write(const char *, uint64_t);
void write(std::string_view);
void write_line(std::string_view);
void run_pty(const std::string &);
std::deque<char> input_queue;
KeyEvent::ReadResult get_next_byte(char &out);
void enqueue_bytes(const std::string &bytes);
static int utf8_seq_len(uint8_t byte);
KeyEvent::ReadResult read_next_unit(std::string &out);
KeyEvent::ReadResult read_bracketed_paste(std::string &out);
KeyEvent parse_mouse(const std::string &buf);
KeyEvent parse_escape(const std::string &buf);
};
} // namespace bed::internal::io
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "internal/buffer/buffer.h"
#include "pch.h"
namespace bed::internal::marks {
struct MarksEngine {
buffer::Line marks[256];
MarksEngine() {
for (auto &m : marks)
m = {"", UINT64_MAX};
}
buffer::Line &get(uint8_t m) {
return marks[m];
}
void insert(std::string bufname, uint64_t start, uint64_t count) {
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
if (marks[i].buffername != bufname)
continue;
if (marks[i].number == UINT64_MAX)
continue;
if (marks[i].number >= start)
marks[i].number += count;
}
}
void erase(std::string bufname, uint64_t start, uint64_t count) {
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
if (marks[i].buffername != bufname)
continue;
if (marks[i].number == UINT64_MAX)
continue;
if (marks[i].number >= start) {
if (marks[i].number < start + count)
marks[i].number = UINT64_MAX;
else
marks[i].number -= count;
}
}
}
void collapse(std::string bufname, uint64_t start, uint64_t count) {
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
if (marks[i].buffername != bufname)
continue;
if (marks[i].number == UINT64_MAX)
continue;
if (marks[i].number > start) {
if (marks[i].number <= start + count)
marks[i].number = start;
else
marks[i].number -= count;
}
}
}
};
} // namespace bed::internal::marks
+114
View File
@@ -0,0 +1,114 @@
#pragma once
#include "internal/functions/functions.h"
#include "internal/functions/suffixes.h"
#include "internal/ui/command.h"
#include "pch.h"
namespace bed::internal::parser {
struct AddressPromise {
struct None {}; //
struct Current {}; // .
struct Last {}; // $
struct Number { // n
uint64_t i;
};
struct Mark { // 'm
char m;
};
struct Regex { // /re/ or ?re?
internal::Direction dir;
std::string re;
};
struct Diagnostic { // ^ or ~ for previous/next diagnostic
internal::Direction dir;
};
struct SymbolDefinition { // <sym> goto symbol definition. (From lsp or using internal language parsers)
std::string sym;
};
struct SymbolReference { // >sym> or <sym<
internal::Direction dir; // goto next or previous symbol reference
std::string sym;
};
struct Block { // [ or ] goto start/end of containing block.
internal::Direction dir;
};
struct Scripted { // {function_name:arguments}
std::string func; // Calls ruby mapping with name giving the argument as string.
std::string arg; // resolves to line number returned by function (or throws error).
// The mruby runtime also has the full context of the file and extentions etc.
};
struct LastRange {}; // % , refers to last used range
std::optional<std::string> bufname;
std::variant<
None,
Current,
Last,
Number,
Mark,
Regex,
Diagnostic,
SymbolDefinition,
SymbolReference,
Block,
Scripted,
LastRange>
base{};
int64_t offset = 0;
bool jumping{false}; // ; == jumping and , == non jumping
buffer::Line resolve(BEd &ctx);
static std::optional<buffer::Line> get_line(BEd &ctx, std::vector<AddressPromise> &);
static std::optional<buffer::Range> get_range(BEd &ctx, std::vector<AddressPromise> &);
};
struct Command {
bool temp_address{false};
std::vector<AddressPromise> addresses{};
functions::Function *function{nullptr};
functions::Function::Argument argument{std::monostate()};
std::vector<AddressPromise> argument_addresses{};
functions::Suffix *suffix{nullptr};
};
struct CompletionContext {
enum {
Error,
Valid,
Incomplete
} error;
// TODO: store a state of typing context to think about possible completions.
};
struct Parser {
BEd &bed;
std::string_view cmd;
uint16_t i;
Command *command;
std::vector<ui::Token> *tokens;
CompletionContext *completion;
explicit Parser(
std::string_view cmd, BEd &bed, Command *command,
std::vector<ui::Token> *tokens, CompletionContext *completion
);
char peek(uint16_t = 0); // == \0 if at eof.
std::string_view peek_str(uint16_t = UINT16_MAX);
void advance(uint16_t = 1);
void skip_ws();
void locator(AddressPromise &);
int64_t offset();
void address(AddressPromise &addr);
void addresses(std::vector<AddressPromise> &addresses);
void operation();
void parse();
static Command get_command(std::string_view, BEd &);
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
};
} // namespace bed::internal::parser
+119
View File
@@ -0,0 +1,119 @@
#pragma once
#include "internal/trie/trie.h"
#include "pch.h"
namespace bed::internal::syntax {
struct Token {
uint32_t start;
uint32_t end;
enum Kind : uint8_t {
Data,
Shebang,
Comment,
Error,
String,
Escape,
Interpolation,
Regexp,
Number,
True,
False,
Char,
Keyword,
KeywordOperator,
Operator,
Function,
Namespace,
Class,
Module,
Type,
Constant,
VariableInstance,
VariableGlobal,
Annotation,
Directive,
Label,
Brace1,
Brace2,
Brace3,
Brace4,
Brace5,
Heading1,
Heading2,
Heading3,
Heading4,
Heading5,
Heading6,
Blockquote,
List,
ListItem,
Code,
LanguageName,
LinkLabel,
ImageLabel,
Link,
Table,
TableHeader,
Italic,
Bold,
Underline,
Strikethrough,
HorizontalRule,
Tag,
Attribute,
CheckDone,
CheckNotDone,
Count
} type;
};
struct ParseEvent {
uint8_t closing;
ParseEvent(uint8_t t) : closing(t) {}
};
struct Language {
std::function<void *()> none_state;
std::function<void(void **, std::string_view, bool, std::vector<Token> *, std::vector<ParseEvent> *)> parse;
std::function<void *(void *)> copy;
std::function<bool(void *, void *)> equal;
std::function<void(void *)> destroy;
};
struct ParseState {
static constexpr uint64_t BRANCH_BIT = 1ull << 63;
static constexpr uint64_t LINES_MASK = ~BRANCH_BIT;
uint64_t header;
bool is_branch() const {
return header & BRANCH_BIT;
}
uint64_t lines() const {
return header & LINES_MASK;
}
};
struct ParseStateBranch : ParseState {
ParseState *left;
ParseState *right;
};
struct ParseStateLeaf : ParseState {
void *state;
uint32_t n;
uint32_t cap;
uint16_t *blocks;
static constexpr uint16_t IS_CLOSING = 0x8000;
static constexpr uint16_t LINE_MASK = 0x7fff;
};
struct TreeCursor {
ParseStateLeaf *leaf = nullptr;
ParseStateBranch *stack[64];
uint8_t depth = 0;
bool went_left[64];
TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative);
void next();
void prev();
};
} // namespace bed::internal::syntax
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include "decl.h"
#include "internal/vase/vase.h"
#include "pch.h"
namespace bed::internal::syntax {
struct Parser {
static constexpr uint64_t MAX_CHUNK = 512;
ParseState *root;
Language lang;
bool in_edit = false;
bool dirty = false;
uint64_t dirty_start = 0;
uint64_t dirty_end = 0;
Parser(vase::Shard *, uint64_t, Language);
~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);
uint64_t next_closing(uint64_t line);
uint64_t prev_opening(uint64_t line);
std::pair<ParseState *, ParseState *> split_tree(ParseState *node, uint64_t line);
ParseState *join_tree(ParseState *a, ParseState *b);
struct Iterator {
Parser *p;
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
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include "../decl.h"
#include "pch.h"
#include "tries.h"
namespace bed::internal::syntax::ruby {
struct alignas(2) RubyState {
struct RubyInternalState {
uint16_t brace_level;
uint16_t lit_brace_level;
enum : uint8_t {
NONE,
STRING,
REGEXP,
HEREDOC,
COMMENT,
END
} state;
static constexpr uint8_t NAME_MASK = 0b00000011;
enum : uint8_t {
NONE_NAME = 0b00,
CLASS_NAME = 0b01,
DEF_NAME = 0b10,
MODULE_NAME = 0b11
};
static constexpr uint8_t NEWLINE = 1 << 5;
static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6;
static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7;
uint8_t flags = 0;
char delim_start;
char delim_end;
};
struct Heredocs {
// header masks
static constexpr const uint8_t ALLOW_INDENTATION = 0b10000000;
static constexpr const uint8_t ALLOW_INTERPOLATION = 0b01000000;
static constexpr const uint8_t LEN_MASK = 0b00111111;
// the rest will be len number of bytes with the actual name.
};
uint8_t top;
uint8_t docs;
// the stack (RubyInternalState * top)
// the docs queue (docs)
inline RubyInternalState *stack() {
return (RubyInternalState *)(this + 1);
}
inline uint8_t *heredocs() {
return (uint8_t *)(stack() + top);
}
};
Language lang_ruby();
} // namespace bed::internal::syntax::ruby
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include "decl.h"
#include "pch.h"
namespace bed::internal::syntax::ruby {
struct RubyParser {
void **v_state;
RubyState *state;
std::string_view line;
uint32_t i = 0;
bool heredoc_start_line = false;
bool ending = true;
bool op_last = false;
bool set_op_last = false;
RubyParser(void **v_state, std::string_view line)
: v_state(v_state), state((RubyState *)*v_state), line(line) {}
uint32_t len() const {
return line.size();
}
RubyState::RubyInternalState &current() {
return state->stack()[state->top - 1];
}
char peek(int32_t offset = 0) {
uint32_t pos = i + offset;
return pos < line.size() ? line[pos] : '\0';
}
std::string_view peek_str(uint32_t len) {
return line.substr(i, len);
}
void advance() {
++i;
}
void advance(uint32_t n) {
i += n;
}
void push_state() {
state->top++;
*v_state = (RubyState *)realloc(
*v_state,
sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState) * state->top
+ state->docs
);
state = (RubyState *)*v_state;
memmove(
state->heredocs(),
state->stack() + state->top - 1,
state->docs
);
current() = {
.brace_level = 1,
.lit_brace_level = 0,
.state = RubyState::RubyInternalState::NONE,
.flags = RubyState::RubyInternalState::EXPECTING_EXPRESSION | RubyState::RubyInternalState::NEWLINE,
.delim_start = '\0',
.delim_end = '\0'
};
}
void pop_state() {
state->top--;
memmove(
state->heredocs(),
state->stack() + state->top + 1,
state->docs
);
}
void enqueue_doc(uint8_t header, std::string_view name) {
uint32_t bytes = 1 + name.size();
uint32_t old_docs = state->docs;
state->docs += bytes;
*v_state = (RubyState *)realloc(
*v_state,
sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState) * state->top
+ state->docs
);
state = (RubyState *)*v_state;
uint8_t *heredocs = state->heredocs();
heredocs[old_docs] = header;
memcpy(heredocs + old_docs + 1, name.data(), name.size());
}
bool dequeue_doc(uint8_t heredoc_len) {
uint32_t bytes = heredoc_len + 1;
uint8_t *heredocs = state->heredocs();
if (state->docs -= bytes)
memmove(heredocs, heredocs + bytes, state->docs);
else
return false;
return true;
}
};
void ruby_parse(
void **v_state, std::string_view line, bool first_line,
std::vector<Token> *tokens, std::vector<ParseEvent> *events
);
} // namespace bed::internal::syntax::ruby
+350
View File
@@ -0,0 +1,350 @@
#pragma once
#include "internal/trie/trie.h"
#include "pch.h"
namespace bed::internal::syntax::ruby {
const static std::vector<std::string> types = {
"BasicObject",
"Object",
"NilClass",
"TrueClass",
"FalseClass",
"Integer",
"Fixnum",
"Bignum",
"Float",
"Rational",
"Complex",
"Numeric",
"String",
"Symbol",
"Array",
"Hash",
"Range",
"Regexp",
"Struct",
"Enumarator",
"Enumerable",
"Time",
"Date",
"IO",
"File",
"Dir",
"Thread",
"Proc",
"Method",
"Module",
"Class",
"Mutex",
"ConditionVariable",
"MatchData",
"Encoding",
"Fiber",
};
const static std::vector<std::string> builtins = {
"ARGF",
"ARGV",
"ENV",
"STDIN",
"STDOUT",
"STDERR",
"DATA",
"TOPLEVEL_BINDING",
"RUBY_PLATFORM",
"RUBY_VERSION",
"RUBY_RELEASE_DATE",
"RUBY_PATCHLEVEL",
"RUBY_ENGINE",
"__LINE__",
"__FILE__",
"__ENCODING__",
"__dir__",
"__callee__",
"__method__",
"__id__",
"__send__",
};
const static std::vector<std::string> methods = {
"abort",
"at_exit",
"binding",
"block_given?",
"caller",
"catch",
"chomp",
"chomp!",
"chop",
"chop!",
"eval",
"exec",
"exit",
"exit!",
"fail",
"fork",
"format",
"gets",
"global_variables",
"gsub",
"gsub!",
"iterator?",
"lambda",
"load",
"loop",
"open",
"print",
"printf",
"proc",
"putc",
"puts",
"raise",
"rand",
"readline",
"readlines",
"require",
"require_relative",
"select",
"sleep",
"spawn",
"split",
"sprintf",
"srand",
"sub",
"sub!",
"syscall",
"system",
"test",
"throw",
"trace_var",
"trap",
"untrace_var",
"attr",
"attr_reader",
"attr_writer",
"attr_accessor",
"class_variable_get",
"class_variable_set",
"define_method",
"instance_variable_get",
"instance_variable_set",
"private",
"protected",
"public",
"public_class_method",
"module_function",
"remove_method",
"undef_method",
"method",
"methods",
"singleton_methods",
"private_methods",
"protected_methods",
"public_methods",
"send",
"extend",
"include",
"prepend",
"clone",
"dup",
"freeze",
"taint",
"untaint",
"trust",
"untrust",
"untaint?",
"trust?",
"each",
"each_with_index",
"each_with_object",
"map",
"collect",
"select",
"reject",
"reduce",
"inject",
"find",
"detect",
"all?",
"any?",
"none?",
"one?",
"count",
"cycle",
"drop",
"drop_while",
"take",
"take_while",
"chunk",
"chunk_while",
"group_by",
"partition",
"slice_before",
"slice_after",
"nil?",
"is_a?",
"kind_of?",
"instance_of?",
"respond_to?",
"equal?",
"object_id",
"singleton_class",
"clone",
"freeze",
"tap",
"then",
};
const static std::vector<std::string> errors = {
"Error",
"Exception",
"SignalException",
"Interrupt",
"StopIteration",
"Errno",
"SystemExit",
"fatal",
};
const static std::vector<std::string> base_keywords = {
"else",
"rescue",
"ensure",
};
const static std::vector<std::string> expecting_keywords = {
"when",
"elsif",
};
const static std::vector<std::string> expecting_end_keywords = {
"case",
"for",
};
const static std::vector<std::string> conditional_keywords = {
"if",
"unless",
"while",
"until",
};
const static std::vector<std::string> end_keywords = {
"begin",
"do",
};
const static std::vector<std::string> operator_keywords = {
"alias",
"BEGIN",
"break",
"catch",
"defined?",
"in",
"next",
"redo",
"rescue",
"retry",
"self",
"nil",
"undef",
};
const static std::vector<std::string> expecting_operators = {
"super",
"and",
"return",
"not",
"yield",
"or",
};
const static std::vector<std::string> operators = {
"+",
"-",
"*",
"/",
"%",
"**",
"==",
"!=",
"===",
"<=>",
">",
">=",
"<",
"<=",
"&&",
"||",
"!",
"&",
"|",
"^",
"~",
"<<",
">>",
"=",
"+=",
"-=",
"*=",
"/=",
"%=",
"**=",
"&=",
"|=",
"^=",
"<<=",
">>=",
"..",
"...",
"===",
"=",
"=>",
"&",
"`",
"->",
"=~",
};
struct RubyTries {
trie::Trie<void> base_keywords_trie;
trie::Trie<void> expecting_keywords_trie;
trie::Trie<void> operator_keywords_trie;
trie::Trie<void> expecting_operators_trie;
trie::Trie<void> operator_trie;
trie::Trie<void> types_trie;
trie::Trie<void> builtins_trie;
trie::Trie<void> methods_trie;
trie::Trie<void> errors_trie;
trie::Trie<void> expecting_end_keywords_trie;
trie::Trie<void> conditional_keywords_trie;
trie::Trie<void> end_keywords_trie;
RubyTries() {
for (auto &keyword : base_keywords)
base_keywords_trie.insert(keyword);
for (auto &keyword : expecting_keywords)
expecting_keywords_trie.insert(keyword);
for (auto &keyword : operator_keywords)
operator_keywords_trie.insert(keyword);
for (auto &keyword : expecting_operators)
expecting_operators_trie.insert(keyword);
for (auto &keyword : operators)
operator_trie.insert(keyword);
for (auto &keyword : types)
types_trie.insert(keyword);
for (auto &keyword : builtins)
builtins_trie.insert(keyword);
for (auto &keyword : methods)
methods_trie.insert(keyword);
for (auto &keyword : errors)
errors_trie.insert(keyword);
for (auto &keyword : expecting_end_keywords)
expecting_end_keywords_trie.insert(keyword);
for (auto &keyword : conditional_keywords)
conditional_keywords_trie.insert(keyword);
for (auto &keyword : end_keywords)
end_keywords_trie.insert(keyword);
}
};
} // namespace bed::internal::syntax::ruby
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "definitions.h"
#include "internal/syntax/parser.h"
#include "pch.h"
namespace bed::internal::theme {
struct Theme {
std::array<Highlight, internal::syntax::Token::Count> hl;
Theme();
Highlight get(internal::syntax::Token token) const;
static Theme default_theme();
static Theme from_name(std::string_view name);
};
} // namespace bed::internal::theme
+293
View File
@@ -0,0 +1,293 @@
#pragma once
#include "pch.h"
namespace bed::internal::trie {
template <typename T = void>
struct Trie {
using V = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
using SearchResult = std::conditional_t<
std::is_void_v<T>,
std::string,
std::pair<std::string, V &>>;
struct Node {
std::string edge;
std::optional<V> value{};
std::vector<Node *> children;
Node(std::string e = {}) : edge(std::move(e)) {}
~Node() {
for (auto *c : children)
delete c;
};
} root;
bool case_sensitive;
Trie(bool cs = true) : case_sensitive(cs) {}
void insert(std::string_view key)
requires std::is_void_v<T>
{
_insert(key, std::monostate{});
}
void insert(std::string_view key, V el)
requires(!std::is_void_v<T>)
{
_insert(key, std::move(el));
}
void _insert(std::string_view key, V &&el) {
Node *current = &root;
uint64_t pos = 0;
while (pos < key.size()) {
Node *child = find_child(*current, key[pos]);
if (!child) {
auto *node = new Node(std::string(key.substr(pos)));
node->value = std::move(el);
current->children.push_back(node);
return;
}
const auto common = common_prefix(child->edge, key.substr(pos));
if (common == child->edge.size()) {
pos += common;
current = child;
continue;
}
auto *split = new Node(child->edge.substr(0, common));
child->edge.erase(0, common);
split->children.push_back(child);
current->children.erase(
std::find(current->children.begin(), current->children.end(), child)
);
current->children.push_back(split);
pos += common;
if (pos == key.size()) {
split->value = std::move(el);
return;
}
auto *node = new Node(std::string(key.substr(pos)));
node->value = std::move(el);
split->children.push_back(node);
return;
}
current->value = std::move(el);
}
void remove(std::string_view key) {
_remove(root, key, 0);
}
bool _remove(Node &node, std::string_view key, uint64_t pos) {
if (pos == key.size()) {
if (!node.value)
return false;
node.value.reset();
return true;
}
Node *child = find_child(node, key[pos]);
if (!child)
return false;
const auto remaining = key.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
return false;
const auto child_pos = pos + common;
if (!_remove(*child, key, child_pos))
return false;
if (!child->value && child->children.empty()) {
auto it = std::find(
node.children.begin(),
node.children.end(),
child
);
node.children.erase(it);
delete child;
return true;
}
if (!child->value && child->children.size() == 1) {
Node *grandchild = child->children.front();
child->edge += grandchild->edge;
child->value = std::move(grandchild->value);
child->children = std::move(grandchild->children);
grandchild->children.clear();
delete grandchild;
}
return true;
}
std::vector<SearchResult> search(std::string_view prefix) {
std::vector<SearchResult> result;
Node *current = &root;
std::string key;
uint64_t pos = 0;
while (pos < prefix.size()) {
Node *child = find_child(*current, prefix[pos]);
if (!child)
return result;
const auto remaining = prefix.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common == 0)
return result;
if (common < child->edge.size()) {
if (common == remaining.size()) {
key += child->edge;
collect(*child, key, result);
return result;
}
return result;
}
key += child->edge;
pos += common;
current = child;
}
collect(*current, key, result);
return result;
}
static void collect(
Node &node,
std::string &key,
std::vector<SearchResult> &result
) {
if (node.value) {
if constexpr (std::is_void_v<T>) {
result.push_back(key);
} else {
result.emplace_back(key, *node.value);
}
}
for (auto *child : node.children) {
const auto old_size = key.size();
key += child->edge;
collect(*child, key, result);
key.resize(old_size);
}
}
uint64_t longest_match(std::string_view input) const {
const Node *current = &root;
uint64_t pos = 0;
uint64_t longest = 0;
if (current->value)
longest = 0;
while (pos < input.size()) {
const Node *child = find_child(*current, input[pos]);
if (!child)
break;
const auto remaining = input.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
break;
pos += common;
current = child;
if (current->value)
longest = pos;
}
return longest;
}
bool matches(std::string_view key) const
requires(std::is_void_v<T>)
{
Node *current = &root;
uint64_t pos = 0;
while (pos < key.size()) {
Node *child = find_child(*current, key[pos]);
if (!child)
return false;
const auto remaining = key.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
return false;
pos += common;
current = child;
}
return current->value.has_value();
}
std::optional<V> get(std::string_view key) const
requires(!std::is_void_v<T>)
{
const Node *current = &root;
uint64_t pos = 0;
while (pos < key.size()) {
const Node *child = find_child(*current, key[pos]);
if (!child)
return std::nullopt;
const auto remaining = key.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
return std::nullopt;
pos += common;
current = child;
}
if (!current->value)
return std::nullopt;
return *current->value;
}
V *get_ptr(std::string_view key) {
Node *current = &root;
uint64_t pos = 0;
while (pos < key.size()) {
Node *child = find_child(*current, key[pos]);
if (!child)
return nullptr;
const auto remaining = key.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
return nullptr;
pos += common;
current = child;
}
if (!current->value)
return nullptr;
return &*current->value;
}
const V *get_ptr(std::string_view key) const {
const Node *current = &root;
uint64_t pos = 0;
while (pos < key.size()) {
const Node *child = find_child(*current, key[pos]);
if (!child)
return nullptr;
const auto remaining = key.substr(pos);
const auto common = common_prefix(child->edge, remaining);
if (common != child->edge.size())
return nullptr;
pos += common;
current = child;
}
if (!current->value)
return nullptr;
return &*current->value;
}
bool equal_char(char a, char b) const {
if (case_sensitive)
return a == b;
return std::tolower((unsigned char)a) == std::tolower((unsigned char)b);
}
uint64_t common_prefix(
std::string_view a,
std::string_view b
) const {
const auto n = std::min(a.size(), b.size());
uint64_t i = 0;
while (i < n && equal_char(a[i], b[i]))
++i;
return i;
}
Node *find_child(const Node &node, char first) const {
for (auto *child : node.children)
if (equal_char(child->edge.front(), first))
return child;
return nullptr;
}
};
} // namespace bed::internal::trie
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::ui {
struct Token {
enum struct Type : uint8_t {
TempCurrent, // @
AddressSeperator, // ; ,
Address, // . $ % [ ] ^ ~
Offset, // +N -N + -
AddressRegex, // /re/ ?re?
AddressSymbol, // >s> <s< <s>
Number, // 10
Mark, // 'm
RubyFunction, // (func:arg)
RubyArg,
Function,
Any,
Shell,
Ruby,
File,
Regex,
Replacement,
Suffix
} type;
uint16_t start;
uint16_t end;
};
struct CommandIO {
std::string cmd;
uint16_t cursor;
std::string prompt;
uint16_t start;
uint16_t height;
uint16_t term_height;
uint16_t term_width;
BEd &bed;
CommandIO(BEd &);
std::pair<std::string, bool> run();
void redraw();
};
} // namespace bed::internal::ui
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::ui {
struct TextMode {
std::string cmd;
uint16_t cursor;
uint16_t start;
uint16_t height;
uint16_t term_height;
uint16_t term_width;
BEd &bed;
TextMode(BEd &);
std::pair<vase::Shard *, bool> run();
void grow(size_t required_height);
void redraw();
};
} // namespace bed::internal::ui
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include "pch.h"
namespace bed::internal::vase {
constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024;
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "../shard.h"
#include "pch.h"
#include "petal.h"
namespace bed::internal::vase {
struct LineIterator {
PetalIterator it;
const char *chunk;
uint64_t len;
Direction dir;
uint64_t current_offset = 0;
uint64_t last_line_offset = 0;
LineIterator(Shard *root, uint64_t line_num, Direction dir);
~LineIterator() = default;
bool next(std::string *line);
uint64_t byte_offset();
};
struct Iterator {
Shard *root;
std::string line;
Iterator(Shard *root, uint64_t line_num, Direction dir)
: root(root), it(root, line_num, dir) {
Shard::retain(root);
}
~Iterator() {
Shard::release(root);
}
Iterator(const Iterator &other)
: root(other.root), it(other.it) {
Shard::retain(root);
}
Iterator &operator=(const Iterator &other) {
if (this != &other) {
Shard::retain(other.root);
Shard::release(root);
root = other.root;
it = other.it;
}
return *this;
}
Iterator(Iterator &&other) noexcept
: root(other.root), it(std::move(other.it)) {
other.root = nullptr;
}
Iterator &operator=(Iterator &&other) noexcept {
if (this != &other) {
Shard::release(root);
root = other.root;
it = std::move(other.it);
other.root = nullptr;
}
return *this;
}
bool next() {
return it.next(&line);
}
private:
LineIterator it;
};
} // namespace bed::internal::vase
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "../shard.h"
#include "internal/generic.h"
#include "pch.h"
namespace bed::internal::vase {
struct PetalIterator {
Direction dir;
Shard *root = nullptr;
std::vector<Shard *> stack;
Petal *petal = nullptr;
uint64_t global_offset = 0;
uint64_t last_offset = 0;
uint64_t petal_offset = 0;
uint64_t global_line = 0;
PetalIterator(Shard *r, Direction dir);
~PetalIterator() = default;
void seek_offset(uint64_t offset);
void seek_line(uint64_t offset);
bool next(const char **data, uint64_t *out_len);
uint64_t byte_offset();
private:
Petal *_next(uint64_t *offset);
};
} // namespace bed::internal::vase
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include "constants.h"
#include "pch.h"
#include "storage/original.h"
#include "storage/storage.h"
namespace bed::internal::vase {
struct Shard {
enum struct Kind : uint8_t {
Branch,
Petal
} kind;
uint8_t height;
std::atomic_uint32_t refs;
uint64_t length;
uint64_t lines;
Shard(Kind kind, uint64_t length, uint64_t lines, uint8_t height)
: kind(kind), height(height), refs(1), length(length), lines(lines) {};
static void retain(Shard *n);
static void release(Shard *n);
static Shard *from_file(const std::filesystem::path &path, bool posix_ending);
static Shard *from_string(const char *data, uint64_t len, bool posix_ending);
static Shard *from_command(const char *cmd, bool posix_ending);
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
static Shard *concat(Shard *a, Shard *b);
static Shard *merge_leaves(Shard *a, Shard *b);
static Shard *append(Shard *root, Shard *leaf);
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
};
struct Branch : Shard {
Shard *left;
Shard *right;
Branch(Shard *l, Shard *r)
: Shard(
Kind::Branch,
l->length + r->length,
l->lines + r->lines,
1 + std::max(l->height, r->height)
),
left(l), right(r) {
retain(left);
retain(right);
};
};
struct Petal : Shard {
Storage *source;
uint64_t pos;
Petal(uint64_t length, uint64_t lines, Storage *source, uint64_t pos)
: Shard(Kind::Petal, length, lines, 1),
source(source), pos(pos) {
source->retain();
};
};
} // namespace bed::internal::vase
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "../constants.h"
#include "pch.h"
#include "storage.h"
namespace bed::internal::vase {
struct AppendStorage : Storage {
char *buf = nullptr;
uint64_t allocated_capacity = 0;
uint64_t current_size = 0;
int fd = -1;
AppendStorage(std::filesystem::path base_dir);
~AppendStorage();
uint64_t append(const char c);
uint64_t append(const char *text, uint64_t len);
const char *read(uint64_t pos) override;
uint64_t length() override;
void retain() override {}
void release() override {}
private:
void grow(uint64_t len);
};
} // namespace bed::internal::vase
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "pch.h"
#include "storage.h"
namespace bed::internal::vase {
struct OriginalStorage : Storage {
std::atomic_uint64_t refs{0};
const char *buf = nullptr;
uint64_t len = 0;
int fd = -1;
OriginalStorage(std::filesystem::path base_dir);
~OriginalStorage();
void initialize();
const char *read(uint64_t pos) override;
uint64_t length() override;
void retain() override {
refs++;
}
void release() override {
if (--refs > 0)
return;
delete this;
}
};
} // namespace bed::internal::vase
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "pch.h"
namespace bed::internal::vase {
struct Storage {
virtual const char *read(uint64_t pos) = 0;
virtual uint64_t length() = 0;
virtual ~Storage() = default;
virtual void retain() = 0;
virtual void release() = 0;
};
} // namespace bed::internal::vase
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include "constants.h"
#include "definitions.h"
#include "iterators/line.h"
#include "pch.h"
#include "shard.h"
#include "storage/append.h"
#include "storage/original.h"
namespace bed::internal::vase {
struct Point {
uint64_t row;
uint64_t col;
};
struct Range {
Point start;
Point end;
};
struct RegexGroup {
uint64_t start{UINT64_MAX};
uint64_t end{UINT64_MAX};
};
struct RegexMatch {
uint64_t start;
uint64_t end;
RegexGroup groups[9]{};
};
struct ReplacePart {
enum struct PartType {
FullMatch,
CaptureGroup,
Constant
} type;
std::variant<uint8_t, Shard *> value;
};
uint64_t offset_of(Shard *root, uint64_t line);
uint64_t offset_of(Shard *root, Point point);
Point point_of(Shard *root, uint64_t offset);
std::string to_string(Shard *root);
std::string to_string(Shard *root, Range range);
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
Shard *erase(Shard *root, uint64_t start, uint64_t end);
Shard *join(Shard *root, uint64_t start, uint64_t end);
Shard *copy(Shard *root, uint64_t start, uint64_t end);
void write_file(std::filesystem::path path, Shard *text);
void write_command(const char *cmd, Shard *text);
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start);
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start);
Shard *substitute(
AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options,
const std::function<void(
uint64_t line, uint64_t old_lines, uint64_t new_lines
)> &on_edit = nullptr
);
// internal
void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len);
Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key);
Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len);
Shard *erase(Shard *root, Range range);
Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len);
std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s);
std::vector<RegexMatch> _regex_search(
Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
);
} // namespace bed::internal::vase
-38
View File
@@ -1,38 +0,0 @@
#pragma once
#include "pch.h"
inline int read_file(const char *path, char **text, uint32_t *len) {
FILE *f = fopen(path, "rb");
if (!f)
return 0;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size < 0 || size > UINT32_MAX) {
fclose(f);
return 0;
}
*len = (uint32_t)size;
*text = (char *)malloc(*len + 1);
if (!*text) {
fclose(f);
return 0;
}
size_t read = fread(*text, 1, *len, f);
fclose(f);
if (read != *len) {
free(*text);
*text = nullptr;
*len = 0;
return 0;
}
return 1;
}
+4 -1
View File
@@ -29,20 +29,23 @@ extern "C" {
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <functional> #include <functional>
#include <iostream>
#include <limits.h> #include <limits.h>
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <poll.h> #include <poll.h>
#include <pty.h>
#include <queue> #include <queue>
#include <set> #include <set>
#include <shared_mutex> #include <shared_mutex>
#include <signal.h> #include <signal.h>
#include <stack> #include <stack>
#include <stdexcept>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <termios.h> #include <termios.h>
-3
View File
@@ -1,3 +0,0 @@
#pragma once
#include "pch.h"
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include "../constants.h"
#include "buffer.h"
#include "pch.h"
struct AppendBuffer : Buffer {
using TChunk = char[APPEND_CHUNK_SIZE];
std::vector<TChunk *> buf;
TChunk *t_current;
uint32_t current_offset{0};
uint32_t t_offset{0};
AppendBuffer();
~AppendBuffer();
uint32_t key(char c);
uint32_t append(const char *text, uint32_t length);
const char *read(uint32_t pos, uint32_t *out_len);
inline uint32_t length();
};
-9
View File
@@ -1,9 +0,0 @@
#pragma once
#include "pch.h"
struct Buffer {
virtual const char *read(uint32_t pos, uint32_t *out_len) = 0;
virtual inline uint32_t length() = 0;
virtual ~Buffer() = default;
};
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "buffer.h"
#include "pch.h"
struct OriginalBuffer : Buffer {
char *buf;
uint32_t len;
// Add 2 modes here, normal and lazy, lazy copies the file to a safe location and uses mmap.
OriginalBuffer(std::string path);
~OriginalBuffer();
const char *read(uint32_t pos, uint32_t *out_len);
uint32_t length();
};
-11
View File
@@ -1,11 +0,0 @@
#pragma once
#include "pch.h"
constexpr uint32_t APPEND_CHUNK_SIZE = 64 * 1024;
constexpr uint32_t PETAL_SIZE_MAX = 32 * 1024;
static_assert(
APPEND_CHUNK_SIZE > PETAL_SIZE_MAX,
"PETAL_SIZE_MAX must be smaller than APPEND_CHUNK_SIZE"
);
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include "../shard.h"
#include "pch.h"
enum struct Direction : uint8_t {
Forward,
Backward
};
struct ChunkIterator {
Direction dir;
Shard *root = nullptr;
std::vector<Shard *> stack;
Petal *petal = nullptr;
uint32_t petal_offset = 0;
uint32_t global_offset = 0;
ChunkIterator(Shard *r, Direction dir);
~ChunkIterator();
void seek_offset(uint32_t offset);
void seek_line(uint32_t offset);
bool next(const char **data, uint32_t *out_len);
uint32_t byte_offset();
};
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "../shard.h"
#include "chunk.h"
#include "pch.h"
struct LineIterator {
ChunkIterator it;
Direction dir;
const char *chunk;
uint32_t len;
LineIterator(Shard *r, uint32_t start_line, Direction dir);
~LineIterator() = default;
bool next(std::string &line);
uint32_t byte_offset();
};
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include "pch.h"
#include "vase/shard.h"
struct RegexGroup {
uint32_t start{UINT32_MAX};
uint32_t end{UINT32_MAX};
};
struct RegexMatch {
uint32_t start;
uint32_t end;
RegexGroup groups[9]{};
};
std::vector<RegexMatch> regex_search(
Shard *root, std::string_view pattern_str,
uint32_t start_offset, uint32_t end_offset,
std::string_view options
);
-63
View File
@@ -1,63 +0,0 @@
#pragma once
#include "buffer/buffer.h"
#include "buffer/original.h"
#include "constants.h"
#include "pch.h"
#include "utils/utils.h"
struct Shard {
enum struct ShardKind : uint8_t {
Branch,
Petal
} kind;
uint8_t height;
uint32_t length;
uint32_t lines;
std::atomic_uint32_t refs;
Shard(ShardKind kind, uint32_t length, uint32_t lines, uint8_t height)
: kind(kind), height(height), length(length), lines(lines), refs(1) {};
static void retain(Shard *n);
static void release(Shard *n);
};
struct Branch : Shard {
Shard *left;
Shard *right;
Branch(Shard *l, Shard *r)
: Shard(
ShardKind::Branch,
l->length + r->length,
l->lines + r->lines,
1 + std::max(l->height, r->height)
),
left(l), right(r) {
retain(left);
retain(right);
};
};
struct Petal : Shard {
Buffer *source;
uint32_t pos;
Petal(uint32_t length, uint32_t lines, Buffer *source, uint32_t pos)
: Shard(ShardKind::Petal, length, lines, 1),
source(source), pos(pos) {};
};
Shard *create_shards(Buffer *o);
std::pair<Shard *, Shard *> split_shard(Shard *n, uint32_t offset);
Shard *concat_shard(Shard *left, Shard *right);
Shard *merge(Shard *a, Shard *b);
Shard *merge_leaves(Shard *a, Shard *b);
Shard *append_leaf(Shard *root, Shard *leaf);
Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi);
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include "buffer/append.h"
#include "buffer/original.h"
#include "constants.h"
#include "iterators/line.h"
#include "pch.h"
#include "search.h"
#include "shard.h"
#include "utils/utils.h"
struct Point {
uint32_t row;
uint32_t col;
};
struct Range {
Point start;
Point end;
};
struct Vase {
OriginalBuffer original;
AppendBuffer append;
Shard *root;
Vase(std::string path);
~Vase();
uint32_t length();
std::string to_string();
Point resolve_column(uint32_t line, uint32_t column);
LineIterator iterate(uint32_t line);
void insert(Point *point, char key);
void insert(Point *point, const char *data, uint32_t len);
void erase(Point *point, int64_t amount);
void erase(Range range);
void replace(Range range, const char *data, uint32_t len);
void move_clusters(Point *point, int64_t amount);
void move_lines(Point *point, int64_t amount);
void clamp(Point *point);
void regex_search_replace(
std::string_view pattern,
Point start, Point end,
std::string_view replace, std::string_view options
);
bool undo();
bool redo();
void snapshot();
void prune_history(uint32_t n);
private:
std::vector<Shard *> history;
uint32_t history_top;
uint32_t offset_of(Point point);
static void flatten(Shard *s, std::string &out);
void _insert(Point *point, const char *data, uint32_t len);
};
+60
View File
@@ -0,0 +1,60 @@
=begin
die
=end
module Outer
class User
def initialize(name)
@name = name
if @name
while @name
begin
case @name
when "admin"
puts "admin"
else
puts "user"
end
end
end
else
unless name
puts "missing"
end
end
end
def each_item(items)
for item in items
if item
die!
puts "ha"
puts "ha"
item.do_something do
puts "ha"
puts "ha"
puts item
puts "ha"
puts "ha"
end
puts "ha"
puts "ha"
end
end
end
end
module Inner
def run(value)
until value == 0
case value
when 1
value -= 1
else
value -= 2
end
end
end
end
end
+343
View File
@@ -0,0 +1,343 @@
#!/usr/bin/env ruby
# Unicode / Emoji / CJK stress-test Ruby file
# Purpose: Test syntax highlighting + width calculation in your editor
# ---------------------------------------------------------------
# Mixed-width CJKssssssssssssssss LoadErssssssssssssssssssssssss
cjk_samples = [
'漢字テスト',
'測試中文字串',
'한국어 테스트',
'ひらがなカタカナ混合'
]
# a hex color: #FFFFFF shouldn't hl here: hsl(147rad, 50%, 47%) as it is not css-style file
0x603010 # another hex color
# Ruby regex with unicode
$unicode_regex_multiline = /[一-龯ぁ-ん12288ァ
\-ヶー
s wow
々〆〤]/
UNICORE = /
s
{#{ss}}
\C-s\u{10}
/
UNINITCORE = %(
{{#{}}}
test = "A:\x41 B:\101 C:\u0043 D:\u{44 45} NUL:\0 DEL:\c? CTRL_A:\cA META_X:\M-x CTRL_META_X:\C-\M-x MIX:\C-\M-z N:\N{UNICODE NAME}"
)
# Unicode identifiers (valid in Ruby)
= 0x5_4eddaee
π = 0.314_159e+2, ?\u0234, "\,", ?\x0A, 's', true, false, 0
= -> { "こんに \n ちは" }
arr = []
not_arr = NotABuiltin.new
raise NameError or SystemExit or CustomError or Errno or ErrorNotAtAll
# Method using unicode variable names
def math_test
puts "π * 2 = #{π * 2}"
end
# Iterate through CJK samples
cjk_samples.each_with_index do |str, idx:|
puts %! CJK[#{idx}] => #{str} (len=#{str.length})\! !
symbol = :"
a
"
sym2 = :hello
end
# Test emoji width behaviors
puts "Emoji count: #{emojis.length}"
# Multi-line string with unicode
multi = <<~BASH
# Function recursion demo
factorial() {
local n="$1"
if ((n <= 1)); then
echo 1
else\ns
local prev
prev=$(factorial $((n - 1)))
echo $((n * prev))
before #{ interpol
# {' '}
# comment should be fine heres s
$a / $-s + 0xFF
}s#{' '}
x
a after
fi
} #{s}
log INFO "factorial(5) = $(factorial 5)"
BASH
puts multi
# Arrays mixing everything
mixed = [
'🐍 Ruby + Python? sacrilege! 🐍',
'日本語とEnglishと🔧mix',
'Spacing test →→→→→→→',
'Zero-width joiner test: 👨‍👩‍👧‍👦 family emoji'
]
two_docs = <<~DOC1, <<~DOC2
stuff for doc2
rdvajehvbaejbfh
DOC1
stuff for doc 2 with #{!interpolation} and more
DOC2
p = 0 << 22 # not a heredoc
mixed.each { |m| puts m }
# Unicode in comments — highlight me!
# コメント:エディタのハイライトを確認します✨
# Emojis should not break formatting: 🦀🦊🐱‍👤🤖
# Dummy Ruby logic
5.times do |i|
puts "Loop #{i}: 🌟 #{cjk_samples[i % cjk_samples.size]}"
end
# String escape sequences + unicode
escaped = "Line1\nLine2\tTabbed 😀"
puts escaped
p = 0 << 2
# Frozen string literal test
# frozen_string_literal: true
const_str = '定数文字列🔒'.freeze
puts const_str
# End marker
puts '--- END OF UNICODE TEST FILE ---'
# Ruby syntax highlighting test
# This is a multi-line comment.
# It spans multiple lines.
# Good for testing highlighting.
#
# This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped line test, This is a wrapped linetest,
#
# Constants
PI = 3.14159
MAX_ITER = 5
# Module
module Utilities
def self.random_greeting
%w[Hello Hi Hey Hola Bonjour Merhaba].sample
end
def self.factorial(n)
return 1 if n <= 1
n * factorial(n - 1)
end
end
# Class
class TestObject
attr_accessor :name, :value
def initialize(name, value)
@name = name
@value = value
end
def display
puts "#{@name}: #{@value}"
end
private
def double_value
@value * 2
end
end
# Inheritance
class SpecialObject < TestObject
def triple_value
@value * 3
end
end
# Lambda
adder = ->(x, y) { x + y }
# Array and hash
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
hash = { a: 1, b: 2, c: 3 }
# Iteration
numbers.each do |n|
puts "Number: #{n}"
end
# Hash iteration
hash.each do |key, value|
puts "#{key} => #{value}"
end
# Conditional
numbers.each do |n|
if n.even?
puts "#{n} is even"
else
puts "#{n} is odd"
end
end
# Method definition
def greet_person(name)
puts "#{Utilities.random_greeting}, #{name}!"
return true if name == 'harry'
's'
end
h = a / a
# Calling methods
greet_person('Alice')
greet_person('Bob')
# Loops
i = 0
while i < 5
puts "Loop iteration #{i}"
i += 1
end
for j in 1..3
puts "For loop #{j}"
end
# Begin-rescue-ensure
begin
risky = 10 / 2
puts "Risky operation succeeded: #{risky}"
rescue ZeroDivisionError => e
puts "Caught an error: #{e}"
ensure
puts 'This runs no matter what'
end
# Arrays of objects
objs = []
5.times do |k|
objs << TestObject.new("Obj#{k}", k)
end
objs.each(&:display)
# Nested arrays
nested = [[1, 2], [3, 4], [5, 6]]
nested.each do |arr|
arr.each { |x| print "#{x} " }
puts
end
# Case statement
numbers.each do |n|
case n
when 1..3
puts "#{n} is small"
when 4..7
puts "#{n} is medium"
else
puts "#{n} is large"
end
end
# Using factorial
(0..5).each do |n|
puts "Factorial of #{n} is #{Utilities.factorial(n)}"
end
# Special objects
so = SpecialObject.new('Special', 10)
puts "Double: #{so.double_value}, Triple: #{so.triple_value}"
# String interpolation and formatting
puts "PI is approximately #{PI.round(2)}"
# Multi-line strings
multi_line = <<~TEXT
k kmW ;
This is a multi-line string.
It spans multiple lines.
Gossn sssmss
ddsss
od for testing highlighting.
TEXT
puts multi_line
# Symbols and strings
sym = :my_symbol == __dir__
str = 'my string'
puts "Symbol: #{sym}, String: #{str}"
# Random numbers
rand_nums = Array.new(5) { rand(100) }
puts "Random numbers: #{rand_nums.join(', ')}"
# More loops
rand_nums.each_with_index do |num, idx|
puts "Index #{idx} has number #{num}"
end
# Ternary operator
rand_nums.each do |num|
puts num.even? ? "#{num} is even" : "#{num} is odd"
end
# Block with yield
def wrapper
puts 'Before block'
yield if block_given?
puts 'After block'
end
# ss
wrapper { puts 'Inside block' }
# Sorting
sorted = rand_nums.sort
puts "Sorted: #{sorted.join(', ')}"
# Regex
sample_text = 'The quick brown fox jumps over the lazy dog'
puts "Match 'fox'?" if sample_text =~ /fox/
# End of test script
puts 'Ruby syntax highlighting test complete.'
__END__
Anything here should be ignored >><<
{{{}}}[[[]]](((000)))
+213
View File
@@ -0,0 +1,213 @@
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed {
BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
: theme(internal::theme::Theme::default_theme()), io(io) {
internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this);
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
}
void BEd::run() {
while (true) {
internal::ui::CommandIO command(*this);
auto [cmd, eof] = command.run();
try {
handle(cmd, eof);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
}
void BEd::handle(std::string_view cmd, bool eof) {
if (eof) {
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
return;
}
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) {
marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true;
}
internal::buffer::Address address;
switch (c.function->address_kind) {
case internal::functions::Function::AddressKind::None: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = a->buffername;
} break;
case internal::functions::Function::AddressKind::Line: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = *a;
} break;
case internal::functions::Function::AddressKind::Range: {
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_range(*this, vec);
if (!a.has_value())
a = internal::buffer::Range(current(), current());
}
address = *a;
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = internal::buffer::Range(current(), current());
}
if (!c.function->accept_zero) {
if (std::holds_alternative<internal::buffer::Line>(address)) {
if (std::get<internal::buffer::Line>(address).number == 0)
throw ed_error("Line number can't be zero.");
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
auto r = std::get<internal::buffer::Range>(address);
if (r.start == 0 || r.end == 0)
throw ed_error("Line number can't be zero.");
}
}
internal::vase::Shard *text = nullptr;
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
internal::ui::TextMode tm(*this);
auto [a, b] = tm.run();
if (!b)
text = a;
}
if (c.function->handle)
c.function->handle(*this, address, text, c.argument, nullptr);
if (c.suffix)
c.suffix->handle(*this);
if (c.temp_address)
temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second;
if (buf->waste()) {
delete buf;
it = buffers.erase(it);
} else {
++it;
}
}
}
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
if (name.empty())
throw ed_error("can't have empty buffer name");
auto it = buffers.find(name);
if (it != buffers.end())
return *it->second;
auto *buf = new internal::buffer::GenericBuffer(name);
buffers.emplace(name, buf);
return *buf;
}
internal::buffer::Line &BEd::current() {
return marks.get(250 + temporary_current);
}
internal::buffer::Range &BEd::prev() {
if (temporary_current)
return prev_2;
else
return prev_1;
}
void BEd::mark(uint8_t m, internal::buffer::Line line) {
marks.get(m) = line;
}
bool BEd::escape_command(std::string &cmd, std::string_view filename) {
bool modified = false;
if (cmd == "!") {
cmd = last_shell;
modified = true;
}
last_shell = cmd;
for (size_t i = 0; i < cmd.size();) {
if (cmd[i] == '\\') {
if (i + 1 >= cmd.size())
break;
cmd.erase(i++, 1);
continue;
}
if (cmd[i] == '%') {
cmd.erase(i, 1);
cmd.insert(i, filename);
i += filename.size();
modified = true;
continue;
}
i++;
}
return modified;
}
} // namespace bed
+270
View File
@@ -0,0 +1,270 @@
#include "bed.h"
#include "internal/buffer/buffer.h"
namespace bed::internal::buffer {
GenericBuffer::~GenericBuffer() {
vase::Shard::release(root);
}
bool GenericBuffer::waste() {
return save_path.empty()
&& root == nullptr;
}
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) {
if (lines())
ctx.marks.erase(name, 1, lines());
vase::Shard::release(root);
vase::Shard::retain(text);
root = text;
state = buffer::GenericBuffer::Unmodified;
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
}
void GenericBuffer::set_filename(std::filesystem::path path) {
save_path = path;
}
std::filesystem::path GenericBuffer::filename() {
return save_path;
};
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1;
root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
if (parser)
parser->insert(root, ctx.prev().start, ctx.prev().end);
state = Modified;
}
void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
root = vase::erase(root, start_line, end_line);
ctx.prev().buffername = name;
ctx.prev().start = std::min(start_line, lines());
ctx.prev().end = std::min(start_line, lines());
ctx.marks.erase(name, start_line, end_line - start_line + 1);
if (parser)
parser->erase(root, start_line, end_line - start_line + 1);
state = Modified;
}
void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
root = vase::join(root, start_line, end_line);
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line;
ctx.marks.collapse(name, start_line, end_line - start_line);
if (parser)
parser->erase(root, start_line, end_line - start_line);
state = Modified;
}
void GenericBuffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
if (parser)
parser->begin_edit();
root = vase::substitute(
&ctx.append,
root,
regex,
start_line,
end_line,
replacement,
options,
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
if (old_lines) {
ctx.marks.erase(name, line, old_lines);
if (parser)
parser->erase(line, old_lines);
}
if (new_lines) {
ctx.marks.insert(name, line, line + new_lines - 1);
if (parser)
parser->insert(line, line + new_lines - 1);
}
}
);
if (parser)
parser->end_edit(root);
ctx.prev().buffername = name;
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
+185
View File
@@ -0,0 +1,185 @@
#include "bed.h"
#include "internal/buffer/buffer.h"
namespace bed::internal::buffer {
ClipBuffer::~ClipBuffer() {}
bool ClipBuffer::waste() {
return false;
}
uint64_t ClipBuffer::lines() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
uint64_t lines = s ? s->lines + 1 : 0;
vase::Shard::release(s);
return lines;
}
uint64_t ClipBuffer::bytes() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
uint64_t length = s ? s->length + 1 : 0;
vase::Shard::release(s);
return length;
}
void ClipBuffer::clip_write(vase::Shard *text) {
FILE *pipe = popen("xclip -selection clipboard -i", "w");
if (!pipe)
throw ed_error("can't access clipboard");
auto s = vase::to_string(text);
fwrite(s.data(), 1, s.length(), pipe);
if (pclose(pipe) != 0)
throw ed_error("can't write clipboard");
}
void ClipBuffer::load(BEd &ctx, vase::Shard *text) {
clip_write(text);
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
}
void ClipBuffer::set_filename(std::filesystem::path) {}
std::filesystem::path ClipBuffer::filename() {
return "";
};
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::insert(&ctx.append, s, text, line);
clip_write(s);
vase::Shard::release(s);
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
}
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::erase(s, start_line, end_line);
clip_write(s);
ctx.prev().buffername = name;
ctx.prev().start = std::min(start_line, s ? s->lines + 1 : 0);
ctx.prev().end = std::min(start_line, s ? s->lines + 1 : 0);
vase::Shard::release(s);
ctx.marks.erase(name, start_line, end_line - start_line + 1);
}
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::join(s, start_line, end_line);
clip_write(s);
vase::Shard::release(s);
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line;
ctx.marks.collapse(name, start_line, end_line - start_line);
}
void ClipBuffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
s = vase::substitute(
&ctx.append,
s,
regex,
start_line,
end_line,
replacement,
options,
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
if (old_lines)
ctx.marks.erase(name, line, old_lines);
if (new_lines)
ctx.marks.insert(name, line, line + new_lines - 1);
}
);
clip_write(s);
vase::Shard::release(s);
ctx.prev().buffername = name;
}
vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
vase::Shard *o = vase::copy(s, start_line, end_line);
vase::Shard::release(s);
return o;
}
uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
uint64_t line = vase::find_next(s, pattern, start);
vase::Shard::release(s);
return line;
}
uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
uint64_t line = vase::find_prev(s, pattern, start);
vase::Shard::release(s);
return line;
}
uint64_t ClipBuffer::next_closing(uint64_t start) {
start += 10;
uint64_t line = lines();
if (start > line)
return line;
return start;
}
uint64_t ClipBuffer::prev_closing(uint64_t start) {
if (start > 10)
return start - 10;
return 0;
}
void ClipBuffer::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;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line);
vase::Shard::release(s);
}
void ClipBuffer::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;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
vase::Shard::release(s);
}
void ClipBuffer::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;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line));
vase::Shard::release(s);
}
} // namespace bed::internal::buffer
+58
View File
@@ -0,0 +1,58 @@
#include "bed.h"
#include "internal/functions/functions.h"
#include "internal/functions/suffixes.h"
namespace bed::internal::functions {
void Function::register_extented(BEd &ctx) {
ctx.functions.insert(
"cd",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Change directory.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto path = std::get<std::string>(arg_);
const auto first = path.find_first_not_of(" \t");
const auto last = path.find_last_not_of(" \t");
if (first == std::string::npos)
path.clear();
else
path = path.substr(first, last - first + 1);
if (path.empty())
path = getenv("HOME");
if (path == "~")
path = getenv("HOME");
if (path.starts_with("~/")) {
const char *home = getenv("HOME");
if (home)
path = std::string(home) + path.substr(1);
}
if (chdir(path.c_str()) == -1)
throw ed_error("Can't change directory.");
},
}
);
ctx.functions.insert(
"pwd",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print directory.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
char cwd[PATH_MAX];
if (!getcwd(cwd, sizeof(cwd)))
throw ed_error("Can't determine current directory.");
ctx.io.write_line(cwd);
},
}
);
}
} // namespace bed::internal::functions
+657
View File
@@ -0,0 +1,657 @@
#include "bed.h"
#include "internal/functions/functions.h"
#include "internal/functions/suffixes.h"
namespace bed::internal::functions {
void Suffix::register_suffixes(BEd &ctx) {
ctx.suffixes['p' - 'a'] = Suffix{
.desc = "Prints current line.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
}
};
ctx.suffixes['n' - 'a'] = Suffix{
.desc = "Prints current line with line number.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).number_print(ctx, addr.number, addr.number);
}
};
ctx.suffixes['l' - 'a'] = Suffix{
.desc = "Prints current line unambiguously.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).list_print(ctx, addr.number, addr.number);
}
};
}
void Function::register_posix(BEd &ctx) {
ctx.functions.insert(
"a",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Append text to a line.",
.default_address = ".",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
);
ctx.functions.insert(
"c",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Change set of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).append(ctx, text, addr.start - 1);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
);
ctx.functions.insert(
"d",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Delete set of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
ctx.functions.insert(
"e",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Try load a file into the current buffer.",
.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 &buf = ctx.buffer(addr);
if (buf.state == buffer::Buffer::Modified) {
buf.state = buffer::Buffer::Warned;
throw ed_error("Buffer modified.");
}
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.load(ctx, s);
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"E",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Load a file into the current buffer.",
.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 &buf = ctx.buffer(addr);
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.load(ctx, s);
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"f",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Set a save path.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return;
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (std::holds_alternative<std::filesystem::path>(arg))
buf.set_filename(std::get<std::filesystem::path>(arg));
else if (std::holds_alternative<ShellArg>(arg))
throw ed_error("Can't save shell command as save path.");
if (buf.filename().empty())
throw ed_error("Filename needed.");
ctx.io.write_line(buf.filename().string());
}
}
);
ctx.functions.insert(
"h",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print last help message",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.io.write_line(ctx.last_help);
}
}
);
ctx.functions.insert(
"H",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle help mode.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.help_mode = !ctx.help_mode;
if (ctx.help_mode)
ctx.io.write_line(ctx.last_help);
}
}
);
ctx.functions.insert(
"i",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Insert text before a line.",
.default_address = ".",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number)
addr.number--;
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
);
ctx.functions.insert(
"j",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Join a set of lines.",
.default_address = ".,.+1",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
ctx.functions.insert(
"k",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::Mark,
.input_mode = Function::InputMode::None,
.desc = "Mark a line.",
.default_address = ".",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
}
}
);
ctx.functions.insert(
"l",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "List (print unambiguous) range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"m",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Move a range of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
if (arg.buffername == addr.buffername
&& addr.start <= arg.number
&& addr.end < arg.number)
throw ed_error("Can't move lines within themselves.");
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
ctx.mark(252, arg);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
arg = ctx.marks.get(252);
try {
if (arg.number == UINT64_MAX)
throw ed_error("Unexpected error when moving lines.");
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
vase::Shard::release(text);
} catch (...) {
if (!addr.start) {
vase::Shard::release(text);
throw;
}
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
ctx.functions.insert(
"n",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range with line numbers",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"p",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"P",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle prompt.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.prompt_mode = !ctx.prompt_mode;
if (ctx.prompt_mode && !ctx.prompt) {
ctx.prompt = [](BEd &) { return "*"; };
}
}
}
);
ctx.functions.insert(
"q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::string modified_buffers;
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
modified_buffers.append(name + ", ");
}
}
if (!modified_buffers.size())
throw fatal_error("Quitting", 0);
modified_buffers.erase(modified_buffers.size() - 2);
throw ed_error("Buffer(s) " + modified_buffers + " modified.");
}
}
);
ctx.functions.insert(
"Q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Force quit.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
throw fatal_error("Force Quitting", 0);
}
}
);
ctx.functions.insert(
"r",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Read from file into buffer.",
.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::Line>(addr_);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
if (buf.filename().empty())
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.append(ctx, s, addr.number);
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
ctx.current() = {addr.buffername, addr.number + (s ? s->lines + 1 : 0)};
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
}
}
);
ctx.functions.insert(
"s",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Regex,
.input_mode = Function::InputMode::None,
.desc = "Substitute regex.",
.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<RegexArg>(arg_);
if (arg.expression == "")
arg.expression = ctx.last_regex;
else
ctx.last_regex = arg.expression;
if (arg.replacement == "%")
arg.replacement = ctx.last_replacement;
else
ctx.last_replacement = arg.replacement;
ctx.buffer(addr.buffername)
.substitute(
ctx,
addr.start,
addr.end,
arg.expression,
arg.replacement,
arg.options
);
}
}
);
ctx.functions.insert(
"t",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Copy a range of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
try {
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
vase::Shard::release(text);
} catch (...) {
if (!addr.start) {
vase::Shard::release(text);
throw;
}
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
ctx.functions.insert(
"w",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Write buffer to disk.",
.default_address = "0,$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *text;
if (!addr.start && !addr.end) {
text = nullptr;
} else {
if (!addr.start && addr.end)
addr.start = 1;
text = buf.copy(addr.start, addr.end);
}
try {
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
vase::write_file(path, text);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
vase::write_command(cmd.c_str(), text);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
vase::write_file(path, text);
};
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
}
}
);
ctx.functions.insert(
"=",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print line numbers",
.default_address = "$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Range>(addr_);
if (addr.start == addr.end)
ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start));
else
ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end));
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"!",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Shell,
.input_mode = Function::InputMode::None,
.desc = "Run a shell command.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto filename = ctx.buffer(addr).filename();
auto &arg = std::get<ShellArg>(arg_);
auto cmd = arg.cmd;
if (ctx.escape_command(cmd, filename.string()))
ctx.io.write(cmd + "\n");
ctx.io.run_pty(cmd);
ctx.io.write("!\n");
},
}
);
ctx.no_op = Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Prints a line and jumps to it.",
.default_address = ".+1",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number != 0)
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
ctx.current() = addr;
}
};
ctx.eof_op = Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::string modified_buffers;
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
modified_buffers.append(name + ", ");
}
}
if (!modified_buffers.size())
throw fatal_error("Quitting", 0);
modified_buffers.erase(modified_buffers.size() - 2);
throw ed_error("Buffer(s) " + modified_buffers + " modified.");
}
};
}
} // namespace bed::internal::functions
+278
View File
@@ -0,0 +1,278 @@
#include "internal/io/io.h"
namespace bed::internal::io {
KeyEvent::ReadResult IO::get_next_byte(char &out) {
if (!input_queue.empty()) {
out = input_queue.front();
input_queue.pop_front();
return KeyEvent::ReadResult::SUCCESS;
}
if (resized.load())
return KeyEvent::ReadResult::RESIZE;
ssize_t n = read(STDIN_FILENO, &out, 1);
if (n == 1)
return KeyEvent::ReadResult::SUCCESS;
if (n == -1 && errno == EINTR && resized.load())
return KeyEvent::ReadResult::RESIZE;
if (n == -1 && errno == EINTR)
return get_next_byte(out);
return KeyEvent::ReadResult::EOF_;
}
void IO::enqueue_bytes(const std::string &bytes) {
input_queue.insert(input_queue.begin(), bytes.begin(), bytes.end());
}
int IO::utf8_seq_len(uint8_t byte) {
if ((byte & 0x80) == 0x00)
return 1;
if ((byte & 0xE0) == 0xC0)
return 2;
if ((byte & 0xF0) == 0xE0)
return 3;
if ((byte & 0xF8) == 0xF0)
return 4;
return 1;
}
KeyEvent::ReadResult IO::read_next_unit(std::string &out) {
out.clear();
char header;
KeyEvent::ReadResult res = get_next_byte(header);
if (res != KeyEvent::ReadResult::SUCCESS)
return res;
if (header == '\x1b') {
out.push_back(header);
char c;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
return res;
out.push_back(c);
if (c != '[')
return KeyEvent::ReadResult::SUCCESS;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
return res;
out.push_back(c);
if (c == 'M') {
for (int i = 0; i < 3; ++i) {
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
return res;
out.push_back(c);
}
return KeyEvent::ReadResult::SUCCESS;
}
while ((uint8_t)c < 0x40 || (uint8_t)c > 0x7E) {
if (out.size() >= 32)
break;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
return res;
out.push_back(c);
}
return KeyEvent::ReadResult::SUCCESS;
}
int seq_len = utf8_seq_len((uint8_t)header);
out.push_back(header);
if (seq_len == 1)
return KeyEvent::ReadResult::SUCCESS;
for (int i = 1; i < seq_len; i++) {
char c;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) {
enqueue_bytes(out);
out.clear();
return res;
}
out.push_back(c);
}
uint32_t prev_cp, cur_cp;
grapheme_decode_utf8(out.data(), out.size(), &prev_cp);
uint16_t state = 0;
while (true) {
char next_header;
if ((res = get_next_byte(next_header)) != KeyEvent::ReadResult::SUCCESS)
break;
int next_len = utf8_seq_len((uint8_t)next_header);
std::string next_seq(1, next_header);
bool complete = true;
for (int i = 1; i < next_len; i++) {
char c;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) {
complete = false;
break;
}
next_seq.push_back(c);
}
if (!complete) {
enqueue_bytes(next_seq);
break;
}
grapheme_decode_utf8(next_seq.data(), next_seq.size(), &cur_cp);
if (grapheme_is_character_break(prev_cp, cur_cp, &state)) {
enqueue_bytes(next_seq);
break;
}
out += next_seq;
prev_cp = cur_cp;
}
return KeyEvent::ReadResult::SUCCESS;
}
KeyEvent::ReadResult IO::read_bracketed_paste(std::string &out) {
KeyEvent::ReadResult res = KeyEvent::ReadResult::SUCCESS;
out.clear();
std::string window;
while (true) {
char c;
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
return res;
window.push_back(c);
if (window.size() == 5 && window == "\x1b[201") {
char tilde;
if ((res = get_next_byte(tilde)) != KeyEvent::ReadResult::SUCCESS)
return res;
if (tilde == '~')
return res;
out += window;
out.push_back(tilde);
window.clear();
continue;
}
if (window.size() == 5) {
out.push_back(window.front());
window.erase(window.begin());
}
}
}
KeyEvent IO::parse_mouse(const std::string &buf) {
KeyEvent ev;
if (buf.size() < 6)
return ev;
uint8_t code = (uint8_t)buf[3] - 32;
uint8_t button = code & 0x03;
if (button != 0 && button != 3)
return ev;
ev.type = KeyEvent::KeyType::MOUSE;
ev.mouse_state = (button == 3) ? KeyEvent::MouseState::RELEASE
: KeyEvent::MouseState::PRESS;
ev.mouse_x = (uint8_t)buf[4] - 33;
ev.mouse_y = (uint8_t)buf[5] - 33;
return ev;
}
KeyEvent IO::parse_escape(const std::string &buf) {
KeyEvent ev;
ev.type = KeyEvent::KeyType::SPECIAL;
bool has_modifier = buf.size() > 3 && buf[3] == ';';
size_t pos;
if (!has_modifier) {
pos = 2;
} else {
pos = 5;
switch (buf.size() > 4 ? buf[4] : 0) {
case '2':
ev.modifier = KeyEvent::Modifier::SHIFT;
break;
case '3':
ev.modifier = KeyEvent::Modifier::ALT;
break;
case '5':
ev.modifier = KeyEvent::Modifier::CTRL;
break;
case '7':
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
break;
default:
ev.modifier = KeyEvent::Modifier::NONE;
break;
}
}
char key = pos < buf.size() ? buf[pos] : 0;
switch (key) {
case 'A':
ev.special_key = KeyEvent::SpecialKey::UP;
break;
case 'B':
ev.special_key = KeyEvent::SpecialKey::DOWN;
break;
case 'C':
ev.special_key = KeyEvent::SpecialKey::RIGHT;
break;
case 'D':
ev.special_key = KeyEvent::SpecialKey::LEFT;
break;
case '3':
ev.special_key = KeyEvent::SpecialKey::DELETE;
break;
default:
ev.special_key = KeyEvent::SpecialKey::UNKNOWN;
break;
}
return ev;
}
KeyEvent IO::read_key() {
while (true) {
std::string buf;
auto res = read_next_unit(buf);
KeyEvent ev;
switch (res) {
case KeyEvent::ReadResult::EOF_:
ev.type = KeyEvent::KeyType::EOF_;
return ev;
case KeyEvent::ReadResult::RESIZE:
resized.store(false);
ev.type = KeyEvent::KeyType::RESIZE;
return ev;
case KeyEvent::ReadResult::SUCCESS:
break;
}
if (buf.size() >= 6 && buf[0] == '\x1b' && buf[1] == '[' && buf.compare(2, 4, "200~") == 0) {
std::string pasted;
switch (read_bracketed_paste(pasted)) {
case KeyEvent::ReadResult::SUCCESS:
ev.type = KeyEvent::KeyType::PASTE;
ev.text = std::move(pasted);
break;
case KeyEvent::ReadResult::EOF_:
ev.type = KeyEvent::KeyType::EOF_;
break;
case KeyEvent::ReadResult::RESIZE:
resized.store(false);
ev.type = KeyEvent::KeyType::RESIZE;
break;
}
return ev;
}
if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
ev = parse_mouse(buf);
if (ev.type == KeyEvent::KeyType::EOF_)
continue;
return ev;
}
if (buf.size() >= 2 && buf[0] == '\x1b' && buf[1] == '[')
return parse_escape(buf);
ev.type = KeyEvent::KeyType::CHAR;
ev.modifier = KeyEvent::Modifier::NONE;
if (buf.size() == 1) {
uint8_t c = (uint8_t)buf[0];
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
ev.modifier = KeyEvent::Modifier::CTRL;
ev.text = 'a' + c - 1;
return ev;
}
}
if (buf.size() == 2 && (uint8_t)buf[0] == 0x1B) {
uint8_t c = (uint8_t)buf[1];
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
ev.text = 'a' + c - 1;
return ev;
}
ev.modifier = KeyEvent::Modifier::ALT;
ev.text = buf.substr(1);
return ev;
}
ev.text = std::move(buf);
return ev;
}
}
} // namespace bed::internal::io
+180
View File
@@ -0,0 +1,180 @@
#include "internal/io/io.h"
namespace bed::internal::io {
termios IO::orig_termios{};
termios IO::raw_termios{};
bool IO::cleaned = true;
volatile std::atomic_bool IO::resized(false);
IO::IO() {
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
throw fatal_error("Can't get terminal state.", 1);
struct sigaction sa{};
sa.sa_handler = handle_sigwinch;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGWINCH, &sa, nullptr) == -1)
throw fatal_error("Can't install SIGWINCH handler.", 1);
raw_termios = orig_termios;
raw_termios.c_iflag &= ~(BRKINT | ISTRIP | IXON);
raw_termios.c_cflag |= (CS8);
raw_termios.c_lflag &= ~(ECHO | ICANON | ISIG);
raw_termios.c_cc[VMIN] = 1;
raw_termios.c_cc[VTIME] = 0;
enable_raw();
atexit(cleanup);
}
IO::~IO() {
cleanup();
}
void IO::enable_mouse() {
const char *seq = "\x1b[?1000h";
write_all(STDOUT_FILENO, seq, 8);
}
void IO::disable_mouse() {
const char *seq = "\x1b[?1000l";
write_all(STDOUT_FILENO, seq, 8);
}
std::pair<uint16_t, uint16_t> IO::terminal_size() {
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
throw fatal_error("Can't get terminal size.", 1);
return {ws.ws_row, ws.ws_col};
}
std::pair<uint16_t, uint16_t> IO::cursor_position() {
write_all(STDOUT_FILENO, "\x1b[6n", 4);
std::string response;
char c;
if (read(STDIN_FILENO, &c, 1) != 1 || c != '\x1b')
throw fatal_error("Invalid cursor position response.", 1);
if (read(STDIN_FILENO, &c, 1) != 1 || c != '[')
throw fatal_error("Invalid cursor position response.", 1);
while (true) {
if (read(STDIN_FILENO, &c, 1) != 1)
throw fatal_error("Invalid cursor position response.", 1);
if (c == 'R')
break;
response += c;
}
unsigned row;
unsigned col;
if (sscanf(response.c_str(), "%u;%u", &row, &col) != 2)
throw fatal_error("Invalid cursor position response.", 1);
return {(uint16_t)row, (uint16_t)col};
}
void IO::enable_raw() {
if (!cleaned)
return;
std::string os = "\x1b[?2004h";
write_all(STDOUT_FILENO, os.c_str(), os.size());
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_termios) == -1)
throw fatal_error("Can't set raw terminal state.", 1);
cleaned = false;
}
void IO::cleanup() {
if (cleaned)
return;
std::string os = "\x1b[?1000l\x1b[?2004l";
write_all(STDOUT_FILENO, os.c_str(), os.size());
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
perror("Can't clean up terminal.");
cleaned = true;
}
void IO::handle_sigwinch(int) {
resized.store(true);
}
void IO::move_cursor(uint16_t row, uint16_t col) {
char buf[32];
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
write_all(STDOUT_FILENO, buf, n);
}
void IO::write(const char *buf, uint64_t n) {
write_all(STDOUT_FILENO, buf, n);
}
void IO::write(std::string_view s) {
write_all(STDOUT_FILENO, s.data(), s.size());
}
void IO::write_line(std::string_view s) {
write(s);
write("\n", 1);
}
void IO::run_pty(const std::string &cmd) {
int master_fd = -1;
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
throw fatal_error("Can't get terminal size.", 1);
pid_t pid = forkpty(
&master_fd,
nullptr,
&orig_termios,
&ws
);
if (pid == -1)
throw fatal_error("Can't create PTY.", 1);
if (pid == 0) {
const char *shell = getenv("BED_SHELL");
if (!shell || !*shell)
shell = getenv("SHELL");
if (!shell || !*shell)
shell = "/bin/sh";
execl(shell, shell, "-i", "-c", cmd.c_str(), (char *)nullptr);
_exit(127);
}
struct pollfd fds[2];
while (true) {
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = master_fd;
fds[1].events = POLLIN;
int rc = poll(fds, 2, -1);
if (rc == -1) {
if (errno == EINTR)
continue;
break;
}
if (resized.exchange(false)) {
struct winsize new_ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_ws) == 0)
ioctl(master_fd, TIOCSWINSZ, &new_ws);
}
if (fds[0].revents & POLLIN) {
char buf[4096];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0)
write_all(master_fd, buf, n);
else if (n == 0)
break;
}
if (fds[1].revents & POLLIN) {
char buf[8192];
ssize_t n = read(master_fd, buf, sizeof(buf));
if (n > 0)
write_all(STDOUT_FILENO, buf, n);
else
break;
}
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
}
close(master_fd);
int status;
while (waitpid(pid, &status, 0) == -1)
if (errno != EINTR)
break;
}
} // namespace bed::internal::io
+252
View File
@@ -0,0 +1,252 @@
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed::internal::parser {
void Parser::locator(AddressPromise &addr) {
addr.base = AddressPromise::None{};
switch (peek()) {
case '.':
advance();
addr.base = AddressPromise::Current{};
break;
case '$':
advance();
addr.base = AddressPromise::Last{};
break;
case '%':
advance();
addr.base = AddressPromise::LastRange{};
break;
case '[':
advance();
addr.base = AddressPromise::Block{Direction::Backward};
break;
case ']':
advance();
addr.base = AddressPromise::Block{Direction::Forward};
break;
case '^':
advance();
addr.base = AddressPromise::Diagnostic{Direction::Backward};
break;
case '~':
advance();
addr.base = AddressPromise::Diagnostic{Direction::Forward};
break;
case '\'':
advance();
if (('a' <= peek() && peek() <= 'z')
|| ('A' <= peek() && peek() <= 'Z'))
addr.base = AddressPromise::Mark{peek()};
else
throw ed_error("Valid mark needed after \'");
advance();
break;
case '{': {
advance();
uint16_t j = 0;
std::string func;
std::string arg;
while (peek(j) != '}') {
if (peek(j) == '\0')
throw ed_error("Scripted address not terminated");
if (peek(j) == '\\')
++j;
if (peek(j) == ':') {
func = peek_str(j);
advance(j + 1);
j = 0;
continue;
}
++j;
}
if (func.size())
arg = peek_str(j);
else
func = peek_str(j);
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
advance(j + 1);
} break;
case '/': {
advance();
uint64_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == '/')
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
addr.base = AddressPromise::Regex(
Direction::Forward,
std::string(peek_str(j))
);
advance(j + 1);
} break;
case '?': {
advance();
uint16_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == '?')
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
addr.base = AddressPromise::Regex(
Direction::Backward,
std::string(peek_str(j))
);
advance(j + 1);
} break;
case '<': {
advance();
uint16_t j = 0;
while (peek(j) != '>'
&& peek(j) != '<'
&& peek(j) != '\0')
j++;
switch (peek(j)) {
case '\0':
case '>':
addr.base = AddressPromise::SymbolDefinition{
std::string(peek_str(j))
};
break;
case '<':
addr.base = AddressPromise::SymbolReference{
Direction::Backward,
std::string(peek_str(j))
};
break;
}
advance(j + 1);
} break;
case '>': {
advance();
uint16_t j = 0;
while (peek(j) != '>' && peek(j) != '\0')
j++;
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated symbol reference addressing.");
case '>':
addr.base = AddressPromise::SymbolReference{
Direction::Forward,
std::string(peek_str(j))
};
break;
}
advance(j + 1);
} break;
case '+': {
advance();
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9') {
num = num * 10 + (peek(j) - '0');
j++;
}
if (j == 0)
num = 1;
advance(j);
addr.offset += num;
} break;
case '-': {
advance();
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9') {
num = num * 10 + (peek(j) - '0');
j++;
}
if (j == 0)
num = 1;
advance(j);
addr.offset -= num;
} break;
default:
if ('0' <= peek() && peek() <= '9') {
uint64_t num = 0;
while ('0' <= peek() && peek() <= '9') {
num = num * 10 + (peek() - '0');
advance();
}
addr.base = AddressPromise::Number{num};
}
}
}
int64_t Parser::offset() {
int64_t offset = 0;
while (peek() == '+' || peek() == '-'
|| ('0' <= peek() && peek() <= '9')) {
bool positive = peek() != '-';
if (peek() == '+' || peek() == '-')
advance();
uint16_t j = 0;
int64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9')
num = num * 10 + (peek(j++) - '0');
if (j == 0)
num = 1;
advance(j);
offset += positive ? num : -num;
skip_ws();
}
skip_ws();
return offset;
}
void Parser::address(AddressPromise &addr) {
if (peek() == ':') {
advance();
uint16_t j = 0;
while (peek(j) != ':' && peek(j) != '\0')
j++;
addr.bufname = peek_str(j);
advance(j);
if (peek() == ':')
advance();
}
skip_ws();
if (peek() == '\0')
return;
locator(addr);
skip_ws();
addr.offset += offset();
}
void Parser::addresses(std::vector<AddressPromise> &addresses) {
skip_ws();
addresses.push_back({});
auto *addr = &addresses.back();
address(*addr);
while (peek() == ',' || peek() == ';') {
addr->jumping = peek() == ';';
advance();
skip_ws();
addresses.push_back({});
addr = &addresses.back();
address(*addr);
}
if (addresses.size() == 1
&& addr->offset == 0 && !addr->bufname.has_value()
&& std::holds_alternative<AddressPromise::None>(addr->base))
addresses.pop_back();
}
} // namespace bed::internal::parser
+217
View File
@@ -0,0 +1,217 @@
#include "bed.h"
#include "internal/parser/parser.h"
#include "internal/vase/vase.h"
namespace bed::internal::parser {
buffer::Line AddressPromise::resolve(BEd &ctx) {
buffer::Line result = std::visit(
[&](auto const &addr) -> buffer::Line {
if (!bufname.has_value() || bufname->empty())
throw ed_error("Buffer name can't be empty.");
buffer::Line line = {*bufname, 0};
auto &buf = ctx.buffer(line.buffername);
using T = std::decay_t<decltype(addr)>;
if constexpr (std::is_same_v<T, None>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
} else if constexpr (std::is_same_v<T, Current>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
} else if constexpr (std::is_same_v<T, Last>) {
line.number = buf.lines();
} else if constexpr (std::is_same_v<T, Number>) {
line.number = addr.i;
} else if constexpr (std::is_same_v<T, Mark>) {
line = ctx.marks.get(addr.m);
if (line.number == UINT64_MAX)
throw ed_error("Mark not set.");
} else if constexpr (std::is_same_v<T, Regex>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
if (buf.lines() > 0 && line.number == 0)
line.number = 1;
if (line.number == 0)
throw ed_error("Can't search empty buffer.");
std::string_view re = addr.re;
if (re.size() == 0)
re = ctx.last_regex;
if (re.size() == 0)
throw ed_error("No regex given.");
if (addr.dir == Direction::Forward)
line.number = buf.find_next(re, line.number);
else
line.number = buf.find_prev(re, line.number);
ctx.last_regex = re;
} else if constexpr (std::is_same_v<T, Block>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
if (buf.lines() > 0 && line.number == 0)
line.number = 1;
if (addr.dir == Direction::Forward)
line.number = buf.next_closing(line.number);
else
line.number = buf.prev_closing(line.number);
} else {
throw ed_error("Unhandled address given.");
}
if (offset < 0 && line.number < (uint64_t)-offset)
throw ed_error("Can't have negative addresses");
line.number += offset;
if (line.number > ctx.buffer(line.buffername).lines())
throw ed_error("Line number too high.");
return line;
},
base
);
return result;
}
std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<AddressPromise> &list) {
std::string bufname = ctx.current().buffername;
bool prev_given = false;
bool prev_set = false;
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
if (!curr.jumping)
curr.base = Number(1);
else
curr.base = Current();
curr.offset = 0;
prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev().end);
prev_given = true;
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
prev = curr;
prev_set = true;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (prev_set) {
if (prev_given) {
curr.base = prev.base;
curr.offset = prev.offset;
} else {
curr.base = Last();
curr.offset = 0;
}
}
} else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev().end);
}
return curr.resolve(ctx);
}
}
return std::nullopt;
}
std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<AddressPromise> &list) {
std::string bufname = ctx.current().buffername;
bool prev_given = false;
bool prev_set = false;
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
if (!curr.jumping)
curr.base = Number(1);
else
curr.base = Current();
curr.offset = 0;
prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev().end);
prev_given = true;
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
prev = curr;
prev_set = true;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (prev_set) {
if (prev_given) {
curr.base = prev.base;
curr.offset = prev.offset;
} else {
curr.base = Last();
curr.offset = 0;
}
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
}
} else if (std::holds_alternative<LastRange>(curr.base)) {
auto previous = ctx.prev();
auto &buf = ctx.buffer(previous.buffername);
if (curr.offset < 0 && previous.start < (uint64_t)-curr.offset)
throw ed_error("Can't have negative addresses");
previous.start += curr.offset;
if (previous.start > buf.lines())
throw ed_error("Line number too high.");
if (curr.offset < 0 && previous.end < (uint64_t)-curr.offset)
throw ed_error("Can't have negative addresses");
previous.end += curr.offset;
if (previous.end > buf.lines())
throw ed_error("Line number too high.");
return previous;
}
if (prev_given)
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
buffer::Line only = curr.resolve(ctx);
return buffer::Range(only, only);
}
}
return std::nullopt;
}
} // namespace bed::internal::parser
+208
View File
@@ -0,0 +1,208 @@
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed::internal::parser {
void Parser::operation() {
if (peek() == '\0') {
command->function = &bed.no_op;
return;
}
uint64_t len = bed.functions.longest_match(peek_str());
if (len == 0)
throw ed_error("Function not found.");
functions::Function *function = bed.functions.get_ptr(peek_str(len));
advance(len);
command->function = function;
char suffix = '\0';
switch (command->function->argument_kind) {
case functions::Function::ArgumentKind::None:
break;
case functions::Function::ArgumentKind::Number:
skip_ws();
command->argument = offset();
break;
case functions::Function::ArgumentKind::Mark:
if (('a' <= peek() && peek() <= 'z')
|| ('A' <= peek() && peek() <= 'Z'))
command->argument = peek();
else
throw ed_error("Valid mark needed.");
advance();
break;
case functions::Function::ArgumentKind::Any:
command->argument = std::string(peek_str());
advance(peek_str().size());
break;
case functions::Function::ArgumentKind::Global: {
char delim;
std::string val;
switch (peek()) {
case '\0':
throw ed_error("Command needs a delimited value.");
case '{': {
advance();
delim = '}';
uint16_t j = 0;
while (peek(j) != '}' && peek(j) != '\0') {
if (peek(j) == '\\')
j++;
j++;
}
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated {");
case '}':
val = peek_str(j);
break;
}
advance(j + 1);
} break;
case '<': {
advance();
delim = '<';
uint16_t j = 0;
while (peek(j) != '>' || peek(j) != '\0') {
if (peek(j) == '\\')
j++;
j++;
}
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated <");
case '>':
val = peek_str(j);
break;
}
advance(j + 1);
} break;
case '^':
case '~':
advance();
delim = '^';
break;
default:
delim = peek();
advance();
uint64_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
val = peek_str(j);
advance(j + 1);
}
command->argument = functions::Function::GlobalArg(delim, std::move(val));
} break;
case functions::Function::ArgumentKind::File:
if (!(peek() == '\t' || peek() == ' ' || peek() == '\0'))
throw ed_error("Incorrect file input usage.");
skip_ws();
switch (peek()) {
case '!':
advance();
command->argument = functions::Function::ShellArg(std::string(peek_str()));
advance(peek_str().size());
break;
case '\0':
command->argument = std::monostate();
break;
default:
command->argument = std::filesystem::path(peek_str());
advance(peek_str().size());
break;
}
break;
case functions::Function::ArgumentKind::Line:
command->argument = buffer::Line();
addresses(command->argument_addresses);
break;
case functions::Function::ArgumentKind::Range:
command->argument = buffer::Range();
addresses(command->argument_addresses);
break;
case functions::Function::ArgumentKind::Regex: {
char delim = peek();
if (delim == '\0')
throw ed_error("regex expected");
advance();
uint16_t j = 0;
while (true) {
if (peek(j) == '\0')
throw ed_error("Unterminated regex");
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && peek(j) != ']')
j++;
else
j++;
}
std::string expression(peek_str(j));
advance(j + 1);
j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && peek(j) != ']')
j++;
else
j++;
}
std::string replacement(peek_str(j));
if (peek(j) != '\0') {
advance(j + 1);
} else {
advance(j);
}
std::string options;
if (peek() != '\0') {
options = std::string(peek_str());
advance(peek_str().size());
}
std::erase_if(options, [&](char c) {
if (bed.suffixes[c - 'a'].has_value()) {
suffix = c;
return true;
}
return false;
});
command->argument = functions::Function::RegexArg(expression, replacement, options);
} break;
case functions::Function::ArgumentKind::Ruby:
command->argument = functions::Function::RubyArg(std::string(peek_str()));
advance(peek_str().size());
break;
case functions::Function::ArgumentKind::Shell:
command->argument = functions::Function::ShellArg(std::string(peek_str()));
advance(peek_str().size());
break;
}
if (!suffix) {
suffix = peek();
advance();
}
if (suffix) {
auto &s = bed.suffixes[suffix - 'a'];
if (s.has_value())
command->suffix = &s.value();
else
throw ed_error("Invalid suffix.");
}
}
} // namespace bed::internal::parser
+65
View File
@@ -0,0 +1,65 @@
#include "internal/parser/parser.h"
#include "bed.h"
namespace bed::internal::parser {
char Parser::peek(uint16_t o) {
return i + o < cmd.size() ? cmd[i + o] : '\0';
}
std::string_view Parser::peek_str(uint16_t len) {
return cmd.substr(i, len);
}
void Parser::advance(uint16_t c) {
i += c;
}
void Parser::skip_ws() {
while (peek() == ' ' || peek() == '\t')
advance();
}
void Parser::parse() {
skip_ws();
if (peek() == '@') {
advance();
command->temp_address = true;
} else {
command->temp_address = false;
}
skip_ws();
addresses(command->addresses);
operation();
skip_ws();
if (peek() != '\0')
throw ed_error("Malformed command");
}
Parser::Parser(
std::string_view cmd, BEd &bed, Command *command,
std::vector<ui::Token> *tokens, CompletionContext *completion
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
i = 0;
}
Command Parser::get_command(std::string_view cmd, BEd &bed) {
Command c;
std::vector<ui::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, &c, &tokens, &completion);
p.parse();
return c;
}
std::vector<AddressPromise> Parser::get_addresses(std::string_view cmd, BEd &bed) {
std::vector<AddressPromise> result;
std::vector<ui::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, nullptr, &tokens, &completion);
p.addresses(result);
p.skip_ws();
if (p.peek() != '\0')
throw ed_error("Malformed address");
return result;
}
} // namespace bed::internal::parser
+391
View File
@@ -0,0 +1,391 @@
#include "internal/syntax/parser.h"
namespace bed::internal::syntax {
static void destroy_tree(ParseState *node, Language &lang) {
if (!node)
return;
if (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
destroy_tree(branch->left, lang);
destroy_tree(branch->right, lang);
free(branch);
} else {
auto *leaf = (ParseStateLeaf *)node;
if (leaf->state)
lang.destroy(leaf->state);
if (leaf->blocks)
free(leaf->blocks);
free(leaf);
}
}
static ParseState *make_branch(ParseState *left, ParseState *right) {
if (!left)
return right;
if (!right)
return left;
auto *branch = (ParseStateBranch *)malloc(sizeof(ParseStateBranch));
branch->header = ParseState::BRANCH_BIT + left->lines() + right->lines();
branch->left = left;
branch->right = right;
return branch;
}
static ParseState *build_tree(std::vector<ParseStateLeaf *> &leaves, size_t begin, size_t end) {
const size_t count = end - begin;
if (count == 0)
return nullptr;
if (count == 1)
return leaves[begin];
const size_t mid = begin + count / 2;
ParseState *left = build_tree(leaves, begin, mid);
ParseState *right = build_tree(leaves, mid, end);
return make_branch(left, right);
}
Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang)
: root(nullptr), lang(lang) {
reset(vase, lines, lang);
}
Parser::~Parser() {
destroy_tree(root, lang);
}
void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) {
destroy_tree(root, lang);
root = nullptr;
if (lines == 0)
return;
lang = std::move(lang_);
std::vector<ParseStateLeaf *> leaves;
leaves.reserve((lines + MAX_CHUNK - 1) / MAX_CHUNK);
uint64_t consumed = 0;
while (consumed < lines) {
auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
leaf->state = nullptr;
leaf->blocks = nullptr;
leaf->n = 0;
leaf->cap = 0;
uint64_t chunk = std::min(MAX_CHUNK, lines - consumed);
leaf->header = chunk;
consumed += chunk;
leaves.push_back(leaf);
}
root = build_tree(leaves, 0, leaves.size());
modify(vase, 0, lines);
}
std::pair<ParseState *, ParseState *> Parser::split_tree(ParseState *node, uint64_t line) {
if (!node)
return {nullptr, nullptr};
if (line == 0)
return {nullptr, node};
if (line >= node->lines())
return {node, nullptr};
if (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
uint64_t left_lines = branch->left->lines();
if (line < left_lines) {
auto [a, b] = split_tree(branch->left, line);
ParseState *right = join_tree(b, branch->right);
free(branch);
return {a, right};
}
if (line == left_lines) {
ParseState *left = branch->left;
ParseState *right = branch->right;
free(branch);
return {left, right};
}
auto [a, b] = split_tree(branch->right, line - left_lines);
ParseState *left = join_tree(branch->left, a);
free(branch);
return {left, b};
} else {
auto *leaf = (ParseStateLeaf *)node;
uint64_t lines = leaf->lines();
auto *right = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
right->header = lines - line;
right->state = nullptr;
right->blocks = nullptr;
right->n = 0;
right->cap = 0;
leaf->header = line;
leaf->n = 0;
return {leaf, right};
}
}
ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
// TODO: balance
return make_branch(a, b);
}
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
begin_edit();
erase(start, count);
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;
TreeCursor c(root, line, &relative);
uint64_t line_offset = line - relative;
int level = 0;
bool first_leaf = true;
while (c.leaf) {
auto *leaf = c.leaf;
for (uint32_t i = 0; i < leaf->n; ++i) {
uint16_t block = leaf->blocks[i];
uint32_t pos = block & ParseStateLeaf::LINE_MASK;
if (first_leaf && pos <= relative)
continue;
bool closing = block & ParseStateLeaf::IS_CLOSING;
if (closing) {
if (level == 0)
return line_offset + pos;
--level;
} else {
++level;
}
}
line_offset += leaf->lines();
c.next();
first_leaf = false;
}
return UINT64_MAX;
}
uint64_t Parser::prev_opening(uint64_t line) {
if (!root)
return 0;
uint64_t relative = 0;
TreeCursor c(root, line, &relative);
uint64_t line_offset = line - relative;
int level = 0;
bool first_leaf = true;
while (c.leaf) {
auto *leaf = c.leaf;
for (int32_t i = (int32_t)leaf->n - 1; i >= 0; --i) {
uint16_t block = leaf->blocks[i];
uint32_t pos = block & ParseStateLeaf::LINE_MASK;
if (first_leaf && pos >= relative)
continue;
bool closing = block & ParseStateLeaf::IS_CLOSING;
if (!closing) {
if (level == 0)
return line_offset + pos;
--level;
} else {
++level;
}
}
c.prev();
first_leaf = false;
if (c.leaf)
line_offset -= c.leaf->lines();
}
return 0;
}
std::optional<Parser::Iterator> Parser::get_hl(vase::Shard *vase, uint64_t target) {
if (!root)
return std::nullopt;
return Parser::Iterator(target, this, vase);
}
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Shard *vase) : p(p) {
uint64_t offset;
TreeCursor c = TreeCursor(p->root, target, &offset);
at = target - offset;
if (c.leaf->state) {
state = p->lang.copy(c.leaf->state);
} else {
while (!c.leaf->state) {
c.prev();
if (!c.leaf) {
at = 0;
break;
}
at -= c.leaf->lines();
}
if (c.leaf) {
state = p->lang.copy(c.leaf->state);
} else {
state = p->lang.none_state();
c = TreeCursor(p->root, 0, &offset);
}
}
it = vase::Iterator(vase, at, Direction::Forward);
while (at < target) {
it->next();
tokens.clear();
events.clear();
p->lang.parse(&state, it->line, at == 0, &tokens, &events);
at++;
}
}
Parser::Iterator::~Iterator() {
if (state)
p->lang.destroy(state);
}
Parser::Iterator::Iterator(Iterator &&other)
: p(other.p),
it(std::move(other.it)),
state(other.state),
tokens(std::move(other.tokens)) {
other.state = nullptr;
}
Parser::Iterator &Parser::Iterator::operator=(Iterator &&other) {
if (this == &other)
return *this;
if (state)
p->lang.destroy(state);
p = other.p;
it = std::move(other.it);
state = other.state;
tokens = std::move(other.tokens);
other.state = nullptr;
return *this;
}
void Parser::Iterator::next() {
it->next();
tokens.clear();
events.clear();
p->lang.parse(&state, it->line, at++ == 0, &tokens, &events);
}
} // namespace bed::internal::syntax
File diff suppressed because it is too large Load Diff
+44
View File
@@ -0,0 +1,44 @@
#include "internal/syntax/ruby/parser.h"
namespace bed::internal::syntax::ruby {
Language lang_ruby() {
return Language{
.none_state = []() {
RubyState *st = (RubyState *)malloc(
sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState)
);
st->top = 1;
st->docs = 0;
st->stack()[0] = {
.brace_level = 1,
.lit_brace_level = 0,
.state = RubyState::RubyInternalState::NONE,
.flags = RubyState::RubyInternalState::EXPECTING_EXPRESSION | RubyState::RubyInternalState::NEWLINE,
.delim_start = '\0',
.delim_end = '\0'
};
return st; },
.parse = ruby_parse,
.copy = [](void *v_i_st) {
RubyState *i_st = (RubyState *)v_i_st;
uint32_t bytes = sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState) * i_st->top
+ i_st->docs;
RubyState *o_st = (RubyState *)malloc(bytes);
memcpy(o_st, i_st, bytes);
return o_st; },
.equal = [](void *v_a_st, void *v_b_st) {
RubyState *a_st = (RubyState *)v_a_st;
uint32_t a_bytes = sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState) * a_st->top
+ a_st->docs;
RubyState *b_st = (RubyState *)v_b_st;
uint32_t b_bytes = sizeof(RubyState)
+ sizeof(RubyState::RubyInternalState) * b_st->top
+ b_st->docs;
return a_bytes == b_bytes && memcmp(a_st, b_st, a_bytes) == 0; },
.destroy = [](void *v_st) { free(v_st); },
};
}
} // namespace bed::internal::syntax::ruby
+66
View File
@@ -0,0 +1,66 @@
#include "internal/syntax/decl.h"
namespace bed::internal::syntax {
TreeCursor::TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative) {
ParseState *node = root;
while (node->is_branch()) {
auto *branch = (ParseStateBranch *)node;
auto *left = branch->left;
stack[depth] = branch;
if (target_line < left->lines()) {
went_left[depth] = true;
++depth;
node = left;
} else {
target_line -= left->lines();
went_left[depth] = false;
++depth;
node = branch->right;
}
}
*relative = target_line;
leaf = (ParseStateLeaf *)node;
}
void TreeCursor::next() {
while (depth > 0) {
auto *branch = stack[depth - 1];
bool from_left = went_left[depth - 1];
--depth;
if (!from_left)
continue;
ParseState *node = branch->right;
while (node->is_branch()) {
auto *b = (ParseStateBranch *)node;
stack[depth] = b;
went_left[depth] = true;
++depth;
node = b->left;
}
leaf = (ParseStateLeaf *)node;
return;
}
leaf = nullptr;
}
void TreeCursor::prev() {
while (depth > 0) {
auto *branch = stack[depth - 1];
bool from_left = went_left[depth - 1];
--depth;
if (from_left)
continue;
ParseState *node = branch->left;
while (node->is_branch()) {
auto *b = (ParseStateBranch *)node;
stack[depth] = b;
went_left[depth] = false;
++depth;
node = b->right;
}
leaf = (ParseStateLeaf *)node;
return;
}
leaf = nullptr;
}
} // namespace bed::internal::syntax
+162
View File
@@ -0,0 +1,162 @@
#include "internal/theme/theme.h"
namespace bed::internal::theme {
Theme::Theme() {
hl.fill({
.fg = 0xF0F0F0,
.bg = 0x000000,
.flags = Highlight::None,
});
}
Highlight Theme::get(internal::syntax::Token token) const {
return hl[token.type];
}
Theme Theme::default_theme() {
Theme theme;
theme.hl[internal::syntax::Token::Shebang] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Error] = {
.fg = 0xEF5168,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Comment] = {
.fg = 0xAAAAAA,
.bg = 0x000000,
.flags = Highlight::Italic,
};
theme.hl[internal::syntax::Token::String] = {
.fg = 0xAAD94C,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Escape] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Interpolation] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Regexp] = {
.fg = 0xD2A6FF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Number] = {
.fg = 0xE6C08A,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::True] = {
.fg = 0x7AE93C,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::False] = {
.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,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::VariableInstance] = {
.fg = 0x95E6CB,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::VariableGlobal] = {
.fg = 0xF07178,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Annotation] = {
.fg = 0x7DCFFF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Directive] = {
.fg = 0xFF8F40,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Label] = {
.fg = 0xD2A6FF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Brace1] = {
.fg = 0xD2A6FF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Brace2] = {
.fg = 0xFFAFAF,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Brace3] = {
.fg = 0xFFFF00,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Brace4] = {
.fg = 0x0FFF0F,
.bg = 0x000000,
.flags = Highlight::None,
};
theme.hl[internal::syntax::Token::Brace5] = {
.fg = 0xFF0F0F,
.bg = 0x000000,
.flags = Highlight::None,
};
return theme;
}
Theme Theme::from_name(std::string_view name) {
if (name == "default")
return default_theme();
throw std::runtime_error("Unknown theme: " + std::string(name));
}
} // namespace bed::internal::theme
+157
View File
@@ -0,0 +1,157 @@
#include "internal/ui/command.h"
#include "bed.h"
namespace bed::internal::ui {
/*template <typename F>
static void for_each_cluster(std::string_view s, F &&f) {
unicode_width_state_t state;
unicode_width_init(&state);
size_t i = 0;
while (i < s.size()) {
unsigned char c = static_cast<unsigned char>(s[i]);
size_t bytes = 1;
int width = 0;
if (c < 128) {
width = unicode_width_process(&state, c);
} else {
uint_least32_t cp;
size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp);
bytes = decoded > 0 ? decoded : 1;
width = unicode_width_process(&state, cp);
}
if (width < 0)
width = 0;
f(i, bytes, width);
i += bytes;
}
}
static int display_width(std::string_view s) {
int w = 0;
for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; });
return w;
}
static uint16_t count_clusters(std::string_view s) {
uint16_t n = 0;
for_each_cluster(s, [&](size_t, size_t, int) { ++n; });
return n;
}
static std::vector<uint16_t> wrap_offsets(std::string_view line, uint16_t avail) {
std::vector<uint16_t> offsets{0};
int col = 0;
for_each_cluster(line, [&](uint16_t i, uint16_t, int w) {
if (col + w > avail && col > 0) {
offsets.push_back(i);
col = 0;
}
col += w;
});
return offsets;
}
// The word under/before `byte_pos`, split on plain ASCII spaces. Used to
// pick what prefix to hand the suggestion trie.
// TODO: change to use libgrapheme word break here.
static std::string current_word(const std::string &line, size_t byte_pos) {
size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1);
start = (start == std::string::npos) ? 0 : start + 1;
if (byte_pos < start)
byte_pos = start;
return line.substr(start, byte_pos - start);
}*/
CommandIO::CommandIO(BEd &bed) : bed(bed) {
if (bed.prompt_mode)
prompt = bed.prompt(bed);
cursor = 0;
}
std::pair<std::string, bool> CommandIO::run() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
term_width = cols;
term_height = rows;
redraw();
io::KeyEvent res;
bool running = true;
while (running) {
res = bed.io.read_key();
switch (res.type) {
case io::KeyEvent::KeyType::EOF_:
running = false;
break;
case io::KeyEvent::KeyType::MOUSE:
case io::KeyEvent::KeyType::RESIZE:
break;
case io::KeyEvent::KeyType::CHAR:
switch (res.modifier) {
case io::KeyEvent::Modifier::SHIFT:
case io::KeyEvent::Modifier::ALT:
case io::KeyEvent::Modifier::CTRL_ALT:
case io::KeyEvent::Modifier::CTRL:
break;
case io::KeyEvent::Modifier::NONE:
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
if (cursor > 0)
cmd.erase(--cursor, 1);
} else if (res.text[0] == '\n') {
running = false;
} else {
cmd.insert(cursor++, res.text);
}
break;
}
break;
case io::KeyEvent::KeyType::PASTE:
cmd.insert(cursor, res.text);
cursor += res.text.size();
break;
case io::KeyEvent::KeyType::SPECIAL:
switch (res.special_key) {
case io::KeyEvent::SpecialKey::UNKNOWN:
case io::KeyEvent::SpecialKey::UP:
case io::KeyEvent::SpecialKey::DOWN:
break;
case io::KeyEvent::SpecialKey::RIGHT:
if (cursor < cmd.size())
cursor++;
break;
case io::KeyEvent::SpecialKey::LEFT:
if (cursor > 0)
cursor--;
break;
case io::KeyEvent::SpecialKey::DELETE:
if (cursor < cmd.size())
cmd.erase(cursor, 1);
break;
}
break;
}
redraw();
}
for (uint16_t i = 1; i < height; ++i) {
bed.io.move_cursor(start + i, 1);
bed.io.write("\x1b[2K", 4);
}
bed.io.move_cursor(start, 1);
bed.io.write("\n", 1);
return {cmd, false};
}
void CommandIO::redraw() {
bed.io.move_cursor(start, 1);
bed.io.write("\x1b[2K", 4);
bed.io.move_cursor(start, 1);
bed.io.write(prompt);
bed.io.write(cmd);
bed.io.move_cursor(start, prompt.size() + cursor + 1);
}
} // namespace bed::internal::ui
+188
View File
@@ -0,0 +1,188 @@
#include "internal/ui/text_mode.h"
#include "bed.h"
namespace bed::internal::ui {
TextMode::TextMode(BEd &bed) : bed(bed) {
cursor = 0;
}
std::pair<vase::Shard *, bool> TextMode::run() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
term_width = cols;
term_height = rows;
cmd.clear();
cursor = 0;
redraw();
bool running = true;
while (running) {
io::KeyEvent res = bed.io.read_key();
switch (res.type) {
case io::KeyEvent::KeyType::EOF_:
running = false;
break;
case io::KeyEvent::KeyType::MOUSE:
case io::KeyEvent::KeyType::RESIZE:
break;
case io::KeyEvent::KeyType::CHAR:
switch (res.modifier) {
case io::KeyEvent::Modifier::SHIFT:
case io::KeyEvent::Modifier::ALT:
case io::KeyEvent::Modifier::CTRL_ALT:
case io::KeyEvent::Modifier::CTRL:
break;
case io::KeyEvent::Modifier::NONE:
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
if (cursor > 0) {
cmd.erase(--cursor, 1);
}
} 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');
} else {
cmd.insert(cursor, res.text);
cursor += res.text.size();
}
break;
}
break;
case io::KeyEvent::KeyType::PASTE:
cmd.insert(cursor, res.text);
cursor += res.text.size();
break;
case io::KeyEvent::KeyType::SPECIAL:
switch (res.special_key) {
case io::KeyEvent::SpecialKey::UNKNOWN:
break;
case io::KeyEvent::SpecialKey::RIGHT:
if (cursor < cmd.size())
++cursor;
break;
case io::KeyEvent::SpecialKey::LEFT:
if (cursor > 0)
--cursor;
break;
case io::KeyEvent::SpecialKey::UP: {
size_t line_start =
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
if (line_start == std::string::npos)
line_start = 0;
else
++line_start;
size_t col = cursor - line_start;
if (line_start == 0)
break;
size_t prev_end = line_start - 1;
size_t prev_start =
cmd.rfind('\n', prev_end == 0 ? 0 : prev_end - 1);
if (prev_start == std::string::npos)
prev_start = 0;
else
++prev_start;
size_t prev_len = prev_end - prev_start;
cursor = prev_start + std::min(col, prev_len);
break;
}
case io::KeyEvent::SpecialKey::DOWN: {
size_t line_start =
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
if (line_start == std::string::npos)
line_start = 0;
else
++line_start;
size_t col = cursor - line_start;
size_t line_end = cmd.find('\n', cursor);
if (line_end == std::string::npos)
line_end = cmd.size();
if (line_end == cmd.size())
break;
size_t next_start = line_end + 1;
size_t next_end = cmd.find('\n', next_start);
if (next_end == std::string::npos)
next_end = cmd.size();
size_t next_len = next_end - next_start;
cursor = next_start + std::min(col, next_len);
break;
}
case io::KeyEvent::SpecialKey::DELETE:
if (cursor < cmd.size())
cmd.erase(cursor, 1);
break;
}
break;
}
redraw();
if (cmd.size() >= 3 && cmd.compare(cmd.size() - 3, 3, "\n.\n") == 0) {
cmd.erase(cmd.size() - 3);
cursor = cmd.size();
running = false;
}
}
size_t total_lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
uint16_t last_row = start + total_lines;
bed.io.move_cursor(last_row, 1);
bed.io.write("\n", 1);
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
}
void TextMode::grow(size_t required_height) {
auto [rows, cols] = bed.io.terminal_size();
term_height = rows;
term_width = cols;
if (required_height > height)
height = required_height;
long overflow = long(start) + long(height) - 1 - long(rows);
if (overflow <= 0)
return;
bed.io.move_cursor(rows, 1);
for (long i = 0; i < overflow; ++i)
bed.io.write("\n", 1);
start -= overflow;
if (start < 1)
start = 1;
}
void TextMode::redraw() {
auto [rows, cols] = bed.io.terminal_size();
term_height = rows;
term_width = cols;
for (uint16_t i = 0; i < height; ++i) {
bed.io.move_cursor(start + i, 1);
bed.io.write("\x1b[2K", 4);
}
size_t line = 0;
size_t line_start = 0;
for (size_t i = 0; i < cursor; ++i) {
if (cmd[i] == '\n') {
++line;
line_start = i + 1;
}
}
size_t col = cursor - line_start;
size_t pos = 0;
uint16_t screen_line = start;
while (pos <= cmd.size()) {
size_t end = cmd.find('\n', pos);
if (end == std::string::npos)
end = cmd.size();
if (screen_line < start + height) {
size_t len = std::min(end - pos, size_t(term_width));
bed.io.move_cursor(screen_line, 1);
bed.io.write(cmd.substr(pos, len));
}
if (end == cmd.size())
break;
pos = end + 1;
++screen_line;
}
size_t vis_col = std::min(col, size_t(term_width - 1));
bed.io.move_cursor(start + line, vis_col + 1);
}
} // namespace bed::internal::ui
+75
View File
@@ -0,0 +1,75 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir)
: it(root, dir), dir(dir) {
it.seek_line(line_num);
it.next(&chunk, &len);
current_offset = it.byte_offset();
last_line_offset = current_offset;
}
bool LineIterator::next(std::string *line) {
if (!line || !chunk)
return false;
last_line_offset = current_offset;
line->clear();
if (dir == Direction::Forward) {
retry_forward:
const char *nl = (const char *)memchr(chunk, '\n', len);
if (!nl) {
if (len && chunk[len - 1] == '\r')
len--;
line->append(chunk, len);
current_offset += len;
if (!it.next(&chunk, &len))
return true;
goto retry_forward;
}
const char *end = nl;
if (end - chunk > 0 && *(end - 1) == '\r')
end--;
line->append(chunk, end);
size_t consumed = (nl - chunk) + 1;
chunk += consumed;
len -= consumed;
current_offset += consumed;
return true;
} else {
retry_backward:
#if defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__)
const char *nl = (const char *)memrchr(chunk, '\n', len);
#else
const char *nl = nullptr;
const char *p = chunk + len;
while (!nl && p != chunk)
if (*(--p) == '\n')
nl = p;
#endif
if (!nl) {
if (len && chunk[len - 1] == '\r')
len--;
line->insert(0, chunk, len);
current_offset -= len;
if (!it.next(&chunk, &len))
return true;
goto retry_backward;
}
const char *start = nl + 1;
const char *line_end = chunk + len;
const size_t consumed = line_end - nl;
const char *end = line_end;
if (end > start && *(end - 1) == '\r')
end--;
if (end > start)
line->insert(0, start, end - start);
len = nl - chunk;
current_offset -= consumed;
return true;
}
}
uint64_t LineIterator::byte_offset() {
return last_line_offset;
}
} // namespace bed::internal::vase
+171
View File
@@ -0,0 +1,171 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
PetalIterator::PetalIterator(Shard *r, Direction dir)
: dir(dir), root(r) {
if (!root)
return;
}
void PetalIterator::seek_offset(uint64_t offset) {
stack.clear();
petal = nullptr;
global_offset = 0;
last_offset = 0;
global_line = 0;
if (!root)
return;
if (offset >= root->length)
offset = root->length - 1;
uint64_t target = offset;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::Kind::Petal) {
petal = (Petal *)curr;
petal_offset = target;
global_offset += target;
last_offset = global_offset;
return;
} else {
auto *b = (Branch *)curr;
uint64_t left_len = b->left->length;
if (target < left_len) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
target -= left_len;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += left_len;
global_line += b->left->lines;
curr = b->right;
}
}
}
std::unreachable();
}
void PetalIterator::seek_line(uint64_t line) {
stack.clear();
petal = nullptr;
global_offset = 0;
last_offset = 0;
bool last_line = false;
if (!root)
return;
if (line > root->lines)
line = root->lines;
if (line == root->lines)
last_line = true;
if (!last_line && dir == Direction::Backward)
line++;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::Kind::Petal) {
petal = (Petal *)curr;
if (last_line && dir == Direction::Backward) {
petal_offset = petal->length;
global_offset += petal->length;
} else {
const char *text = petal->source->read(petal->pos);
uint64_t offset = 0;
while (line) {
const char *c = (const char *)memchr(text, '\n', petal->length - offset);
if (!c)
throw std::runtime_error("leaf line count is wrong.");
offset += (c - text) + 1;
text = c + 1;
line--;
}
if (dir == Direction::Backward)
--offset;
petal_offset = offset;
global_offset += offset;
}
last_offset = global_offset;
return;
} else {
auto *b = (Branch *)curr;
uint64_t left_lines = b->left->lines;
if (line <= left_lines) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
line -= left_lines;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += b->left->length;
curr = b->right;
}
}
}
std::unreachable();
}
Petal *PetalIterator::_next(uint64_t *offset) {
while (true) {
if (petal) {
if (dir == Direction::Forward) {
if (petal_offset == petal->length) {
petal = nullptr;
continue;
}
} else {
if (petal_offset == 0) {
petal = nullptr;
continue;
}
}
auto ret = petal;
last_offset = global_offset;
if (dir == Direction::Forward)
global_offset += petal_offset;
else
global_offset -= petal_offset;
petal = nullptr;
*offset = petal_offset;
return ret;
}
if (stack.empty())
return nullptr;
auto s = stack.back();
stack.pop_back();
while (s->kind == Shard::Kind::Branch) {
Branch *b = (Branch *)s;
if (dir == Direction::Forward) {
stack.push_back(b->right);
s = b->left;
} else {
stack.push_back(b->left);
s = b->right;
}
}
petal = (Petal *)s;
petal_offset = dir == Direction::Forward ? 0 : petal->length;
}
}
bool PetalIterator::next(const char **data, uint64_t *out_len) {
uint64_t offset = 0;
Petal *p = _next(&offset);
if (!p) {
*data = nullptr;
*out_len = 0;
return false;
}
if (dir == Direction::Forward) {
*out_len = p->length - offset;
*data = p->source->read(p->pos + offset);
} else {
*out_len = offset;
*data = p->source->read(p->pos);
}
return true;
}
uint64_t PetalIterator::byte_offset() {
return last_offset;
}
} // namespace bed::internal::vase
+308
View File
@@ -0,0 +1,308 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
std::vector<ReplacePart> parts;
std::string constant;
auto flush_constant = [&]() {
if (!constant.empty()) {
uint64_t lines = 0;
const char *p = constant.data();
const char *end = p + constant.size();
while ((p = (const char *)memchr(p, '\n', end - p))) {
++lines;
++p;
}
uint64_t pos = ap->append(constant.data(), (uint64_t)constant.size());
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::Constant,
.value = new Petal((uint64_t)constant.size(), lines, ap, pos)
}
);
constant.clear();
}
};
for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == '\\' && i + 1 < s.size()) {
char next = s[i + 1];
if (next == '0') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::FullMatch,
.value = (uint8_t)0
}
);
} else if (next >= '1' && next <= '9') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::CaptureGroup,
.value = (uint8_t)(next - '0')
}
);
} else if (next == 'n') {
constant.push_back('\n');
} else {
constant.push_back(next);
}
++i;
continue;
}
if (c == '&') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::FullMatch,
.value = (uint8_t)0
}
);
}
constant.push_back(c);
}
flush_constant();
return parts;
}
Shard *substitute(
AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options,
const std::function<void(uint64_t line, uint64_t old_lines, uint64_t new_lines)> &on_edit
) {
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;
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
if (matches.empty())
return root;
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
struct Edit {
uint64_t line;
uint64_t old_lines;
uint64_t new_lines;
};
uint64_t orig_line = start;
int64_t line_delta = 0;
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
Shard *remaining = root;
Shard::retain(remaining);
uint64_t cursor = 0;
for (const RegexMatch &match : matches) {
uint64_t gap = match.start - cursor;
if (gap > 0) {
auto [keep, rest] = Shard::split(remaining, gap);
Shard::release(remaining);
pieces.push_back(keep);
remaining = rest;
orig_line += keep ? keep->lines : 0;
}
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
Shard::release(remaining);
remaining = rest2;
uint64_t old_lines = dropped ? dropped->lines : 0;
uint64_t new_lines = 0;
for (size_t i = 0; i < replace_parts.size(); ++i) {
const ReplacePart &part = replace_parts[i];
switch (part.type) {
case ReplacePart::PartType::Constant: {
Shard *c = std::get<Shard *>(part.value);
Shard::retain(c);
pieces.push_back(c);
new_lines += c ? c->lines : 0;
break;
}
case ReplacePart::PartType::FullMatch:
Shard::retain(dropped);
pieces.push_back(dropped);
new_lines += dropped ? dropped->lines : 0;
break;
case ReplacePart::PartType::CaptureGroup: {
uint8_t idx = std::get<uint8_t>(part.value);
if (idx <= 9) {
const RegexGroup &group = match.groups[idx - 1];
if (group.start != UINT64_MAX) {
uint64_t ls = group.start - match.start;
uint64_t le = group.end - match.start;
auto [a, b] = Shard::split(dropped, ls);
auto [g, c] = Shard::split(b, le - ls);
Shard::release(a);
Shard::release(b);
Shard::release(c);
pieces.push_back(g);
new_lines += g ? g->lines : 0;
}
}
break;
}
}
}
Shard::release(dropped);
if (old_lines || new_lines) {
uint64_t report_line = (uint64_t)((int64_t)orig_line + line_delta);
if (on_edit)
on_edit(report_line, old_lines, new_lines);
line_delta += (int64_t)new_lines - (int64_t)old_lines;
}
orig_line += old_lines;
cursor = match.end;
}
pieces.push_back(remaining);
for (auto &part : replace_parts)
if (part.type == ReplacePart::PartType::Constant)
Shard::release(std::get<Shard *>(part.value));
std::vector<Shard *> compact;
compact.reserve(pieces.size());
for (Shard *p : pieces) {
if (p && p->length > 0)
compact.push_back(p);
else if (p)
Shard::release(p);
}
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
Shard::release(root);
return new_root;
}
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (!root)
throw ed_error("Invalid line number.");
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)pattern.data(),
pattern.size(),
PCRE2_UTF | PCRE2_EXTENDED,
&errornumber,
&erroroffset,
NULL
);
if (re == NULL)
throw ed_error("Can't compile regex.");
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
if (!match_data) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start + 1) % (root->lines + 1);
LineIterator it(root, at, Direction::Forward);
std::string line;
while (it.next(&line)) {
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at++;
}
at = 0;
LineIterator it2(root, at, Direction::Forward);
while (it2.next(&line)) {
if (at > start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at++;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("No line matched.");
}
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (!root)
throw ed_error("Invalid line number.");
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)pattern.data(),
pattern.size(),
PCRE2_UTF | PCRE2_EXTENDED,
&errornumber,
&erroroffset,
NULL
);
if (re == NULL)
throw ed_error("Can't compile regex.");
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
if (!match_data) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start == 0 ? root->lines : start - 1);
LineIterator it(root, at, Direction::Backward);
std::string line;
while (it.next(&line)) {
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at--;
}
at = root->lines;
LineIterator it2(root, at, Direction::Backward);
while (it2.next(&line)) {
if (at < start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at--;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("No line matched.");
}
} // namespace bed::internal::vase
@@ -1,15 +1,11 @@
#include "vase/search.h" #include "internal/vase/vase.h"
#include "vase/iterators/chunk.h"
std::vector<RegexMatch> regex_search( namespace bed::internal::vase {
Shard *root, std::string_view pattern_str, std::vector<RegexMatch> _regex_search(
uint32_t start_offset, uint32_t end_offset, Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
std::string_view options
) { ) {
bool global = false; bool global = false;
uint32_t flags = PCRE2_MULTILINE; uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
const char *dot = "(?:(?!\\n)\\X)";
for (char c : options) { for (char c : options) {
switch (c) { switch (c) {
@@ -20,7 +16,7 @@ std::vector<RegexMatch> regex_search(
flags |= PCRE2_CASELESS; flags |= PCRE2_CASELESS;
break; break;
case 's': case 's':
dot = "(?:\\X)"; flags |= PCRE2_DOTALL;
break; break;
case 'x': case 'x':
flags |= PCRE2_EXTENDED; flags |= PCRE2_EXTENDED;
@@ -42,36 +38,9 @@ std::vector<RegexMatch> regex_search(
int errornumber; int errornumber;
PCRE2_SIZE erroroffset; PCRE2_SIZE erroroffset;
std::string out;
out.reserve(pattern_str.size() * 2);
bool escaped = false;
for (char c : pattern_str) {
// TODO: also dont switch out in [.] style groups.
if (escaped) {
out += c;
escaped = false;
continue;
}
if (c == '\\') {
out += c;
escaped = true;
continue;
}
if (c == '.') {
out += dot;
continue;
}
out += c;
}
pcre2_code *re = pcre2_compile( pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)out.data(), (PCRE2_SPTR)pattern.data(),
out.size(), pattern.size(),
flags, flags,
&errornumber, &errornumber,
&erroroffset, &erroroffset,
@@ -83,40 +52,40 @@ std::vector<RegexMatch> regex_search(
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
ChunkIterator it(root, Direction::Forward); PetalIterator it(root, Direction::Forward);
it.seek_offset(start_offset); it.seek_offset(start_offset);
const char *data; const char *data;
uint32_t length; uint64_t length;
char buf[2048]; char buf[2048];
uint32_t global_offset = start_offset; uint64_t global_offset = start_offset;
int64_t offset = -1; uint64_t offset = UINT64_MAX;
auto record_match = [&](int rc, PCRE2_SIZE *ovector) { auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
if (global_offset + (uint32_t)ovector[1] > end_offset || ovector[0] == ovector[1]) if (global_offset + (uint64_t)ovector[1] > end_offset)
return; return;
RegexMatch match{ RegexMatch match{
.start = global_offset + (uint32_t)ovector[0], .start = global_offset + (uint64_t)ovector[0],
.end = global_offset + (uint32_t)ovector[1] .end = global_offset + (uint64_t)ovector[1]
}; };
for (int i = 1; i < rc && i <= 9; ++i) { for (int i = 1; i < rc && i <= 9; ++i) {
PCRE2_SIZE s = ovector[2 * i]; PCRE2_SIZE s = ovector[2 * i];
PCRE2_SIZE e = ovector[2 * i + 1]; PCRE2_SIZE e = ovector[2 * i + 1];
if (s != PCRE2_UNSET) { if (s != PCRE2_UNSET) {
match.groups[i].start = global_offset + (uint32_t)s; match.groups[i - 1].start = global_offset + (uint64_t)s;
match.groups[i].end = global_offset + (uint32_t)e; match.groups[i - 1].end = global_offset + (uint64_t)e;
} }
} }
results.push_back(std::move(match)); results.push_back(std::move(match));
}; };
auto skip_to_next_line = [&](const char *&data, uint32_t &length, int64_t &offset, uint32_t &global_offset) { auto skip_to_next_line = [&](const char *&data, uint64_t &length, uint64_t &offset, uint64_t &global_offset) {
while (true) { while (true) {
const char *p = (const char *)memchr(data + offset, '\n', length - offset); const char *p = (const char *)memchr(data + offset, '\n', length - offset);
if (p) { if (p) {
uint32_t nl = p - data; uint64_t nl = p - data;
offset = nl + 1; offset = nl + 1;
return; return;
} }
@@ -129,7 +98,7 @@ std::vector<RegexMatch> regex_search(
}; };
while (global_offset < end_offset) { while (global_offset < end_offset) {
if (offset == -1) { if (offset == UINT64_MAX) {
bool more = it.next(&data, &length); bool more = it.next(&data, &length);
if (!more) if (!more)
break; break;
@@ -137,7 +106,7 @@ std::vector<RegexMatch> regex_search(
} }
while (offset < length) { while (offset < length) {
if (offset == -1) if (offset == UINT64_MAX)
break; break;
int rc = pcre2_match( int rc = pcre2_match(
@@ -151,15 +120,15 @@ std::vector<RegexMatch> regex_search(
); );
if (rc == PCRE2_ERROR_PARTIAL) { if (rc == PCRE2_ERROR_PARTIAL) {
uint32_t buflen = 0; uint64_t buflen = 0;
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
uint32_t partial_start = ovector[0]; uint64_t partial_start = ovector[0];
uint32_t partial_length = length - partial_start; uint64_t partial_length = length - partial_start;
if (partial_length >= 2048) { if (partial_length >= 2048) {
global_offset += offset; global_offset += offset;
offset = -1; offset = UINT64_MAX;
continue; continue;
} }
@@ -168,7 +137,7 @@ std::vector<RegexMatch> regex_search(
memcpy(buf, data + partial_start, partial_length); memcpy(buf, data + partial_start, partial_length);
buflen += partial_length; buflen += partial_length;
offset = -1; offset = UINT64_MAX;
bool exhausted = false; bool exhausted = false;
while (true) { while (true) {
@@ -180,7 +149,7 @@ std::vector<RegexMatch> regex_search(
exhausted = true; exhausted = true;
break; break;
} }
uint32_t l = std::min(length, 2048 - buflen); uint64_t l = std::min(length, 2048 - buflen);
memcpy(buf + buflen, data, l); memcpy(buf + buflen, data, l);
buflen += l; buflen += l;
@@ -214,7 +183,7 @@ std::vector<RegexMatch> regex_search(
} }
if (exhausted) { if (exhausted) {
uint32_t search_from = 0; uint64_t search_from = 0;
while (search_from <= buflen) { while (search_from <= buflen) {
int rc = pcre2_match( int rc = pcre2_match(
re, re,
@@ -230,7 +199,7 @@ std::vector<RegexMatch> regex_search(
PCRE2_SIZE *ov = pcre2_get_ovector_pointer(match_data); PCRE2_SIZE *ov = pcre2_get_ovector_pointer(match_data);
record_match(rc, ov); record_match(rc, ov);
if (global) { if (global) {
search_from = (ov[1] == ov[0]) ? (uint32_t)ov[1] + 1 : (uint32_t)ov[1]; search_from = (ov[1] == ov[0]) ? (uint64_t)ov[1] + 1 : (uint64_t)ov[1];
} else { } else {
const void *p = memchr(buf + ov[1], '\n', buflen - ov[1]); const void *p = memchr(buf + ov[1], '\n', buflen - ov[1]);
if (!p) if (!p)
@@ -238,7 +207,7 @@ std::vector<RegexMatch> regex_search(
search_from = (const char *)p - buf + 1; search_from = (const char *)p - buf + 1;
} }
} }
offset = -1; offset = UINT64_MAX;
} }
continue; continue;
@@ -246,7 +215,7 @@ std::vector<RegexMatch> regex_search(
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data); PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
record_match(rc, ovector); record_match(rc, ovector);
if (global) { if (global) {
if ((int64_t)ovector[1] == offset) if (ovector[1] == offset)
offset++; offset++;
else else
offset = ovector[1]; offset = ovector[1];
@@ -264,7 +233,7 @@ std::vector<RegexMatch> regex_search(
} }
global_offset += offset; global_offset += offset;
offset = -1; offset = UINT64_MAX;
} }
pcre2_match_data_free(match_data); pcre2_match_data_free(match_data);
@@ -272,3 +241,4 @@ std::vector<RegexMatch> regex_search(
return results; return results;
} }
} // namespace bed::internal::vase
+433
View File
@@ -0,0 +1,433 @@
#include "internal/io/io.h"
#include "internal/vase/vase.h"
namespace bed::internal::vase {
void Shard::retain(Shard *n) {
if (n)
n->refs++;
};
void Shard::release(Shard *n) {
if (!n || --n->refs > 0)
return;
if (n->kind == Shard::Kind::Branch) {
release(((Branch *)n)->left);
release(((Branch *)n)->right);
delete (Branch *)n;
} else {
((Petal *)n)->source->release();
delete (Petal *)n;
}
}
int height(Shard *n) {
return n ? n->height : 0;
}
int balance_factor(Shard *n) {
Branch *b = (Branch *)n;
return height(b->left) - height(b->right);
}
Shard *rotate_right(Branch *z) {
Branch *y = (Branch *)z->left;
Shard *middle = new Branch(y->right, z->right);
Shard *out = new Branch(y->left, middle);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *rotate_left(Branch *z) {
Branch *y = (Branch *)z->right;
Shard *middle = new Branch(z->left, y->left);
Shard *out = new Branch(middle, y->right);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *balance(Shard *node) {
if (!node || node->kind == Shard::Kind::Petal)
return node;
Branch *b = (Branch *)node;
int bf = balance_factor(node);
if (bf > 1) {
Branch *left = (Branch *)b->left;
if (balance_factor(left) < 0) {
Shard::retain(left);
auto new_left = rotate_left(left);
auto rebuilt = new Branch(new_left, b->right);
auto result = rotate_right((Branch *)rebuilt);
Shard::release(new_left);
Shard::release(b);
return result;
}
return rotate_right(b);
}
if (bf < -1) {
Branch *right = (Branch *)b->right;
if (balance_factor(right) > 0) {
Shard::retain(right);
auto new_right = rotate_right(right);
auto rebuilt = new Branch(b->left, new_right);
auto result = rotate_left((Branch *)rebuilt);
Shard::release(new_right);
Shard::release(b);
return result;
}
return rotate_left(b);
}
return node;
}
Shard *Shard::concat(Shard *a, Shard *b) {
if (!a)
return (Shard::retain(b), b);
if (!b)
return (Shard::retain(a), a);
if (a->height > b->height + 1) {
Branch *ba = (Branch *)a;
Shard *r = concat(ba->right, b);
Shard *out = balance(new Branch(ba->left, r));
Shard::release(r);
return out;
}
if (b->height > a->height + 1) {
Branch *bb = (Branch *)b;
Shard *l = concat(a, bb->left);
Shard *out = balance(new Branch(l, bb->right));
Shard::release(l);
return out;
}
return balance(new Branch(a, b));
}
std::pair<Shard *, Shard *> Shard::split(Shard *n, uint64_t offset) {
if (!n)
return {nullptr, nullptr};
if (offset == 0) {
Shard::retain(n);
return {nullptr, n};
}
if (offset == n->length) {
Shard::retain(n);
return {n, nullptr};
}
if (n->kind == Shard::Kind::Branch) {
Branch *b = (Branch *)n;
if (offset < b->left->length) {
auto [a, b2] = split(b->left, offset);
Shard *right = concat(b2, b->right);
Shard::release(b2);
return {a, right};
} else {
auto [a, b2] = split(b->right, offset - b->left->length);
Shard *left = concat(b->left, a);
Shard::release(a);
return {left, b2};
}
} else {
Petal *p = (Petal *)n;
uint64_t count[2]{0};
const char *c = p->source->read(p->pos);
const char *start = c;
const char *end = c + p->length;
while (c < end) {
const char *nl = (const char *)memchr(c, '\n', end - c);
if (!nl)
break;
if ((uint64_t)(nl - start) < offset)
count[0]++;
else
count[1]++;
c = nl + 1;
}
auto left = new Petal(offset, count[0], p->source, p->pos);
auto right = new Petal(p->length - offset, count[1], p->source, p->pos + offset);
return {left, right};
}
}
Shard *Shard::merge_leaves(Shard *a, Shard *b) {
if (a->kind != Shard::Kind::Petal || b->kind != Shard::Kind::Petal)
return concat(a, b);
if (a->length + b->length > PETAL_SIZE_MAX)
return concat(a, b);
Petal *pa = (Petal *)a;
Petal *pb = (Petal *)b;
if (!(pa->source == pb->source && pa->pos + pa->length == pb->pos))
return concat(a, b);
return new Petal(
pa->length + pb->length,
pa->lines + pb->lines,
pa->source,
pa->pos
);
}
Shard *Shard::append(Shard *root, Shard *leaf) {
if (!root) {
Shard::retain(leaf);
return leaf;
}
if (root->kind == Shard::Kind::Petal)
return merge_leaves(root, leaf);
Branch *b = (Branch *)root;
auto new_right = append(b->right, leaf);
auto out = balance(new Branch(b->left, new_right));
Shard::release(new_right);
return out;
}
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
if (hi - lo == 1)
return pieces[lo];
uint64_t mid = lo + (hi - lo) / 2;
Shard *left = build(pieces, lo, mid);
Shard *right = build(pieces, mid, hi);
Shard *node = Shard::concat(left, right);
Shard::release(left);
Shard::release(right);
return node;
}
Shard *Shard::from_command(const char *cmd, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1) {
delete o;
return nullptr;
}
io::IO::cleanup();
FILE *pipe = popen(cmd, "r");
if (!pipe) {
delete o;
io::IO::enable_raw();
return nullptr;
}
std::vector<Shard *> pieces;
pieces.reserve(16);
uint64_t pos = 0;
char buf[PETAL_SIZE_MAX];
uint64_t buf_cursor = 0;
char ending[2] = {'\0', '\0'};
while (true) {
size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe);
buf_cursor += got;
if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) {
if (buf_cursor == 0)
break;
uint64_t lines = 0;
const char *p = buf;
const char *end = p + buf_cursor;
while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
}
if (buf_cursor >= 2) {
ending[0] = buf[buf_cursor - 2];
ending[1] = buf[buf_cursor - 1];
} else if (buf_cursor == 1) {
ending[0] = ending[1];
ending[1] = buf[0];
}
if (!write_all(dest_fd, buf, buf_cursor)) {
pclose(pipe);
delete o;
io::IO::enable_raw();
return nullptr;
}
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
pos += buf_cursor;
}
if (feof(pipe))
break;
if (ferror(pipe)) {
delete o;
io::IO::enable_raw();
return nullptr;
}
}
int status = pclose(pipe);
if (status == -1) {
delete o;
io::IO::enable_raw();
return nullptr;
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
delete o;
io::IO::enable_raw();
return nullptr;
}
if (pieces.empty()) {
delete o;
io::IO::enable_raw();
return nullptr;
}
if (posix_ending) {
if (ending[1] == '\n') {
Petal *last = (Petal *)pieces.back();
last->lines--;
last->length--;
if (last->length == 0) {
Shard::release(last);
pieces.pop_back();
last = nullptr;
if (!pieces.empty())
last = (Petal *)pieces.back();
}
if (last && ending[0] == '\r')
last->length--;
}
}
o->initialize();
io::IO::enable_raw();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1) {
delete o;
return nullptr;
}
int src_fd = open(path.c_str(), O_RDONLY);
if (src_fd == -1) {
delete o;
return nullptr;
}
uint64_t total = std::filesystem::file_size(path);
if (posix_ending && total > 0) {
char last;
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
delete o;
return nullptr;
}
if (last == '\n') {
total--;
if (total > 0) {
char s_last;
if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1) {
delete o;
return nullptr;
}
if (s_last == '\r')
total--;
}
}
}
if (total == 0) {
delete o;
return nullptr;
}
std::vector<Shard *> pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
char buf[PETAL_SIZE_MAX];
while (pos < total) {
uint64_t want = std::min(PETAL_SIZE_MAX, total - pos);
ssize_t got = pread(src_fd, buf, want, pos);
if (got <= 0) {
close(src_fd);
delete o;
return nullptr;
}
uint64_t take = (uint64_t)got;
uint64_t lines = 0;
const char *p = buf;
const char *end = p + take;
while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
}
if (!write_all(dest_fd, buf, take)) {
close(src_fd);
delete o;
return nullptr;
}
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
}
close(src_fd);
if (pieces.empty()) {
delete o;
return nullptr;
}
o->initialize();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1 || data == nullptr) {
delete o;
return nullptr;
}
uint64_t total = len;
if (posix_ending && total > 0) {
if (data[total - 1] == '\n') {
total--;
if (total > 0 && data[total - 1] == '\r')
total--;
}
}
if (total == 0) {
delete o;
return nullptr;
}
std::vector<Shard *> pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
while (pos < total) {
uint64_t take = std::min(PETAL_SIZE_MAX, total - pos);
const char *buf = data + pos;
uint64_t lines = 0;
const char *p = buf;
const char *end = buf + take;
while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
}
if (!write_all(dest_fd, buf, take)) {
delete o;
return nullptr;
}
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
}
if (pieces.empty()) {
delete o;
return nullptr;
}
o->initialize();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
}
} // namespace bed::internal::vase
+72
View File
@@ -0,0 +1,72 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
AppendStorage::AppendStorage(std::filesystem::path base_dir) {
base_dir /= "tapp.XXXXXX";
char *s = strdup(base_dir.c_str());
fd = mkstemp(s);
if (fd == -1)
throw std::runtime_error("mkstemp failed");
unlink(s);
free(s);
allocated_capacity = 1ull << 30;
if (ftruncate(fd, allocated_capacity) == -1)
throw std::runtime_error("ftruncate failed");
buf = (char *)mmap(nullptr, allocated_capacity, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (buf == MAP_FAILED)
throw std::runtime_error("mmap failed");
}
AppendStorage::~AppendStorage() {
if (buf && buf != MAP_FAILED)
munmap(buf, allocated_capacity);
if (fd != -1)
close(fd);
}
void AppendStorage::grow(uint64_t len) {
if (current_size + len > allocated_capacity) {
uint64_t new_capacity = allocated_capacity * 2;
if (new_capacity < current_size + len)
new_capacity = current_size + len + (1ull << 30);
if (ftruncate(fd, new_capacity) == -1)
throw std::runtime_error("ftruncate failed");
#if defined(__linux__)
void *new_buf = mremap(buf, allocated_capacity, new_capacity, MREMAP_MAYMOVE);
if (new_buf == MAP_FAILED)
throw std::runtime_error("mremap failed");
#else
munmap(buf, allocated_capacity);
void *new_buf = mmap(nullptr, new_capacity, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (new_buf == MAP_FAILED)
throw std::runtime_error("mmap failed");
#endif
allocated_capacity = new_capacity;
buf = (char *)new_buf;
}
}
uint64_t AppendStorage::append(const char c) {
grow(1);
buf[current_size++] = c;
return current_size - 1;
}
uint64_t AppendStorage::append(const char *text, uint64_t len) {
grow(len);
memcpy(buf + current_size, text, len);
uint64_t old_pos = current_size;
current_size += len;
return old_pos;
}
const char *AppendStorage::read(uint64_t pos) {
if (pos >= current_size)
return nullptr;
return buf + pos;
}
uint64_t AppendStorage::length() {
return current_size;
}
} // namespace bed::internal::vase
+47
View File
@@ -0,0 +1,47 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
OriginalStorage::OriginalStorage(std::filesystem::path base_dir) {
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
base_dir /= "tbuf.XXXXXX";
char *s = strdup(base_dir.c_str());
fd = mkstemp(s);
if (fd == -1)
throw std::runtime_error("mkstemp failed");
unlink(s);
free(s);
}
OriginalStorage::~OriginalStorage() {
if (buf)
munmap((char *)buf, len);
if (fd != -1)
close(fd);
}
void OriginalStorage::initialize() {
struct stat st;
if (fstat(fd, &st) == -1)
throw std::runtime_error("fstat failed");
len = (uint64_t)st.st_size;
buf = (const char *)mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
buf = nullptr;
throw std::runtime_error("mmap failed");
}
madvise((void *)buf, len, MADV_RANDOM);
close(fd);
fd = -1;
}
const char *OriginalStorage::read(uint64_t pos) {
if (pos >= len)
return nullptr;
return buf + pos;
}
uint64_t OriginalStorage::length() {
return len;
}
} // namespace bed::internal::vase
+327
View File
@@ -0,0 +1,327 @@
#include "internal/vase/vase.h"
#include "internal/io/io.h"
namespace bed::internal::vase {
std::string to_string(Shard *root) {
std::string out;
if (!root)
return out;
PetalIterator it(root, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len))
out.append(data, len);
return out;
}
std::string to_string(Shard *root, Range range) {
std::string out;
if (!root)
return out;
PetalIterator it(root, Direction::Forward);
uint64_t start = offset_of(root, range.start);
it.seek_offset(start);
const char *data;
uint64_t len;
uint64_t remaining = offset_of(root, range.end) - start;
while (remaining && it.next(&data, &len)) {
uint64_t n = std::min(len, remaining);
out.append(data, n);
remaining -= n;
}
return out;
}
Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key) {
uint64_t pos = ap->append(key);
Shard *inserted = new Petal(1, key == '\n', ap, pos);
auto [left, right] = Shard::split(root, offset_of(root, *point));
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
Shard *new_root = Shard::concat(left2, right);
Shard::release(left2);
Shard::release(right);
Shard::release(root);
if (key == '\n')
*point = {point->row + 1, 0};
else
point->col++;
return new_root;
}
Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len) {
while (len) {
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
_insert(ap, &root, point, data, chunk_size);
len -= chunk_size;
data += chunk_size;
}
return root;
}
void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len) {
if (len == 0)
return;
uint64_t offset = offset_of(*root, *point);
uint64_t pos = ap->append(data, len);
uint64_t lines = 0;
const char *start = data;
const char *last_line = start;
const char *end = start + len;
while ((data = (const char *)memchr(data, '\n', end - data))) {
++lines;
last_line = ++data;
}
uint64_t col = 0;
uint64_t remaining = end - last_line;
while (remaining) {
uint64_t n = grapheme_next_character_break_utf8(last_line, remaining);
last_line += n;
remaining -= n;
++col;
}
if (lines) {
point->row += lines;
point->col = col;
} else {
point->col += col;
}
Shard *inserted = new Petal(len, lines, ap, pos);
auto [left, right] = Shard::split(*root, offset);
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
Shard *new_root = Shard::concat(left2, right);
Shard::release(left2);
Shard::release(right);
Shard::release(*root);
*root = new_root;
}
Shard *erase(Shard *root, Range range) {
Point start = range.start;
Point end = range.end;
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset = offset_of(root, end);
uint64_t count = end_offset - start_offset;
auto [a, b] = Shard::split(root, start_offset);
auto [d, c] = Shard::split(b, count);
Shard *new_root = Shard::concat(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
return new_root;
}
Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len) {
root = erase(root, range);
return insert(ap, root, &range.start, data, len);
}
uint64_t offset_of(Shard *root, Point point) {
return offset_of(root, point.row) + point.col;
}
uint64_t offset_of(Shard *root, uint64_t line) {
if (!root)
return 0;
if (line == 0)
return 0;
if (line == root->lines + 1)
return root->length;
if (line > root->lines + 1)
throw ed_error("line out of range");
uint64_t offset = 0;
Shard *curr = root;
while (curr->kind == Shard::Kind::Branch) {
auto *b = (Branch *)curr;
if (line <= b->left->lines) {
curr = b->left;
} else {
line -= b->left->lines;
offset += b->left->length;
curr = b->right;
}
}
auto *petal = (Petal *)curr;
if (line == 0)
return offset;
const char *text = petal->source->read(petal->pos);
uint64_t local = 0;
while (line--) {
const char *nl = (const char *)memchr(text + local, '\n', petal->length - local);
if (!nl)
throw std::runtime_error("leaf line count is wrong.");
local = (nl - text) + 1;
}
return offset + local;
}
static Shard *newline(AppendStorage *ap) {
uint64_t pos = ap->append('\n');
return new Petal(1, true, ap, pos);
}
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
if (!root) {
if (line != 0)
throw ed_error("line out of range");
Shard::retain(text);
return text;
}
if (line > root->lines + 1)
throw ed_error("line out of range");
Shard::retain(text);
Shard *nl = newline(ap);
if (line <= root->lines) {
uint64_t offset = offset_of(root, line);
auto [left, right] = Shard::split(root, offset);
Shard *middle = Shard::concat(text, nl);
Shard::release(text);
Shard::release(nl);
Shard *new_root = Shard::concat(left, middle);
Shard::release(left);
Shard::release(middle);
middle = Shard::concat(new_root, right);
Shard::release(new_root);
Shard::release(right);
Shard::release(root);
return middle;
}
Shard *new_root = Shard::concat(root, nl);
Shard::release(root);
Shard::release(nl);
Shard *result = Shard::concat(new_root, text);
Shard::release(new_root);
Shard::release(text);
return result;
}
Shard *erase(Shard *root, 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 = offset_of(root, end + 1);
if (end == root->lines && start_offset)
start_offset--;
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard *new_root = Shard::concat(left, right);
Shard::release(left);
Shard::release(rest);
Shard::release(middle);
Shard::release(right);
Shard::release(root);
return new_root;
}
Shard *join(Shard *root, 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 offset = offset_of(root, start);
std::vector<Shard *> pieces;
pieces.reserve(end - start + 3);
auto [a, b] = Shard::split(root, offset);
pieces.push_back(a);
for (uint64_t line = start; line < end; line++) {
uint64_t nl_pos = offset_of(b, 1) - 1;
auto [content, rest] = Shard::split(b, nl_pos);
Shard::release(b);
auto [nl, next_b] = Shard::split(rest, 1);
Shard::release(rest);
Shard::release(nl);
pieces.push_back(content);
b = next_b;
}
pieces.push_back(b);
Shard *joined = Shard::build(pieces.data(), 0, pieces.size());
Shard::release(root);
return joined;
}
Shard *copy(Shard *root, 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::release(left);
Shard::release(rest);
Shard::release(right);
return middle;
}
void write_file(std::filesystem::path path, Shard *text) {
std::ofstream file(path, std::ios::binary);
if (!text)
return;
PetalIterator it(text, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len)) {
if (!file)
throw ed_error("Failed while writing file: " + path.string());
file.write(data, len);
}
file.write("\n", 1);
if (!file)
throw ed_error("Failed while writing file: " + path.string());
}
void write_command(const char *cmd, Shard *text) {
io::IO::cleanup();
FILE *pipe = popen(cmd, "w");
if (!pipe) {
io::IO::enable_raw();
throw ed_error("Failed to start command: " + std::string(cmd));
}
if (!text) {
int status = pclose(pipe);
if (status == -1)
throw ed_error("Failed to wait for command: " + std::string(cmd));
return;
}
PetalIterator it(text, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len))
fwrite(data, 1, len, pipe);
fwrite("\n", 1, 1, pipe);
int status = pclose(pipe);
io::IO::enable_raw();
if (status == -1)
throw ed_error("Failed to wait for command: " + std::string(cmd));
return;
}
} // namespace bed::internal::vase
+17 -12
View File
@@ -1,17 +1,22 @@
#include "bed.h"
#include "pch.h" #include "pch.h"
#include "vase/iterators/chunk.h"
#include "vase/iterators/line.h"
#include "vase/vase.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc < 2) std::vector<std::string> args(argv, argv + argc);
throw std::runtime_error("Please give filename."); try {
bed::internal::io::IO io = bed::internal::io::IO();
Vase vase = Vase(std::string(argv[1])); bed::BEd ed(args, io);
ed.run();
std::cout << "\n" } catch (bed::fatal_error &e) {
<< vase.to_string() if (e.code)
<< "\n"; printf("Fatal error: %s\n", e.what());
return e.code;
}
#ifndef DEBUG
catch (...) {
printf("Unexpected error.\n");
return 1;
}
#endif
return 0; return 0;
} }
-55
View File
@@ -1,55 +0,0 @@
#include "vase/buffer/append.h"
AppendBuffer::AppendBuffer() {
t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
}
AppendBuffer::~AppendBuffer() {
for (auto p : buf)
free(p);
}
uint32_t AppendBuffer::key(char c) {
if (t_offset == APPEND_CHUNK_SIZE) {
t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
t_offset = 0;
}
(*t_current)[t_offset++] = c;
return current_offset++;
}
uint32_t AppendBuffer::append(const char *text, uint32_t length) {
uint32_t start = current_offset;
while (length > 0) {
if (t_offset == APPEND_CHUNK_SIZE) {
t_current = (TChunk *)malloc(sizeof(TChunk));
buf.push_back(t_current);
t_offset = 0;
}
uint32_t copy = std::min(length, APPEND_CHUNK_SIZE - t_offset);
memcpy((*t_current) + t_offset, text, copy);
t_offset += copy;
current_offset += copy;
text += copy;
length -= copy;
}
return start;
}
const char *AppendBuffer::read(uint32_t pos, uint32_t *out_len) {
if (pos >= current_offset)
return nullptr;
uint32_t local_offset = pos % APPEND_CHUNK_SIZE;
if (out_len) {
uint32_t remaining = current_offset - pos;
uint32_t until_chunk_end = APPEND_CHUNK_SIZE - local_offset;
*out_len = std::min(remaining, until_chunk_end);
}
return &(*buf[pos / APPEND_CHUNK_SIZE])[local_offset];
}
inline uint32_t AppendBuffer::length() {
return current_offset;
}
-22
View File
@@ -1,22 +0,0 @@
#include "vase/buffer/original.h"
#include "io/file.h"
OriginalBuffer::OriginalBuffer(std::string path) {
read_file(path.c_str(), &buf, &len);
}
OriginalBuffer::~OriginalBuffer() {
free(buf);
}
const char *OriginalBuffer::read(uint32_t pos, uint32_t *out_len) {
if (pos >= len)
return nullptr;
if (out_len)
*out_len = len - pos;
return buf + pos;
}
uint32_t OriginalBuffer::length() {
return len;
}
-185
View File
@@ -1,185 +0,0 @@
#include "vase/iterators/chunk.h"
ChunkIterator::ChunkIterator(Shard *r, Direction dir)
: dir(dir), root(r) {
if (!root)
return;
Shard::retain(root);
}
ChunkIterator::~ChunkIterator() {
Shard::release(root);
}
void ChunkIterator::seek_offset(uint32_t offset) {
stack.clear();
petal = nullptr;
petal_offset = 0;
if (!root)
return;
if (offset >= root->length)
offset = root->length - 1;
uint32_t target = offset;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr;
petal_offset = target;
global_offset += target;
return;
} else {
auto *b = (Branch *)curr;
uint32_t left_len = b->left->length;
if (target < left_len) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
target -= left_len;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += left_len;
curr = b->right;
}
}
}
std::unreachable();
}
void ChunkIterator::seek_line(uint32_t line) {
stack.clear();
petal = nullptr;
petal_offset = 0;
bool last_line = false;
if (!root)
return;
if (line > root->lines + 1)
line = root->lines + 1;
if (line == root->lines + 1)
last_line = true;
Shard *curr = root;
while (curr) {
if (curr->kind == Shard::ShardKind::Petal) {
petal = (Petal *)curr;
if (last_line) {
petal_offset = petal->length;
global_offset += petal->length;
} else {
const char *text = nullptr;
uint32_t remaining = 0;
uint32_t offset = 0;
while (line) {
if (!text) {
uint32_t got = 0;
text = petal->source->read(petal->pos + offset, &got);
remaining = std::min(got, petal->length - offset);
}
const char *c = (const char *)memchr(text, '\n', remaining);
if (!c) {
offset += remaining;
text = nullptr;
continue;
}
remaining -= (c - text) + 1;
offset += (c - text) + 1;
text = c + 1;
line--;
}
if (dir == Direction::Backward)
--offset;
petal_offset = offset;
global_offset += offset;
}
return;
} else {
auto *b = (Branch *)curr;
uint32_t left_lines = b->left->lines;
if (line <= left_lines) {
if (dir == Direction::Forward)
stack.push_back(b->right);
curr = b->left;
} else {
line -= left_lines;
if (dir == Direction::Backward)
stack.push_back(b->left);
global_offset += b->left->length;
curr = b->right;
}
}
}
std::unreachable();
}
uint32_t ChunkIterator::byte_offset() {
return global_offset;
}
bool ChunkIterator::next(const char **data, uint32_t *out_len) {
if (dir == Direction::Forward) {
while (true) {
if (petal) {
if (petal_offset == petal->length) {
petal = nullptr;
continue;
}
uint32_t remaining = petal->length - petal_offset;
uint32_t got = 0;
const char *chunk = petal->source->read(petal->pos + petal_offset, &got);
if (got == 0) {
petal = nullptr;
petal_offset = 0;
return false;
}
uint32_t take = std::min(got, remaining);
*data = chunk;
*out_len = take;
petal_offset += take;
return true;
}
if (stack.empty())
return false;
auto s = stack.back();
stack.pop_back();
while (s->kind == Shard::ShardKind::Branch) {
Branch *b = (Branch *)s;
stack.push_back(b->right);
s = b->left;
}
petal = (Petal *)s;
petal_offset = 0;
}
} else {
while (true) {
if (petal) {
if (petal_offset == 0) {
petal = nullptr;
continue;
}
uint32_t got = 0;
*data = petal->source->read(petal->pos, &got);
if (petal_offset > got) {
uint32_t got2 = 0;
*data = petal->source->read(petal->pos + got, &got2);
*out_len = std::min(got2, petal_offset - got);
petal_offset = got;
return true;
} else {
*out_len = std::min(got, petal_offset);
petal_offset = 0;
return true;
}
}
if (stack.empty())
return false;
auto s = stack.back();
stack.pop_back();
while (s->kind == Shard::ShardKind::Branch) {
Branch *b = (Branch *)s;
stack.push_back(b->left);
s = b->right;
}
petal = (Petal *)s;
petal_offset = petal->length;
}
}
}
-65
View File
@@ -1,65 +0,0 @@
#include "vase/iterators/line.h"
LineIterator::LineIterator(Shard *r, uint32_t start_line, Direction dir)
: it(r, dir), dir(dir) {
it.seek_line(start_line + (dir == Direction::Backward));
if (!it.next(&chunk, &len))
chunk = nullptr;
}
uint32_t LineIterator::byte_offset() {
return 0;
}
bool LineIterator::next(std::string &line) {
line.clear();
if (!chunk)
return false;
if (dir == Direction::Forward) {
while (true) {
const char *nl = (const char *)memchr(chunk, '\n', len);
if (!nl) {
if (len && chunk[len - 1] == '\r')
--len;
line.append(chunk, len);
if (!it.next(&chunk, &len)) {
chunk = nullptr;
return true;
}
continue;
}
const char *end = nl;
if (end > chunk && *(end - 1) == '\r')
--end;
line.append(chunk, end);
uint32_t consumed = (nl - chunk) + 1;
chunk += consumed;
len -= consumed;
return true;
}
} else {
while (true) {
const char *nl = (const char *)memrchr(chunk, '\n', len);
if (!nl) {
if (len && chunk[len - 1] == '\n')
--len;
if (len && chunk[len - 1] == '\r')
--len;
line.insert(0, chunk, len);
if (!it.next(&chunk, &len)) {
chunk = nullptr;
return true;
}
continue;
}
const char *end = chunk + len;
const char *start = nl + 1;
const char *line_end = end;
if (line_end > start && *(line_end - 1) == '\r')
--line_end;
line.insert(0, start, line_end - start);
len = nl - chunk;
return true;
}
}
}
-253
View File
@@ -1,253 +0,0 @@
#include "vase/shard.h"
void Shard::retain(Shard *n) {
n->refs++;
};
void Shard::release(Shard *n) {
if (!n || --n->refs > 0)
return;
if (n->kind == Shard::ShardKind::Branch) {
release(((Branch *)n)->left);
release(((Branch *)n)->right);
delete (Branch *)n;
} else {
delete (Petal *)n;
}
}
int height(Shard *n) {
return n ? n->height : 0;
}
int balance_factor(Shard *n) {
Branch *b = (Branch *)n;
return height(b->left) - height(b->right);
}
Shard *rotate_right(Branch *z) {
Branch *y = (Branch *)z->left;
Shard *middle = new Branch(y->right, z->right);
Shard *out = new Branch(y->left, middle);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *rotate_left(Branch *z) {
Branch *y = (Branch *)z->right;
Shard *middle = new Branch(z->left, y->left);
Shard *out = new Branch(middle, y->right);
Shard::release(middle);
Shard::release(z);
return out;
}
Shard *balance(Shard *node) {
if (!node || node->kind == Shard::ShardKind::Petal)
return node;
Branch *b = (Branch *)node;
int bf = balance_factor(node);
if (bf > 1) {
Branch *left = (Branch *)b->left;
if (balance_factor(left) < 0) {
Shard::retain(left);
auto new_left = rotate_left(left);
auto rebuilt = new Branch(new_left, b->right);
auto result = rotate_right((Branch *)rebuilt);
Shard::release(new_left);
Shard::release(b);
return result;
}
return rotate_right(b);
}
if (bf < -1) {
Branch *right = (Branch *)b->right;
if (balance_factor(right) > 0) {
Shard::retain(right);
auto new_right = rotate_right(right);
auto rebuilt = new Branch(b->left, new_right);
auto result = rotate_left((Branch *)rebuilt);
Shard::release(new_right);
Shard::release(b);
return result;
}
return rotate_left(b);
}
return node;
}
Shard *merge(Shard *a, Shard *b) {
if (!a)
return b ? (Shard::retain(b), b) : nullptr;
if (!b)
return (Shard::retain(a), a);
if (a->height > b->height + 1) {
Branch *ba = (Branch *)a;
Shard *r = merge(ba->right, b);
Shard *out = balance(new Branch(ba->left, r));
Shard::release(r);
return out;
}
if (b->height > a->height + 1) {
Branch *bb = (Branch *)b;
Shard *l = merge(a, bb->left);
Shard *out = balance(new Branch(l, bb->right));
Shard::release(l);
return out;
}
return balance(new Branch(a, b));
}
std::pair<Shard *, Shard *> split_shard(Shard *n, uint32_t offset) {
if (!n)
return {nullptr, nullptr};
if (offset == 0) {
Shard::retain(n);
return {nullptr, n};
}
if (offset == n->length) {
Shard::retain(n);
return {n, nullptr};
}
if (n->kind == Shard::ShardKind::Branch) {
Branch *b = (Branch *)n;
if (offset < b->left->length) {
auto [a, b2] = split_shard(b->left, offset);
Shard *right = merge(b2, b->right);
Shard::release(b2);
return {a, right};
} else {
auto [a, b2] = split_shard(b->right, offset - b->left->length);
Shard *left = merge(b->left, a);
Shard::release(a);
return {left, b2};
}
} else {
Petal *p = (Petal *)n;
uint32_t count[2]{0};
uint32_t read_offset = 0;
while (read_offset < p->length) {
uint32_t got = 0;
const char *c = p->source->read(p->pos + read_offset, &got);
const char *end = c + std::min(got, p->length - read_offset);
const char *cursor = c;
while (cursor < end) {
const char *nl = (const char *)memchr(cursor, '\n', end - cursor);
if (!nl) {
cursor = end;
break;
}
uint32_t nl_pos = read_offset + (uint32_t)(nl - c);
if (nl_pos < offset)
count[0]++;
else
count[1]++;
cursor = nl + 1;
}
read_offset += (uint32_t)(cursor - c);
}
auto left = new Petal(
offset,
count[0],
p->source,
p->pos
);
auto right = new Petal(
p->length - offset,
count[1],
p->source,
p->pos + offset
);
return {left, right};
}
}
Shard *merge_leaves(Shard *a, Shard *b) {
if (a->kind != Shard::ShardKind::Petal || b->kind != Shard::ShardKind::Petal)
return merge(a, b);
Petal *pa = (Petal *)a;
Petal *pb = (Petal *)b;
if (!(pa->source == pb->source && pa->pos + pa->length == pb->pos))
return merge(a, b);
return new Petal(
pa->length + pb->length,
pa->lines + pb->lines,
pa->source,
pa->pos
);
}
Shard *append_leaf(Shard *root, Shard *leaf) {
if (!root)
return leaf;
if (root->kind == Shard::ShardKind::Petal && root->length < PETAL_SIZE_MAX)
return merge_leaves(root, leaf);
Branch *b = (Branch *)root;
auto new_right = append_leaf(b->right, leaf);
auto out = balance(new Branch(b->left, new_right));
Shard::release(new_right);
return out;
}
Shard *concat_shard(Shard *left, Shard *right) {
return merge(left, right);
}
Shard *build_balanced(Shard **pieces, uint32_t lo, uint32_t hi) {
if (hi - lo == 1)
return pieces[lo];
size_t mid = lo + (hi - lo) / 2;
Shard *left = build_balanced(pieces, lo, mid);
Shard *right = build_balanced(pieces, mid, hi);
Shard *node = new Branch(left, right);
Shard::release(left);
Shard::release(right);
return node;
}
Shard *create_shards(Buffer *o) {
std::vector<Shard *> pieces;
uint32_t pos = 0;
const uint32_t total = o->length();
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
while (pos < total) {
uint32_t len = 0;
const char *data = o->read(pos, &len);
uint32_t take = std::min(len, PETAL_SIZE_MAX);
uint32_t lines = 0;
const char *p = data;
const char *end = data + take;
while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
}
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
}
if (pieces.empty())
return nullptr;
if (pieces.size() == 1)
return pieces[0];
return build_balanced(pieces.data(), 0, pieces.size());
}
-469
View File
@@ -1,469 +0,0 @@
#include "vase/vase.h"
#include "utils/utils.h"
#include "vase/iterators/line.h"
#include "vase/search.h"
Vase::Vase(std::string path)
: original(path), append(),
root(create_shards(&original)) {
history.push_back(root);
Shard::retain(root);
history_top = 0;
}
Vase::~Vase() {
Shard::release(root);
for (auto s : history)
Shard::release(s);
}
uint32_t Vase::length() {
return root->length;
}
std::string Vase::to_string() {
std::string out;
flatten(root, out);
return out;
}
LineIterator Vase::iterate(uint32_t line) {
return LineIterator(root, line, Direction::Forward);
}
bool Vase::undo() {
if (history_top == 0)
return false;
Shard::release(root);
history_top--;
root = history[history_top];
Shard::retain(root);
return true;
}
bool Vase::redo() {
if (history_top + 1 >= history.size())
return false;
Shard::release(root);
history_top++;
root = history[history_top];
Shard::retain(root);
return true;
}
void Vase::snapshot() {
if (history[history_top] == root)
return;
while (history.size() > history_top + 1) {
Shard::release(history.back());
history.pop_back();
}
Shard::retain(root);
history.push_back(root);
history_top++;
}
void Vase::prune_history(uint32_t n) {
n = std::min(n, history_top);
for (uint32_t i = 0; i < n; ++i)
Shard::release(history[i]);
history.erase(history.begin(), history.begin() + n);
history_top -= n;
}
void Vase::insert(Point *point, char key) {
if (key == '\n')
*point = {point->row + 1, 0};
else
point->col++;
uint32_t pos = append.key(key);
Shard *inserted = new Petal(1, key == '\n', &append, pos);
auto [left, right] = split_shard(root, offset_of(*point));
Shard *left2 = append_leaf(left, inserted);
Shard *new_root = concat_shard(left2, right);
Shard::release(left);
Shard::release(right);
Shard::release(left2);
Shard::release(inserted);
Shard::release(root);
root = new_root;
}
void Vase::insert(Point *point, const char *data, uint32_t len) {
while (len) {
uint32_t chunk_size = std::min<uint32_t>(len, PETAL_SIZE_MAX);
_insert(point, data, chunk_size);
len -= chunk_size;
data += chunk_size;
}
}
void Vase::_insert(Point *point, const char *data, uint32_t len) {
if (len == 0)
return;
uint32_t offset = offset_of(*point);
uint32_t pos = append.append(data, len);
uint32_t lines = 0;
const char *start = data;
const char *last_line = start;
const char *end = start + len;
while ((data = (const char *)memchr(data, '\n', end - data))) {
++lines;
last_line = ++data;
}
uint32_t col = 0;
uint32_t remaining = end - last_line;
while (remaining) {
uint32_t n = grapheme_next_character_break_utf8(last_line, remaining);
last_line += n;
remaining -= n;
++col;
}
if (lines) {
point->row += lines;
point->col = col;
} else {
point->col += col;
}
Shard *inserted = new Petal(len, lines, &append, pos);
auto [left, right] = split_shard(root, offset);
Shard *left2 = append_leaf(left, inserted);
Shard *new_root = concat_shard(left2, right);
Shard::release(left);
Shard::release(right);
Shard::release(left2);
Shard::release(inserted);
Shard::release(root);
root = new_root;
}
void Vase::erase(Point *point, int64_t amount) {
if (amount == 0)
return;
Point start = *point;
Point end = *point;
if (amount < 0)
move_clusters(&start, amount);
else
move_clusters(&end, amount);
uint32_t start_offset = offset_of(start);
uint32_t end_offset = offset_of(end);
if (start_offset > end_offset)
std::swap(start_offset, end_offset);
uint32_t count = end_offset - start_offset;
auto [a, b] = split_shard(root, start_offset);
auto [d, c] = split_shard(b, count);
Shard *new_root = concat_shard(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
if (amount < 0)
*point = start;
}
void Vase::erase(Range range) {
Point start = range.start;
Point end = range.end;
uint32_t start_offset = offset_of(start);
uint32_t end_offset = offset_of(end);
uint32_t count = end_offset - start_offset;
auto [a, b] = split_shard(root, start_offset);
auto [d, c] = split_shard(b, count);
Shard *new_root = concat_shard(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
}
void Vase::replace(Range range, const char *data, uint32_t len) {
erase(range);
insert(&range.start, data, len);
}
void Vase::flatten(Shard *s, std::string &out) {
if (s->kind == Shard::ShardKind::Petal) {
auto *p = (Petal *)s;
uint32_t remaining = p->length;
uint32_t pos = p->pos;
while (remaining) {
uint32_t got;
const char *data = p->source->read(pos, &got);
uint32_t take = std::min(got, remaining);
out.append(data, take);
remaining -= take;
pos += take;
}
} else {
auto *b = (Branch *)s;
flatten(b->left, out);
flatten(b->right, out);
}
}
uint32_t Vase::offset_of(Point point) {
LineIterator it(root, point.row, Direction::Forward);
std::string line;
uint32_t offset = 0;
if (it.next(line)) {
const char *ptr = line.data();
uint32_t remaining = line.length();
while (point.col && remaining) {
uint32_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
remaining -= next_len;
ptr += next_len;
offset += next_len;
point.col--;
}
}
return it.byte_offset() + offset;
}
void Vase::move_clusters(Point *point, int64_t amount) {
if (amount == 0)
return;
if (amount < 0) {
amount = -amount;
LineIterator it(root, point->row, Direction::Backward);
while (amount > 0) {
std::string line;
if (!it.next(line))
return;
std::vector<uint32_t> clusters;
const char *ptr = line.data();
uint32_t remaining = line.size();
uint32_t byte = 0;
while (remaining) {
clusters.push_back(byte);
uint32_t len =
grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
byte += len;
}
while (amount > 0 && point->col > 0) {
point->col--;
amount--;
}
if (amount == 0)
return;
if (point->row == 0)
return;
point->row--;
point->col = clusters.size();
amount--;
}
} else {
LineIterator it(root, point->row, Direction::Forward);
while (amount > 0) {
std::string line;
if (!it.next(line))
return;
if (point->col > 0 || amount > 0) {
const char *ptr = line.data();
uint32_t remaining = line.size();
uint32_t col = 0;
while (col < point->col && remaining) {
uint32_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
col++;
}
while (amount > 0 && remaining) {
uint32_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
point->col++;
amount--;
}
}
if (amount > 0) {
point->row++;
point->col = 0;
amount--;
}
}
}
}
void Vase::clamp(Point *point) {
if (point->row > root->lines)
point->row = root->lines;
LineIterator it(root, point->row, Direction::Forward);
std::string line;
if (!it.next(line)) {
point->col = 0;
return;
}
uint32_t clusters = 0;
const char *ptr = line.data();
uint32_t remaining = line.size();
while (remaining) {
uint32_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
clusters++;
}
if (point->col > clusters)
point->col = clusters;
}
void Vase::move_lines(Point *point, int64_t amount) {
if (point->row + amount < 0)
return;
point->row += amount;
clamp(point);
}
struct ReplacePart {
enum struct PartType {
FullMatch,
CaptureGroup,
Constant
} type;
std::variant<uint8_t, Shard *> value;
};
std::vector<ReplacePart> parse_replace(AppendBuffer &buf, std::string_view s) {
std::vector<ReplacePart> parts;
std::string constant;
auto flush_constant = [&]() {
if (!constant.empty()) {
uint32_t lines = 0;
uint32_t pos = buf.append(constant.data(), (uint32_t)constant.size());
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::Constant,
.value = new Petal((uint32_t)constant.size(), lines, &buf, pos)
}
);
constant.clear();
}
};
for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == '\\' && i + 1 < s.size() && s[i + 1] == '$') {
constant.push_back('$');
++i;
continue;
}
if (c == '$' && i + 1 < s.size()) {
char next = s[i + 1];
if (next == '0') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::FullMatch,
.value = (uint8_t)0
}
);
++i;
continue;
}
if (next >= '1' && next <= '9') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::CaptureGroup,
.value = (uint8_t)(next - '0')
}
);
++i;
continue;
}
}
constant.push_back(c);
}
flush_constant();
return parts;
}
void Vase::regex_search_replace(
std::string_view pattern,
Point start, Point end,
std::string_view replace, std::string_view options
) {
std::vector<RegexMatch> matches = regex_search(root, pattern, offset_of(start), offset_of(end), options);
if (matches.empty())
return;
std::vector<ReplacePart> replace_parts = parse_replace(append, replace);
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
Shard *remaining = root;
Shard::retain(remaining);
uint32_t cursor = 0;
for (const RegexMatch &match : matches) {
uint32_t gap = match.start - cursor;
if (gap > 0) {
auto [keep, rest] = split_shard(remaining, gap);
Shard::release(remaining);
pieces.push_back(keep);
remaining = rest;
}
auto [dropped, rest2] = split_shard(remaining, match.end - match.start);
Shard::release(remaining);
remaining = rest2;
for (size_t i = 0; i < replace_parts.size(); ++i) {
const ReplacePart &part = replace_parts[i];
switch (part.type) {
case ReplacePart::PartType::Constant:
Shard::retain(std::get<Shard *>(part.value));
pieces.push_back(std::get<Shard *>(part.value));
break;
case ReplacePart::PartType::FullMatch:
Shard::retain(dropped);
pieces.push_back(dropped);
break;
case ReplacePart::PartType::CaptureGroup: {
uint8_t idx = std::get<uint8_t>(part.value);
if (idx <= 9) {
const RegexGroup &group = match.groups[idx - 1];
if (group.start != UINT32_MAX) {
uint32_t ls = group.start - match.start;
uint32_t le = group.end - match.start;
auto [a, b] = split_shard(dropped, ls);
auto [g, c] = split_shard(b, le - ls);
Shard::release(a);
Shard::release(b);
Shard::release(c);
pieces.push_back(g);
}
}
break;
}
}
}
Shard::release(dropped);
cursor = match.end;
}
pieces.push_back(remaining);
for (auto &part : replace_parts)
if (part.type == ReplacePart::PartType::Constant)
Shard::release(std::get<Shard *>(part.value));
std::vector<Shard *> compact;
compact.reserve(pieces.size());
for (Shard *p : pieces) {
if (p && p->length > 0)
compact.push_back(p);
else if (p)
Shard::release(p);
}
Shard *new_root = compact.empty() ? nullptr : build_balanced(compact.data(), 0, compact.size());
Shard::release(root);
root = new_root;
}