Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f67be87ee
|
||
|
|
88785436cf
|
||
|
|
d246559292
|
||
|
|
3346bc9f83
|
||
|
|
a8d51dc0b6
|
||
|
|
118934a393
|
||
|
|
f5ec5516fa
|
||
|
|
c325409dd0
|
||
|
|
0e4422b0c8
|
||
|
|
bb3ce7549a
|
||
|
|
a5794d9ab3
|
||
|
|
d6d3f31326
|
||
|
|
51676362b9
|
||
|
|
0760209417
|
||
|
|
0df03f418a
|
||
|
|
a276bf60da
|
||
|
|
3bfebe30ce
|
||
|
|
46bdb8cd0f
|
||
|
|
662df68ab4
|
||
|
|
cd9583ec34
|
||
|
|
0104f96d51
|
||
|
|
abbfcc352a
|
||
|
|
f8975a60d8
|
||
|
|
33dc9ad507
|
||
|
|
956bdd6039
|
||
|
|
151817973f
|
||
|
|
25f4d8efa8
|
||
|
|
569b69263a
|
||
|
|
c0f9c6a234
|
||
|
|
4b0f3b8fb7
|
||
|
|
24914028c0
|
||
|
|
0d126021d0
|
||
|
|
e44267a6fe
|
||
|
|
a996beb4aa
|
||
|
|
010f916d35
|
||
|
|
81bb65b7cf
|
||
|
|
49bf5a2e2d
|
||
|
|
41049b1ab6
|
||
|
|
4a87a1a834
|
||
|
|
44e58b3041
|
||
|
|
7a18aa0db2
|
||
|
|
87d9148b82
|
||
|
|
e19a8e6bba
|
||
|
|
afa60dd12f
|
||
|
|
b8eac7b2da
|
||
|
|
d7278251ec
|
||
|
|
c8a270378a
|
||
|
|
6f1c87400a
|
||
|
|
f4e3a190b8
|
||
|
|
dba279b036
|
||
|
|
2ac71085db
|
||
|
|
3563324a1c
|
||
|
|
1f3b53753a
|
||
|
|
387ea61efd
|
||
|
|
bea8ff18c0
|
||
|
|
2a903f761a
|
||
|
|
9680a18e05
|
||
|
|
556bb631f6
|
||
|
|
3e3fb6c39d
|
||
|
|
739d2e450e
|
||
|
|
6ea03d651a
|
||
|
|
1489b08d57
|
||
|
|
3a41af01f9
|
||
|
|
48fb8e3501
|
||
|
|
cd13557e15
|
||
|
|
cc5d475ab8
|
||
|
|
c077ba2dc8
|
||
|
|
e2a79c60d9
|
||
|
|
31a43e17bb
|
||
|
|
20ac202f88
|
||
|
|
512bdd522c
|
||
|
|
b852c604c8
|
||
|
|
f6d4987b8c
|
||
|
|
23b7b8c8a8
|
||
|
|
e56f784c9a
|
||
|
|
f044f69f1c
|
||
|
|
717c048d6f
|
||
|
|
fdb67b449d
|
||
|
|
04a27b30aa
|
||
|
|
0720a254a3
|
||
|
|
9841c2cf15
|
||
|
|
189b9ab6ca
|
||
|
|
5722731c6a
|
||
|
|
1f57c40eef
|
||
|
|
e253fa0efb
|
@@ -7,3 +7,4 @@ build/
|
|||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
sample.txt
|
sample.txt
|
||||||
|
old*
|
||||||
|
|||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# Base.
|
||||||
|
|
||||||
|
### `PCH`
|
||||||
|
|
||||||
|
Brings in all the external and standard headers. <br/>
|
||||||
|
Apart from c++std stuff we have:
|
||||||
|
- `mruby` and almost all it's subheaders.
|
||||||
|
- `pcre2` (For regex.)
|
||||||
|
- `grapheme.h` - The suckless libgrapheme (to find utf8 boundaries)
|
||||||
|
- `unicode_width.h` - To find terminal width of unicode char's.
|
||||||
|
|
||||||
|
*Maybe try removing some of them ive probably stopped using.*
|
||||||
|
|
||||||
|
### `definitions.h`
|
||||||
|
|
||||||
|
- Defines the `bed` namespace
|
||||||
|
- Forward declaration of `BEd` type needed by all.
|
||||||
|
- `fatal_error` definition, a msg, code (quits application on being thrown)
|
||||||
|
- `ed_error`. just a message (stops the current cycle of the ed repl.)
|
||||||
|
|
||||||
|
### `bed.h`
|
||||||
|
|
||||||
|
**As this is based heavily on the internals, and so ill look into it at the end.**
|
||||||
|
|
||||||
|
# `Internal`
|
||||||
|
|
||||||
|
## `Vase`
|
||||||
|
|
||||||
|
- This module doesn't depend on any other.
|
||||||
|
- It's namespace is `bed::internal::vase`
|
||||||
|
- Vase is the module to do with the actual storage and handling of a disk backed immutable and reference counted avl piece tree.
|
||||||
|
- It is a O(log n) per edit text datatype.
|
||||||
|
|
||||||
|
### `constants.h`
|
||||||
|
|
||||||
|
Defines `PETAL_SIZE_MAX` as `32 * 1024`.
|
||||||
|
- `PETAL_SIZE_MAX` limits teh size of a petal which makes multiple O(n) in petal searches O(1) as it is now bounded.
|
||||||
|
|
||||||
|
### `storage/`
|
||||||
|
|
||||||
|
A storage is something that stores the actual text, the text is actually stored in storages which are referenced by the trees.
|
||||||
|
|
||||||
|
#### `storage.h`
|
||||||
|
|
||||||
|
Defines the virtual `Storage` class.
|
||||||
|
It has the methods:
|
||||||
|
- `const char *read(uint64_t pos)`: reads text from position `pos` and returns a pointer to it.
|
||||||
|
- `uint64_t length()`: returns the length of the storage.
|
||||||
|
- `void retain() / release()`: Methods to help with refcounted storages.
|
||||||
|
|
||||||
|
#### `original.h`
|
||||||
|
|
||||||
|
Defines the `Original` type of storage.
|
||||||
|
This is a refcounted storage.
|
||||||
|
|
||||||
|
It has the fields:
|
||||||
|
- `atomic_uint64_t refs{0}`: refcount
|
||||||
|
- `const char *buf`: pointer to the actual text.
|
||||||
|
- `uint64_t len`
|
||||||
|
- `int fd = -1`: the file descriptor of teh undelying disk storage.
|
||||||
|
|
||||||
|
It adds the method `initialize` over the base class.
|
||||||
|
|
||||||
|
`src/vase/storage/original.cc`.
|
||||||
|
|
||||||
|
The constructor uses `mkstemp` to make a temp file, then unlinks it to hide it from the fs but keeps the fd alive.
|
||||||
|
|
||||||
|
`initialize` then `mmap`s the fd into a non modifyable pointer managed by the kernel then closes the fd itself.
|
||||||
|
- This is what gives us a pointer to the file contents that the kernel can load and evict from memory as needed, and therefore if we have say a 500MB file loaded, but the cursor is arount 200MB and the user is only editing a couple lines around that, the rest of the file will not be using your memory, it might be loaded but if needed the kernel can evict it.
|
||||||
|
|
||||||
|
The call site fills the fd before calling initialze, it cannot be modified after that.
|
||||||
|
|
||||||
|
#### `append.h`
|
||||||
|
|
||||||
|
Defines the `Append` type of storage.
|
||||||
|
This is not actually refcounted, it is to be owned by the application just once and reused all throughout.
|
||||||
|
It contains the actively typed stuff.
|
||||||
|
|
||||||
|
It has the fields:
|
||||||
|
- `const char *buf`: pointer to the actual text.
|
||||||
|
- `uint64_t allocated_capacity`: the amount of bytees that can be written to it without needing to increase the size.
|
||||||
|
- `uint64_t current_size`: Stores the size of actual text stored in it. (the cursor)
|
||||||
|
- `int fd = -1`: the file descriptor of the underlying disk storage.
|
||||||
|
|
||||||
|
It has the methods `append` (with 2 overloads) and private `grow` over the base class.
|
||||||
|
`retain` and `release` do nothing.
|
||||||
|
|
||||||
|
`src/vase/storage/append.cc`.
|
||||||
|
|
||||||
|
The constructor uses `mkstemp` an `unlink` similar to the original storage.
|
||||||
|
It then `ftruncate`s the file to `2^30` bytes or 1GiB this makes writing 1gb to the file possible but does'nt neccasarily use up 1gb on the disk.
|
||||||
|
But when `mmap`ing we set `PROT_READ | PROT_WRITE` and `MAP_SHARED` to be able to write edits to disk.
|
||||||
|
|
||||||
|
`grow` works by doubling the capacity `ftruncat`ing the fd to the new capacity and then on `linux` it uses `mremap` to remap to the new file size, but otherwise we `munmap` and `mmap` into the new size.
|
||||||
|
|
||||||
|
The append methods append text into the buffer (either a single char or char * + len), they may grow the storage size. (the char*+len version uses memcpy).
|
||||||
|
|
||||||
|
### `shard.h`
|
||||||
|
|
||||||
|
Defines the actual peice tree.
|
||||||
|
|
||||||
|
The tree is a polymorhic set of classes.
|
||||||
|
|
||||||
|
The base class `Shard` has:
|
||||||
|
- `enum Kind : uint8_t kind`: Branch or Petal.
|
||||||
|
- `uint16_t height`: the tree height (for avl balancing.)
|
||||||
|
- `atomic_uint32_t refs`: the refcount.
|
||||||
|
- `uint64_t length`: the length in bytes of this (sub)tree.
|
||||||
|
- `uint64_t lines`: the number of `\n` in this subtree.
|
||||||
|
- It starts refcount at 1.
|
||||||
|
|
||||||
|
The `Branch` class adds:
|
||||||
|
- `Shard *left/right` the subtrees.
|
||||||
|
- its constructor takes l, r and sets length/lines `(l->length + r->length)`
|
||||||
|
- and height `1 + std::max(l->height, r->height)`
|
||||||
|
|
||||||
|
The `Petal` (leaf) class adds:
|
||||||
|
- `Storage *` a pointer to the storage used.
|
||||||
|
- `uint64_t pos` the position in the storage its text starts at.
|
||||||
|
- it takes `(uint64_t length, uint64_t lines, Storage *source, uint64_t pos)`
|
||||||
|
- it retains `source`. (usefull for refcounted sotrages.)
|
||||||
|
|
||||||
|
A `nullptr` `Shard*` is an empty peice of text (i.e. valid).
|
||||||
|
|
||||||
|
`src/internal/vase/shard.cc`: defines a set of static function on the Shard class. (All the applications of it.)
|
||||||
|
|
||||||
|
the operations methods are:
|
||||||
|
- `retain/release` retain/releases them, if freeing a leaf it also releases the storage and for branches it releases both subtrees. nullptr's are ignored.
|
||||||
|
- `balance/rotate_(right/left) and height/balance factor` are for doing simple avl balancing operations.
|
||||||
|
- `concat` borrows 2 pointers to `Shard` and returns an owned Shard*.
|
||||||
|
- `split` borrows a `Shard` and returns a `pair<Shard*, Shard*>` of 2 owned shards.
|
||||||
|
- `merge_leaves` works similar to concat but it tries to merge the middle bit if they have their physical representation in order. for example if i have a shard `hell` and i type the letter `o` and because i'd been typing in order the Append storage will have text `hello` we can merge them into a single petal.
|
||||||
|
- `append` similar to concat, but uses merge_leaves, useful for small appends like when typing.
|
||||||
|
- `build` takes a pointer to an array of `Shard*` and start and end then makes a single avl balanced `Shard*` out of it it is generally faster than calling concat on each peice when we have many.
|
||||||
|
|
||||||
|
Then for building a Shard* in the first place we have:
|
||||||
|
- `from_command(const char *cmd)` runs the \0 terminated command string and reads its STDOUT and returns an owned Shard*.
|
||||||
|
- `from_file(const std::filesystem::path &path)` reads the file at path.
|
||||||
|
- `from_string(const char *data, uint64_t len)` loads the string given into a new storage, (normally inserting text can work by inserting into the append storage but this function loads it into a `OriginalStorage` object.).
|
||||||
|
These methods all clip the final newline if it exists.
|
||||||
|
And they also create a new `OriginalStorage` object.
|
||||||
|
|
||||||
|
### `vase.h`
|
||||||
|
|
||||||
|
### `iterators/`
|
||||||
|
|
||||||
|
#### `line.h`
|
||||||
|
|
||||||
|
#### `petal.h`
|
||||||
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ CFLAGS_RELEASE :=\
|
|||||||
-fomit-frame-pointer -DNDEBUG -s \
|
-fomit-frame-pointer -DNDEBUG -s \
|
||||||
-I./include -I./libs/unicode_width
|
-I./include -I./libs/unicode_width
|
||||||
|
|
||||||
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS) $(C_SANITIZER)
|
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
|
||||||
CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
|
CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
|
||||||
|
|
||||||
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
|
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
|
||||||
|
|||||||
@@ -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,8 @@ It should support:
|
|||||||
- Loading lsp features etc. automatically.
|
- Loading lsp features etc. automatically.
|
||||||
- Error handling.
|
- Error handling.
|
||||||
- And more.
|
- And more.
|
||||||
<br/>
|
|
||||||
<br/>
|
### TODO:
|
||||||
Not done yet.
|
|
||||||
|
- Make "g" command work.
|
||||||
|
- properly handle escapes for %q ' etc in ruby parser (rn everything is escapable.)
|
||||||
|
|||||||
@@ -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
|
||||||
];
|
];
|
||||||
@@ -71,26 +70,25 @@
|
|||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
make \
|
make \
|
||||||
MRBC=${mruby}/bin/mrbc \
|
MRUBY_CFLAGS="-I${mruby}/include" \
|
||||||
MRUBY_CFLAGS=-I${mruby}/include \
|
MRUBY_LIBS="-L${mruby}/lib -lmruby"
|
||||||
MRUBY_LIBS=-L${mruby}/lib -lmruby
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
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 +97,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
|
||||||
@@ -112,7 +109,6 @@
|
|||||||
export CC="ccache $CC"
|
export CC="ccache $CC"
|
||||||
export CXX="ccache $CXX"
|
export CXX="ccache $CXX"
|
||||||
|
|
||||||
export MRBC=${mruby}/bin/mrbc
|
|
||||||
export MRUBY_CFLAGS=-I${mruby}/include
|
export MRUBY_CFLAGS=-I${mruby}/include
|
||||||
export MRUBY_LIBS="-L${mruby}/lib -lmruby"
|
export MRUBY_LIBS="-L${mruby}/lib -lmruby"
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#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/scripting/ruby.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::scripting::RubyState mrb;
|
||||||
|
internal::trie::Trie<internal::functions::Function> functions;
|
||||||
|
internal::functions::Function no_op;
|
||||||
|
internal::functions::Function eof_op;
|
||||||
|
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
|
||||||
|
internal::theme::Theme theme;
|
||||||
|
std::unordered_map<std::string, internal::syntax::Language *> languages;
|
||||||
|
internal::io::IO io;
|
||||||
|
internal::vase::AppendStorage append{"/tmp"};
|
||||||
|
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
||||||
|
|
||||||
|
bool help_mode = false;
|
||||||
|
bool prompt_mode = true;
|
||||||
|
std::function<std::string(BEd &)> prompt = nullptr;
|
||||||
|
bool suppress_mode = false;
|
||||||
|
bool color_mode = false;
|
||||||
|
bool temporary_current = false;
|
||||||
|
std::string last_help = "";
|
||||||
|
std::string last_regex = "";
|
||||||
|
std::string last_symbol = "";
|
||||||
|
std::string last_replacement = "";
|
||||||
|
std::string last_shell = "";
|
||||||
|
|
||||||
|
internal::buffer::Range prev_1;
|
||||||
|
internal::buffer::Range prev_2;
|
||||||
|
internal::marks::MarksEngine marks;
|
||||||
|
|
||||||
|
BEd(std::vector<std::string> args);
|
||||||
|
~BEd();
|
||||||
|
|
||||||
|
internal::buffer::Buffer &buffer(const std::string &);
|
||||||
|
internal::buffer::Line ¤t();
|
||||||
|
internal::buffer::Range &prev();
|
||||||
|
void mark(uint8_t, internal::buffer::Line);
|
||||||
|
|
||||||
|
void print_help();
|
||||||
|
void handle(std::string_view cmd, bool eof);
|
||||||
|
void run();
|
||||||
|
void suffix_handle(char s);
|
||||||
|
bool escape_command(std::string &cmd, std::string_view filename);
|
||||||
|
};
|
||||||
|
} // namespace bed
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#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) {}
|
||||||
|
};
|
||||||
|
} // namespace bed
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "./types/clip.h"
|
||||||
|
#include "./types/generic.h"
|
||||||
|
#include "decl.h"
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#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,
|
||||||
|
History,
|
||||||
|
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(std::move(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 saved_hook() = 0;
|
||||||
|
virtual void load(BEd &ctx, vase::Shard *text) = 0;
|
||||||
|
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, 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 replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
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
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.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;
|
||||||
|
void saved_hook() override;
|
||||||
|
uint64_t lines() override;
|
||||||
|
uint64_t bytes() override;
|
||||||
|
void load(BEd &ctx, vase::Shard *text) override;
|
||||||
|
void set_filename(std::filesystem::path path) override;
|
||||||
|
std::filesystem::path filename() override;
|
||||||
|
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) override;
|
||||||
|
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
|
||||||
|
void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
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
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.h"
|
||||||
|
#include "readonly.h"
|
||||||
|
#include "shard.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct HistoryItem {
|
||||||
|
syntax::ParserSnapshot parse_state;
|
||||||
|
vase::Shard *text;
|
||||||
|
std::chrono::system_clock::time_point timestamp;
|
||||||
|
std::string summary;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GenericBuffer : ShardBuffer {
|
||||||
|
uint64_t base_version{0};
|
||||||
|
std::chrono::system_clock::time_point timestamp{std::chrono::system_clock::now()};
|
||||||
|
std::string action{"Created buffer."};
|
||||||
|
std::filesystem::path save_path{};
|
||||||
|
std::vector<HistoryItem> undo_stack;
|
||||||
|
std::vector<HistoryItem> redo_stack;
|
||||||
|
|
||||||
|
GenericBuffer(std::string name)
|
||||||
|
: ShardBuffer(name, nullptr, nullptr, Kind::Generic) {}
|
||||||
|
~GenericBuffer();
|
||||||
|
|
||||||
|
void language(BEd &ctx, std::string name);
|
||||||
|
void list_history(BEd &ctx);
|
||||||
|
ReadonlyBuffer *get_history(uint64_t version);
|
||||||
|
void snapshot(std::string action);
|
||||||
|
bool undo(BEd &ctx);
|
||||||
|
bool redo(BEd &ctx);
|
||||||
|
uint64_t prune(int);
|
||||||
|
void saved_hook() override;
|
||||||
|
bool waste() override;
|
||||||
|
void load(BEd &ctx, vase::Shard *text) override;
|
||||||
|
void set_filename(std::filesystem::path path) override;
|
||||||
|
std::filesystem::path filename() override;
|
||||||
|
void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) override;
|
||||||
|
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
|
||||||
|
void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.h"
|
||||||
|
#include "shard.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct ReadonlyBuffer : ShardBuffer {
|
||||||
|
bool useless = true;
|
||||||
|
|
||||||
|
ReadonlyBuffer(
|
||||||
|
std::string name, vase::Shard *root,
|
||||||
|
const syntax::ParserSnapshot &snapshot
|
||||||
|
) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {}
|
||||||
|
|
||||||
|
void saved_hook() override {}
|
||||||
|
bool waste() override {
|
||||||
|
return useless;
|
||||||
|
}
|
||||||
|
void load(BEd &, vase::Shard *) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
void set_filename(std::filesystem::path) override {}
|
||||||
|
std::filesystem::path filename() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
void substitute(BEd &, uint64_t, uint64_t, std::string &, std::string &, std::string &) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
void join(BEd &, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
void remove(BEd &, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
void append(BEd &, vase::Shard *, uint64_t) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
void replace(BEd &, vase::Shard *, uint64_t, uint64_t) override {
|
||||||
|
throw ed_error("This buffer is read-only.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../decl.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct ShardBuffer : Buffer {
|
||||||
|
vase::Shard *root;
|
||||||
|
syntax::ParserSnapshot parse{};
|
||||||
|
|
||||||
|
ShardBuffer(std::string name, vase::Shard *root, syntax::Language *language, Kind kind)
|
||||||
|
: Buffer(std::move(name), kind), root(root) {
|
||||||
|
vase::Shard::retain(root);
|
||||||
|
if (language)
|
||||||
|
parse = syntax::make_parser(root, lines(), language);
|
||||||
|
}
|
||||||
|
ShardBuffer(
|
||||||
|
std::string name,
|
||||||
|
vase::Shard *root,
|
||||||
|
const syntax::ParserSnapshot &snapshot,
|
||||||
|
Kind kind
|
||||||
|
) : Buffer(std::move(name), kind),
|
||||||
|
root(root),
|
||||||
|
parse(syntax::retain(snapshot)) {
|
||||||
|
vase::Shard::retain(root);
|
||||||
|
}
|
||||||
|
~ShardBuffer() {
|
||||||
|
vase::Shard::release(root);
|
||||||
|
syntax::release(parse);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t lines() override;
|
||||||
|
uint64_t bytes() override;
|
||||||
|
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
uint64_t next_closing(uint64_t start) override;
|
||||||
|
uint64_t prev_closing(uint64_t start) override;
|
||||||
|
uint64_t find_next(std::string_view pattern, uint64_t start) override;
|
||||||
|
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
#include "tokens.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);
|
||||||
|
BEd &bed;
|
||||||
|
|
||||||
|
static enum struct Mode {
|
||||||
|
PIPE,
|
||||||
|
TERMINAL
|
||||||
|
} mode;
|
||||||
|
|
||||||
|
std::string pipe_input;
|
||||||
|
std::deque<char> input_queue;
|
||||||
|
|
||||||
|
IO(BEd &);
|
||||||
|
~IO();
|
||||||
|
|
||||||
|
IO(const IO &) = delete;
|
||||||
|
IO &operator=(const IO &) = delete;
|
||||||
|
|
||||||
|
bool interactive() const {
|
||||||
|
return mode == Mode::TERMINAL;
|
||||||
|
}
|
||||||
|
void apply(const io::Token::Kind &t);
|
||||||
|
void reset();
|
||||||
|
void enable_mouse();
|
||||||
|
void disable_mouse();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
std::pair<std::string, bool> read_pipe();
|
||||||
|
|
||||||
|
KeyEvent read_key();
|
||||||
|
void write(const char *, uint64_t);
|
||||||
|
void write(std::string_view);
|
||||||
|
void write_line(std::string_view);
|
||||||
|
void run_pty(const std::string &);
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
struct Token {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
enum Kind : uint8_t {
|
||||||
|
TempCurrent,
|
||||||
|
BufferName,
|
||||||
|
AddressSeperator,
|
||||||
|
AddressSymbol,
|
||||||
|
Number,
|
||||||
|
Mark,
|
||||||
|
RubyFunction,
|
||||||
|
RubyArg,
|
||||||
|
Function,
|
||||||
|
Shell,
|
||||||
|
File,
|
||||||
|
Suffix,
|
||||||
|
Color1,
|
||||||
|
Color2,
|
||||||
|
Color3,
|
||||||
|
Color4,
|
||||||
|
Color5,
|
||||||
|
Warning,
|
||||||
|
Data,
|
||||||
|
Shebang,
|
||||||
|
Comment,
|
||||||
|
Error,
|
||||||
|
String,
|
||||||
|
Escape,
|
||||||
|
Interpolation,
|
||||||
|
Regexp,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
Char,
|
||||||
|
Keyword,
|
||||||
|
KeywordOperator,
|
||||||
|
Operator,
|
||||||
|
Namespace,
|
||||||
|
Class,
|
||||||
|
Module,
|
||||||
|
Type,
|
||||||
|
Constant,
|
||||||
|
VariableInstance,
|
||||||
|
VariableGlobal,
|
||||||
|
Annotation,
|
||||||
|
Directive,
|
||||||
|
Label,
|
||||||
|
Brace1,
|
||||||
|
Brace2,
|
||||||
|
Brace3,
|
||||||
|
Brace4,
|
||||||
|
Brace5,
|
||||||
|
Heading1,
|
||||||
|
Heading2,
|
||||||
|
Heading3,
|
||||||
|
Heading4,
|
||||||
|
Heading5,
|
||||||
|
Heading6,
|
||||||
|
Blockquote,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Code,
|
||||||
|
LanguageName,
|
||||||
|
LinkLabel,
|
||||||
|
ImageLabel,
|
||||||
|
Link,
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
Italic,
|
||||||
|
Bold,
|
||||||
|
Underline,
|
||||||
|
Strikethrough,
|
||||||
|
HorizontalRule,
|
||||||
|
Tag,
|
||||||
|
Attribute,
|
||||||
|
CheckDone,
|
||||||
|
CheckNotDone,
|
||||||
|
Count
|
||||||
|
} type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Highlight {
|
||||||
|
enum : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
Bold = 1 << 0,
|
||||||
|
Italic = 1 << 1,
|
||||||
|
Strikethrough = 1 << 2,
|
||||||
|
Underline = 1 << 3,
|
||||||
|
};
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#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<io::Token> *tokens;
|
||||||
|
CompletionContext *completion;
|
||||||
|
|
||||||
|
explicit Parser(
|
||||||
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
|
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||||
|
);
|
||||||
|
|
||||||
|
void token(io::Token::Kind);
|
||||||
|
void end_token();
|
||||||
|
|
||||||
|
char peek(uint16_t = 0); // == \0 if at eof.
|
||||||
|
std::string_view peek_str(uint16_t = UINT16_MAX);
|
||||||
|
void advance(uint16_t = 1);
|
||||||
|
|
||||||
|
void skip_ws();
|
||||||
|
|
||||||
|
void locator(AddressPromise &);
|
||||||
|
int64_t offset();
|
||||||
|
void address(AddressPromise &addr);
|
||||||
|
void addresses(std::vector<AddressPromise> &addresses);
|
||||||
|
|
||||||
|
void operation();
|
||||||
|
|
||||||
|
void parse();
|
||||||
|
static std::vector<io::Token> get_highlight(std::string_view cmd, BEd &bed);
|
||||||
|
static Command get_command(std::string_view, BEd &);
|
||||||
|
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::scripting {
|
||||||
|
struct RubyState {
|
||||||
|
mrb_state *state = nullptr;
|
||||||
|
int arena = 0;
|
||||||
|
|
||||||
|
explicit RubyState(BEd *ctx) : state(mrb_open()) {
|
||||||
|
if (!state)
|
||||||
|
throw fatal_error("Failed to initialize mruby.", 1);
|
||||||
|
state->ud = ctx;
|
||||||
|
arena = mrb_gc_arena_save(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
~RubyState() {
|
||||||
|
state->ud = nullptr;
|
||||||
|
if (state) {
|
||||||
|
mrb_gc_arena_restore(state, arena);
|
||||||
|
mrb_full_gc(state);
|
||||||
|
mrb_close(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RubyState(const RubyState &) = delete;
|
||||||
|
RubyState &operator=(const RubyState &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Block {
|
||||||
|
mrb_state *mrb = nullptr;
|
||||||
|
mrb_value proc = mrb_nil_value();
|
||||||
|
|
||||||
|
Block(mrb_state *mrb, mrb_value proc) noexcept
|
||||||
|
: mrb(mrb), proc(proc) {
|
||||||
|
mrb_gc_register(mrb, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block() noexcept = default;
|
||||||
|
|
||||||
|
~Block() noexcept {
|
||||||
|
if (!mrb_nil_p(proc) && mrb)
|
||||||
|
mrb_gc_unregister(mrb, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block(const Block &other) noexcept
|
||||||
|
: mrb(other.mrb), proc(other.proc) {
|
||||||
|
if (mrb && !mrb_nil_p(proc))
|
||||||
|
mrb_gc_register(mrb, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Block &operator=(const Block &other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (mrb && !mrb_nil_p(proc))
|
||||||
|
mrb_gc_unregister(mrb, proc);
|
||||||
|
mrb = other.mrb;
|
||||||
|
proc = other.proc;
|
||||||
|
if (mrb && !mrb_nil_p(proc))
|
||||||
|
mrb_gc_register(mrb, proc);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block(Block &&other) noexcept
|
||||||
|
: mrb(other.mrb), proc(other.proc) {
|
||||||
|
other.mrb = nullptr;
|
||||||
|
other.proc = mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Block &operator=(Block &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (mrb && !mrb_nil_p(proc))
|
||||||
|
mrb_gc_unregister(mrb, proc);
|
||||||
|
mrb = other.mrb;
|
||||||
|
proc = other.proc;
|
||||||
|
other.mrb = nullptr;
|
||||||
|
other.proc = mrb_nil_value();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_proc(mrb_state *mrb_, mrb_value new_proc) {
|
||||||
|
if (!mrb_nil_p(proc) && mrb)
|
||||||
|
mrb_gc_unregister(mrb, proc);
|
||||||
|
mrb = mrb_;
|
||||||
|
proc = new_proc;
|
||||||
|
mrb_gc_register(mrb, proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_value call(int argc = 0, mrb_value *argv = nullptr) const {
|
||||||
|
if (mrb_nil_p(proc))
|
||||||
|
return mrb_nil_value();
|
||||||
|
mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv);
|
||||||
|
if (!mrb->exc)
|
||||||
|
return result;
|
||||||
|
mrb_value exc = mrb_obj_value(mrb->exc);
|
||||||
|
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
|
||||||
|
std::string error;
|
||||||
|
if (mrb_string_p(msg))
|
||||||
|
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
|
||||||
|
auto *fatal_class = mrb_class_get(mrb, "FatalError");
|
||||||
|
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
|
||||||
|
mrb_value code =
|
||||||
|
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
int c = 1;
|
||||||
|
if (mrb_fixnum_p(code))
|
||||||
|
c = mrb_fixnum(code);
|
||||||
|
throw fatal_error(error, c);
|
||||||
|
}
|
||||||
|
auto *ed_class = mrb_class_get(mrb, "EdError");
|
||||||
|
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
throw ed_error(error);
|
||||||
|
}
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
throw ed_error("Ruby Exception: " + error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void register_basic(BEd &ctx);
|
||||||
|
std::string run(BEd &ctx, const std::string &str);
|
||||||
|
}; // namespace bed::internal::scripting
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/io/tokens.h"
|
||||||
|
#include "internal/trie/trie.h"
|
||||||
|
#include "internal/vase/vase.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
struct 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<io::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;
|
||||||
|
uint16_t height;
|
||||||
|
std::atomic_uint16_t refs;
|
||||||
|
bool is_branch() const {
|
||||||
|
return header & BRANCH_BIT;
|
||||||
|
}
|
||||||
|
uint64_t lines() const {
|
||||||
|
return header & LINES_MASK;
|
||||||
|
}
|
||||||
|
explicit ParseState(bool branch, uint64_t lines, uint16_t height)
|
||||||
|
: height(height), refs(1) {
|
||||||
|
header = lines;
|
||||||
|
header |= branch * BRANCH_BIT;
|
||||||
|
};
|
||||||
|
static void retain(ParseState *node);
|
||||||
|
static void release(Language &lang, ParseState *node);
|
||||||
|
static ParseState *build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi);
|
||||||
|
static ParseState *splice(
|
||||||
|
Language &lang, ParseState *node, vase::Shard *vase,
|
||||||
|
uint64_t line, uint64_t original, uint64_t final
|
||||||
|
);
|
||||||
|
static ParseState *concat(Language &lang, ParseState *a, ParseState *b);
|
||||||
|
static void *state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseStateBranch : ParseState {
|
||||||
|
ParseState *left;
|
||||||
|
ParseState *right;
|
||||||
|
ParseStateBranch(ParseState *l, ParseState *r)
|
||||||
|
: ParseState(
|
||||||
|
true, l->lines() + r->lines(),
|
||||||
|
1 + std::max(l->height, r->height)
|
||||||
|
),
|
||||||
|
left(l), right(r) {
|
||||||
|
retain(l);
|
||||||
|
retain(r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParseStateLeaf : ParseState {
|
||||||
|
static constexpr uint64_t MAX_CHUNK = 512;
|
||||||
|
uint32_t n{0};
|
||||||
|
static constexpr uint16_t IS_CLOSING = 0x8000;
|
||||||
|
static constexpr uint16_t LINE_MASK = 0x7fff;
|
||||||
|
uint16_t *blocks{nullptr};
|
||||||
|
void *state;
|
||||||
|
ParseStateLeaf(void *state, uint64_t lines, uint32_t n, uint16_t *blocks_)
|
||||||
|
: ParseState(false, lines, 1), n(n), state(state) {
|
||||||
|
blocks = (uint16_t *)malloc(sizeof(uint16_t) * n);
|
||||||
|
memcpy(blocks, blocks_, sizeof(uint16_t) * n);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsePieceBuilder {
|
||||||
|
Language ⟨
|
||||||
|
std::vector<ParseState *> pieces;
|
||||||
|
std::vector<uint16_t> blocks;
|
||||||
|
void *piece_state{nullptr};
|
||||||
|
void *prev_state{nullptr};
|
||||||
|
uint64_t chunk_start{0};
|
||||||
|
uint64_t chunk_lines{0};
|
||||||
|
ParsePieceBuilder(Language &lang, uint64_t first_line, void *entry_state)
|
||||||
|
: lang(lang), chunk_start(first_line) {
|
||||||
|
prev_state = lang.copy(entry_state);
|
||||||
|
}
|
||||||
|
~ParsePieceBuilder() {
|
||||||
|
if (prev_state)
|
||||||
|
lang.destroy(prev_state);
|
||||||
|
if (piece_state)
|
||||||
|
lang.destroy(piece_state);
|
||||||
|
}
|
||||||
|
ParsePieceBuilder(const ParsePieceBuilder &) = delete;
|
||||||
|
ParsePieceBuilder &operator=(const ParsePieceBuilder &) = delete;
|
||||||
|
void add(void *state, uint64_t line, const std::vector<ParseEvent> &events) {
|
||||||
|
if (chunk_lines == 0) {
|
||||||
|
chunk_start = line;
|
||||||
|
piece_state = lang.copy(prev_state);
|
||||||
|
}
|
||||||
|
for (const auto &ev : events) {
|
||||||
|
blocks.push_back(
|
||||||
|
(ev.closing ? ParseStateLeaf::IS_CLOSING : 0)
|
||||||
|
| (line - chunk_start)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++chunk_lines;
|
||||||
|
if (prev_state)
|
||||||
|
lang.destroy(prev_state);
|
||||||
|
prev_state = lang.copy(state);
|
||||||
|
if (chunk_lines == ParseStateLeaf::MAX_CHUNK)
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
void flush() {
|
||||||
|
if (chunk_lines == 0)
|
||||||
|
return;
|
||||||
|
pieces.push_back(new ParseStateLeaf(
|
||||||
|
piece_state,
|
||||||
|
chunk_lines,
|
||||||
|
blocks.size(),
|
||||||
|
blocks.data()
|
||||||
|
));
|
||||||
|
piece_state = nullptr;
|
||||||
|
blocks.clear();
|
||||||
|
chunk_lines = 0;
|
||||||
|
}
|
||||||
|
ParseState *finish() {
|
||||||
|
flush();
|
||||||
|
if (pieces.empty())
|
||||||
|
return nullptr;
|
||||||
|
ParseState *root = ParseState::build(lang, pieces.data(), 0, pieces.size());
|
||||||
|
pieces.clear();
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TreeCursor {
|
||||||
|
Language ⟨
|
||||||
|
ParseState *root;
|
||||||
|
ParseStateLeaf *leaf = nullptr;
|
||||||
|
ParseStateBranch *stack[64];
|
||||||
|
uint8_t depth = 0;
|
||||||
|
bool went_left[64];
|
||||||
|
TreeCursor(
|
||||||
|
Language &lang, ParseState *root,
|
||||||
|
uint64_t target_line, uint64_t *relative
|
||||||
|
);
|
||||||
|
~TreeCursor();
|
||||||
|
TreeCursor(const TreeCursor &) = delete;
|
||||||
|
TreeCursor &operator=(const TreeCursor &) = delete;
|
||||||
|
void next();
|
||||||
|
void prev();
|
||||||
|
ParseState *prefix();
|
||||||
|
ParseState *suffix();
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decl.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
struct MiniParser {
|
||||||
|
Language ⟨
|
||||||
|
void *start_state;
|
||||||
|
std::vector<std::pair<void *, std::vector<io::Token>>> lines;
|
||||||
|
|
||||||
|
explicit MiniParser(Language &lang, vase::Shard *vase, void *initial_state);
|
||||||
|
~MiniParser();
|
||||||
|
|
||||||
|
MiniParser(const MiniParser &) = delete;
|
||||||
|
MiniParser &operator=(const MiniParser &) = delete;
|
||||||
|
|
||||||
|
void dirty(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
void insert(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
void erase(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decl.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
struct ParserSnapshot {
|
||||||
|
ParseState *root = nullptr;
|
||||||
|
Language *lang = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
|
||||||
|
ParserSnapshot retain(const ParserSnapshot &snap);
|
||||||
|
void release(ParserSnapshot &snap);
|
||||||
|
|
||||||
|
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line);
|
||||||
|
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line);
|
||||||
|
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line);
|
||||||
|
|
||||||
|
struct Iterator {
|
||||||
|
ParserSnapshot snap;
|
||||||
|
std::optional<vase::Iterator> it;
|
||||||
|
void *state;
|
||||||
|
uint64_t at;
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
Iterator(uint64_t, ParserSnapshot, vase::Shard *);
|
||||||
|
~Iterator();
|
||||||
|
Iterator(const Iterator &) = delete;
|
||||||
|
Iterator &operator=(const Iterator &) = delete;
|
||||||
|
Iterator(Iterator &&other);
|
||||||
|
Iterator &operator=(Iterator &&other);
|
||||||
|
void next();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Iterator> get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target);
|
||||||
|
|
||||||
|
void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
|
||||||
|
struct Edit {
|
||||||
|
ParserSnapshot *target;
|
||||||
|
bool dirty = false;
|
||||||
|
uint64_t dirty_start = 0;
|
||||||
|
uint64_t dirty_end = 0;
|
||||||
|
int64_t edit_delta = 0;
|
||||||
|
|
||||||
|
explicit Edit(ParserSnapshot &snap) : target(&snap) {}
|
||||||
|
|
||||||
|
void mark_dirty(uint64_t start, uint64_t end);
|
||||||
|
void insert(uint64_t start, uint64_t count);
|
||||||
|
void erase(uint64_t start, uint64_t count);
|
||||||
|
void commit(vase::Shard *vase);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#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 << 4;
|
||||||
|
static constexpr uint8_t ALLOW_ESCAPE = 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
|
||||||
@@ -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 ¤t() {
|
||||||
|
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<io::Token> *tokens, std::vector<ParseEvent> *events
|
||||||
|
);
|
||||||
|
} // namespace bed::internal::syntax::ruby
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
#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 = {
|
||||||
|
// BEd methods.
|
||||||
|
"handle",
|
||||||
|
"register",
|
||||||
|
"unregister",
|
||||||
|
// Normal:
|
||||||
|
"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
|
||||||
@@ -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<io::Highlight, io::Token::Count> hl;
|
||||||
|
|
||||||
|
Theme();
|
||||||
|
|
||||||
|
io::Highlight get(const io::Token::Kind &token) const;
|
||||||
|
|
||||||
|
static Theme default_theme();
|
||||||
|
static Theme from_name(std::string_view name);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::theme
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
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();
|
||||||
|
std::pair<std::string, bool> run_pipe();
|
||||||
|
std::pair<std::string, bool> run_terminal();
|
||||||
|
void redraw();
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/syntax/miniparser.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui::text_mode {
|
||||||
|
struct TextMode {
|
||||||
|
struct Row {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t length;
|
||||||
|
};
|
||||||
|
using Lines = std::vector<Row>;
|
||||||
|
vase::Shard *vase;
|
||||||
|
vase::Point cursor;
|
||||||
|
std::optional<syntax::MiniParser> parser;
|
||||||
|
std::vector<Lines> lines;
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t height;
|
||||||
|
uint16_t term_height;
|
||||||
|
uint16_t term_width;
|
||||||
|
BEd &bed;
|
||||||
|
|
||||||
|
TextMode(BEd &, vase::Shard *, syntax::Language *, void *);
|
||||||
|
std::pair<vase::Shard *, bool> run();
|
||||||
|
std::pair<vase::Shard *, bool> run_pipe();
|
||||||
|
std::pair<vase::Shard *, bool> run_terminal();
|
||||||
|
void grow();
|
||||||
|
void redraw();
|
||||||
|
void layout_lines();
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::ui::text_mode
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::vase {
|
||||||
|
constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../shard.h"
|
#include "../shard.h"
|
||||||
|
#include "internal/generic.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
enum struct Direction : uint8_t {
|
namespace bed::internal::vase {
|
||||||
Forward,
|
struct PetalIterator {
|
||||||
Backward
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ChunkIterator {
|
|
||||||
Direction dir;
|
Direction dir;
|
||||||
Shard *root = nullptr;
|
Shard *root = nullptr;
|
||||||
std::vector<Shard *> stack;
|
std::vector<Shard *> stack;
|
||||||
Petal *petal = nullptr;
|
Petal *petal = nullptr;
|
||||||
uint64_t petal_offset = 0;
|
|
||||||
uint64_t global_offset = 0;
|
uint64_t global_offset = 0;
|
||||||
|
uint64_t last_offset = 0;
|
||||||
|
uint64_t petal_offset = 0;
|
||||||
uint64_t global_line = 0;
|
uint64_t global_line = 0;
|
||||||
bool at_end = false;
|
|
||||||
|
|
||||||
ChunkIterator(Shard *r, Direction dir);
|
PetalIterator(Shard *r, Direction dir);
|
||||||
|
~PetalIterator() = default;
|
||||||
~ChunkIterator();
|
|
||||||
|
|
||||||
void seek_offset(uint64_t offset);
|
void seek_offset(uint64_t offset);
|
||||||
void seek_line(uint64_t offset);
|
void seek_line(uint64_t offset);
|
||||||
bool next(const char **data, uint64_t *out_len);
|
bool next(const char **data, uint64_t *out_len);
|
||||||
uint64_t byte_offset();
|
uint64_t byte_offset();
|
||||||
uint64_t line_offset();
|
|
||||||
|
private:
|
||||||
|
Petal *_next(uint64_t *offset);
|
||||||
};
|
};
|
||||||
|
} // namespace bed::internal::vase
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
uint16_t height;
|
||||||
|
std::atomic_uint32_t refs;
|
||||||
|
|
||||||
|
uint64_t length;
|
||||||
|
uint64_t lines;
|
||||||
|
|
||||||
|
Shard(Kind kind, uint64_t length, uint64_t lines, uint16_t height)
|
||||||
|
: kind(kind), height(height), refs(1), length(length), lines(lines) {};
|
||||||
|
|
||||||
|
static void retain(Shard *n);
|
||||||
|
static void release(Shard *n);
|
||||||
|
|
||||||
|
static Shard *from_file(const std::filesystem::path &path);
|
||||||
|
static Shard *from_string(const char *data, uint64_t len);
|
||||||
|
static Shard *from_command(const char *cmd);
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#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 eof_point(Shard *root);
|
||||||
|
|
||||||
|
std::string to_string(Shard *root);
|
||||||
|
std::string to_string(Shard *root, Range range);
|
||||||
|
|
||||||
|
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
||||||
|
Shard *erase(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
Shard *replace(Shard *root, Shard *text, uint64_t start, uint64_t end);
|
||||||
|
Shard *join(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
Shard *copy(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
void write_file(std::filesystem::path path, Shard *text);
|
||||||
|
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
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
+20
-1
@@ -4,10 +4,29 @@
|
|||||||
|
|
||||||
#include <mruby.h>
|
#include <mruby.h>
|
||||||
#include <mruby/array.h>
|
#include <mruby/array.h>
|
||||||
|
#include <mruby/boxing_word.h>
|
||||||
|
#include <mruby/class.h>
|
||||||
|
#include <mruby/common.h>
|
||||||
#include <mruby/compile.h>
|
#include <mruby/compile.h>
|
||||||
|
#include <mruby/data.h>
|
||||||
|
#include <mruby/dump.h>
|
||||||
|
#include <mruby/error.h>
|
||||||
|
#include <mruby/gc.h>
|
||||||
#include <mruby/hash.h>
|
#include <mruby/hash.h>
|
||||||
|
#include <mruby/internal.h>
|
||||||
#include <mruby/irep.h>
|
#include <mruby/irep.h>
|
||||||
|
#include <mruby/numeric.h>
|
||||||
|
#include <mruby/object.h>
|
||||||
|
#include <mruby/opcode.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/proc.h>
|
||||||
|
#include <mruby/range.h>
|
||||||
|
#include <mruby/re.h>
|
||||||
#include <mruby/string.h>
|
#include <mruby/string.h>
|
||||||
|
#include <mruby/throw.h>
|
||||||
|
#include <mruby/value.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/version.h>
|
||||||
#include <pcre2.h>
|
#include <pcre2.h>
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <grapheme.h>
|
#include <grapheme.h>
|
||||||
@@ -29,12 +48,12 @@ 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>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
@@ -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;
|
|
||||||
uint64_t current_offset{0};
|
|
||||||
uint64_t t_offset{0};
|
|
||||||
|
|
||||||
AppendBuffer();
|
|
||||||
|
|
||||||
~AppendBuffer();
|
|
||||||
|
|
||||||
uint64_t append(char c);
|
|
||||||
uint64_t append(const char *text, uint64_t length);
|
|
||||||
|
|
||||||
const char *read(uint64_t pos, uint64_t *out_len);
|
|
||||||
inline uint64_t length();
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
struct Buffer {
|
|
||||||
virtual const char *read(uint64_t pos, uint64_t *out_len) = 0;
|
|
||||||
virtual inline uint64_t length() = 0;
|
|
||||||
virtual ~Buffer() = default;
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "buffer.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
struct OriginalBuffer : Buffer {
|
|
||||||
const char *buf;
|
|
||||||
uint64_t len;
|
|
||||||
int fd = -1;
|
|
||||||
|
|
||||||
OriginalBuffer();
|
|
||||||
~OriginalBuffer();
|
|
||||||
|
|
||||||
void initialize();
|
|
||||||
const char *read(uint64_t pos, uint64_t *out_len);
|
|
||||||
uint64_t length();
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
constexpr uint64_t APPEND_CHUNK_SIZE = 64 * 1024;
|
|
||||||
constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024;
|
|
||||||
|
|
||||||
static_assert(
|
|
||||||
APPEND_CHUNK_SIZE > PETAL_SIZE_MAX,
|
|
||||||
"PETAL_SIZE_MAX must be smaller than APPEND_CHUNK_SIZE"
|
|
||||||
);
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../shard.h"
|
|
||||||
#include "chunk.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
struct LineIterator {
|
|
||||||
ChunkIterator it;
|
|
||||||
Direction dir;
|
|
||||||
|
|
||||||
const char *chunk;
|
|
||||||
uint64_t len;
|
|
||||||
uint64_t offset = 0;
|
|
||||||
uint64_t chunk_offset = 0;
|
|
||||||
bool at_end = false;
|
|
||||||
|
|
||||||
LineIterator(Shard *r, uint64_t start_line, Direction dir);
|
|
||||||
~LineIterator() = default;
|
|
||||||
|
|
||||||
bool next(std::string &line);
|
|
||||||
uint64_t byte_offset();
|
|
||||||
};
|
|
||||||
@@ -1,64 +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;
|
|
||||||
|
|
||||||
uint64_t length;
|
|
||||||
uint64_t lines;
|
|
||||||
|
|
||||||
std::atomic_uint64_t refs;
|
|
||||||
|
|
||||||
Shard(ShardKind kind, uint64_t length, uint64_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;
|
|
||||||
|
|
||||||
uint64_t pos;
|
|
||||||
|
|
||||||
Petal(uint64_t length, uint64_t lines, Buffer *source, uint64_t pos)
|
|
||||||
: Shard(ShardKind::Petal, length, lines, 1),
|
|
||||||
source(source), pos(pos) {};
|
|
||||||
};
|
|
||||||
|
|
||||||
Shard *create_file_shards(std::string &path, OriginalBuffer *b);
|
|
||||||
Shard *create_swap_shards(std::string &path, OriginalBuffer *b);
|
|
||||||
|
|
||||||
std::pair<Shard *, Shard *> split_shard(Shard *n, uint64_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, uint64_t lo, uint64_t hi);
|
|
||||||
@@ -1,101 +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 {
|
|
||||||
uint64_t row;
|
|
||||||
uint64_t col;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Range {
|
|
||||||
Point start;
|
|
||||||
Point end;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RegexGroup {
|
|
||||||
uint64_t start{UINT32_MAX};
|
|
||||||
uint64_t end{UINT32_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;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Vase {
|
|
||||||
OriginalBuffer *original;
|
|
||||||
AppendBuffer *append;
|
|
||||||
Shard *root;
|
|
||||||
|
|
||||||
Vase(std::string path);
|
|
||||||
|
|
||||||
~Vase();
|
|
||||||
|
|
||||||
uint64_t length();
|
|
||||||
std::string to_string();
|
|
||||||
std::string to_string(Range range);
|
|
||||||
|
|
||||||
Point resolve_column(uint64_t line, uint64_t column);
|
|
||||||
|
|
||||||
LineIterator iterate(uint64_t line);
|
|
||||||
|
|
||||||
void insert(Point *point, char key);
|
|
||||||
void insert(Point *point, const char *data, uint64_t len);
|
|
||||||
void erase(Point *point, uint64_t amount, Direction dir);
|
|
||||||
void erase(Range range);
|
|
||||||
void replace(Range range, const char *data, uint64_t len);
|
|
||||||
|
|
||||||
void move_clusters(Point *point, uint64_t amount, Direction dir);
|
|
||||||
void move_lines(Point *point, uint64_t amount, Direction dir);
|
|
||||||
void clamp(Point *point);
|
|
||||||
|
|
||||||
void regex_search_replace(
|
|
||||||
std::string_view pattern, Range range,
|
|
||||||
std::string_view replace, std::string_view options
|
|
||||||
);
|
|
||||||
|
|
||||||
std::vector<Range> regex_search(
|
|
||||||
std::string_view pattern, Range range, std::string_view options
|
|
||||||
);
|
|
||||||
|
|
||||||
bool undo();
|
|
||||||
bool redo();
|
|
||||||
|
|
||||||
void snapshot();
|
|
||||||
void prune_history(uint64_t n);
|
|
||||||
|
|
||||||
bool save(std::string path);
|
|
||||||
bool save_swap(std::string path);
|
|
||||||
|
|
||||||
uint64_t offset_of(Point point);
|
|
||||||
Point point_of(uint64_t offset);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Shard *> history;
|
|
||||||
uint64_t history_top;
|
|
||||||
|
|
||||||
void _insert(Point *point, const char *data, uint64_t len);
|
|
||||||
|
|
||||||
std::vector<ReplacePart> parse_replace(std::string_view s);
|
|
||||||
std::vector<RegexMatch> _regex_search(
|
|
||||||
std::string_view pattern, Range range, std::string_view options
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
register :happy, address: :none do
|
||||||
|
puts "be nice"
|
||||||
|
end
|
||||||
@@ -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
|
||||||
@@ -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)))
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
BEd::BEd(std::vector<std::string> args)
|
||||||
|
: mrb(this), theme(internal::theme::Theme::default_theme()), io(*this) {
|
||||||
|
std::string prompt_ = "";
|
||||||
|
std::string file = "";
|
||||||
|
bool suppress = false;
|
||||||
|
bool color = true;
|
||||||
|
for (size_t i = 1; i < args.size(); i++) {
|
||||||
|
if (args[i] == "-p") {
|
||||||
|
i++;
|
||||||
|
if (i >= args.size())
|
||||||
|
throw fatal_error("Prompt not specified!", 1);
|
||||||
|
prompt_ = args[i];
|
||||||
|
} else if (args[i] == "-s") {
|
||||||
|
suppress = true;
|
||||||
|
} else if (args[i] == "--no-color") {
|
||||||
|
color = false;
|
||||||
|
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
||||||
|
help_mode = true;
|
||||||
|
} else if (args[i] == "-h" || args[i] == "--help") {
|
||||||
|
print_help();
|
||||||
|
throw fatal_error("", 0);
|
||||||
|
} else {
|
||||||
|
if (file.size())
|
||||||
|
throw fatal_error("Invalid arguments given.", 1);
|
||||||
|
file = args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prompt_ != "")
|
||||||
|
prompt = [p = std::move(prompt_)](BEd &) { return p; };
|
||||||
|
else
|
||||||
|
prompt_mode = false;
|
||||||
|
suppress_mode = suppress;
|
||||||
|
const char *no_color = getenv("NO_COLOR");
|
||||||
|
if (no_color && *no_color != '\0')
|
||||||
|
color = false;
|
||||||
|
const char *colorterm = getenv("COLORTERM");
|
||||||
|
if (colorterm
|
||||||
|
&& strcmp(colorterm, "truecolor") != 0
|
||||||
|
&& strcmp(colorterm, "24bit") != 0)
|
||||||
|
color = false;
|
||||||
|
color_mode = color;
|
||||||
|
internal::functions::Function::register_posix(*this);
|
||||||
|
internal::functions::Function::register_extented(*this);
|
||||||
|
internal::functions::Suffix::register_suffixes(*this);
|
||||||
|
internal::scripting::register_basic(*this);
|
||||||
|
languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby());
|
||||||
|
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
|
||||||
|
current() = {"default", 0};
|
||||||
|
try {
|
||||||
|
if (file != "")
|
||||||
|
handle(":default:E " + file, false);
|
||||||
|
} catch (ed_error &e) {
|
||||||
|
io.write_line("?");
|
||||||
|
if (help_mode)
|
||||||
|
io.write_line(e.what());
|
||||||
|
last_help = e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BEd::~BEd() {
|
||||||
|
for (auto &[_, buffer] : buffers)
|
||||||
|
delete buffer;
|
||||||
|
for (auto &[_, lang] : languages)
|
||||||
|
delete lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BEd::print_help() {
|
||||||
|
io.write("BEd - A line editor.\n");
|
||||||
|
}
|
||||||
|
} // namespace bed
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
void BEd::handle(std::string_view cmd, bool eof) {
|
||||||
|
if (eof && cmd.empty()) {
|
||||||
|
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
|
||||||
|
if (c.temp_address) {
|
||||||
|
marks.get(251) = marks.get(250);
|
||||||
|
prev_2 = prev_1;
|
||||||
|
temporary_current = true;
|
||||||
|
}
|
||||||
|
internal::buffer::Address address;
|
||||||
|
switch (c.function->address_kind) {
|
||||||
|
case internal::functions::Function::AddressKind::None: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
|
}
|
||||||
|
address = a->buffername;
|
||||||
|
} break;
|
||||||
|
case internal::functions::Function::AddressKind::Line: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
|
}
|
||||||
|
address = *a;
|
||||||
|
} break;
|
||||||
|
case internal::functions::Function::AddressKind::Range: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_range(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = internal::buffer::Range(current(), current());
|
||||||
|
}
|
||||||
|
address = *a;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
|
||||||
|
if (c.argument_addresses.empty())
|
||||||
|
throw ed_error("Function needs address argument.");
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
|
||||||
|
if (a.has_value())
|
||||||
|
c.argument = *a;
|
||||||
|
else
|
||||||
|
c.argument = current();
|
||||||
|
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
|
||||||
|
if (c.argument_addresses.empty())
|
||||||
|
throw ed_error("Function needs address argument.");
|
||||||
|
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
|
||||||
|
if (a.has_value())
|
||||||
|
c.argument = *a;
|
||||||
|
else
|
||||||
|
c.argument = internal::buffer::Range(current(), current());
|
||||||
|
}
|
||||||
|
if (!c.function->accept_zero) {
|
||||||
|
if (std::holds_alternative<internal::buffer::Line>(address)) {
|
||||||
|
if (std::get<internal::buffer::Line>(address).number == 0)
|
||||||
|
throw ed_error("Line number can't be zero.");
|
||||||
|
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
|
||||||
|
auto r = std::get<internal::buffer::Range>(address);
|
||||||
|
if (r.start == 0 || r.end == 0)
|
||||||
|
throw ed_error("Line number can't be zero.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal::vase::Shard *text = nullptr;
|
||||||
|
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
|
||||||
|
internal::vase::Shard *vase = nullptr;
|
||||||
|
internal::syntax::Language *lang = nullptr;
|
||||||
|
void *state = nullptr;
|
||||||
|
if (c.function->pre_text_mode)
|
||||||
|
std::tie(vase, lang, state) = c.function->pre_text_mode(*this, address, c.argument);
|
||||||
|
internal::ui::text_mode::TextMode tm(*this, vase, lang, state);
|
||||||
|
auto [a, b] = tm.run();
|
||||||
|
if (!b) {
|
||||||
|
text = a;
|
||||||
|
} else {
|
||||||
|
if (a) {
|
||||||
|
auto p = internal::syntax::make_parser(a, a->lines + 1, lang);
|
||||||
|
auto cancel_buf = new internal::buffer::ReadonlyBuffer("cancel", a, p);
|
||||||
|
internal::syntax::release(p);
|
||||||
|
buffers["cancel"] = cancel_buf;
|
||||||
|
cancel_buf->useless = false;
|
||||||
|
internal::vase::Shard::release(a);
|
||||||
|
}
|
||||||
|
throw ed_error("Operation cancelled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.function->handle)
|
||||||
|
c.function->handle(*this, address, text, c.argument, nullptr);
|
||||||
|
if (c.suffix)
|
||||||
|
c.suffix->handle(*this);
|
||||||
|
if (c.temp_address)
|
||||||
|
temporary_current = false;
|
||||||
|
for (auto it = buffers.begin(); it != buffers.end();) {
|
||||||
|
internal::buffer::Buffer *buf = it->second;
|
||||||
|
if (buf->waste()) {
|
||||||
|
delete buf;
|
||||||
|
it = buffers.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
void BEd::run() {
|
||||||
|
while (true) {
|
||||||
|
internal::ui::CommandIO command(*this);
|
||||||
|
auto [cmd, eof] = command.run();
|
||||||
|
try {
|
||||||
|
handle(cmd, eof);
|
||||||
|
} catch (const ed_error &e) {
|
||||||
|
io.apply(internal::io::Token::Warning);
|
||||||
|
io.write_line("?");
|
||||||
|
if (help_mode)
|
||||||
|
io.write_line(e.what());
|
||||||
|
io.reset();
|
||||||
|
last_help = e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
constexpr std::string_view prefix = "history/";
|
||||||
|
if (name == "cancel")
|
||||||
|
throw ed_error("Cancel buffer empty.");
|
||||||
|
if (name.starts_with(prefix)) {
|
||||||
|
std::string_view path{name};
|
||||||
|
path.remove_prefix(prefix.size());
|
||||||
|
auto slash = path.rfind('_');
|
||||||
|
if (slash == std::string_view::npos || slash == 0 || slash == path.size() - 1)
|
||||||
|
throw ed_error("invalid history");
|
||||||
|
std::string bufname(path.substr(0, slash));
|
||||||
|
auto version_str = path.substr(slash + 1);
|
||||||
|
std::size_t version;
|
||||||
|
try {
|
||||||
|
version = std::stoull(std::string(version_str));
|
||||||
|
} catch (...) {
|
||||||
|
throw ed_error("invalid history version");
|
||||||
|
}
|
||||||
|
auto it = buffers.find(bufname);
|
||||||
|
if (it != buffers.end()) {
|
||||||
|
auto &buf_ = *it->second;
|
||||||
|
if (buf_.kind != internal::buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Only normal buffers can have history.");
|
||||||
|
auto &buf = *(internal::buffer::GenericBuffer *)&buf_;
|
||||||
|
auto *history_buf = buf.get_history(version);
|
||||||
|
buffers.emplace(name, history_buf);
|
||||||
|
return *history_buf;
|
||||||
|
}
|
||||||
|
throw ed_error("Buffer has no history.");
|
||||||
|
}
|
||||||
|
for (auto &c : name)
|
||||||
|
if (!(('0' <= c && c <= '9')
|
||||||
|
|| ('a' <= c && c <= 'z')
|
||||||
|
|| ('A' <= c && c <= 'Z')
|
||||||
|
|| c == '-' || c == '_'
|
||||||
|
|| c == '+' || c == '.'
|
||||||
|
|| c == ',' || c == '$'
|
||||||
|
|| c == '/' || c == '~'))
|
||||||
|
throw ed_error("Invalid buffer name.");
|
||||||
|
auto *buf = new internal::buffer::GenericBuffer(name);
|
||||||
|
buffers.emplace(name, buf);
|
||||||
|
return *buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
GenericBuffer::~GenericBuffer() {
|
||||||
|
for (auto &item : undo_stack) {
|
||||||
|
syntax::release(item.parse_state);
|
||||||
|
vase::Shard::release(item.text);
|
||||||
|
}
|
||||||
|
for (auto &item : redo_stack) {
|
||||||
|
syntax::release(item.parse_state);
|
||||||
|
vase::Shard::release(item.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::list_history(BEd &ctx) {
|
||||||
|
uint64_t current = base_version + undo_stack.size();
|
||||||
|
uint64_t max_version = current + redo_stack.size();
|
||||||
|
size_t width = std::to_string(max_version).size();
|
||||||
|
for (size_t i = 0; i < undo_stack.size(); ++i) {
|
||||||
|
auto &item = undo_stack[i];
|
||||||
|
uint64_t version = base_version + i;
|
||||||
|
auto time = std::chrono::system_clock::to_time_t(item.timestamp);
|
||||||
|
std::tm tm = *std::localtime(&time);
|
||||||
|
ctx.io.write_line(
|
||||||
|
std::format(
|
||||||
|
" {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
|
||||||
|
version,
|
||||||
|
width,
|
||||||
|
tm.tm_year + 1900,
|
||||||
|
tm.tm_mon + 1,
|
||||||
|
tm.tm_mday,
|
||||||
|
tm.tm_hour,
|
||||||
|
tm.tm_min,
|
||||||
|
tm.tm_sec,
|
||||||
|
item.summary
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto time = std::chrono::system_clock::to_time_t(timestamp);
|
||||||
|
std::tm tm = *std::localtime(&time);
|
||||||
|
ctx.io.write_line(
|
||||||
|
std::format(
|
||||||
|
"* {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
|
||||||
|
current,
|
||||||
|
width,
|
||||||
|
tm.tm_year + 1900,
|
||||||
|
tm.tm_mon + 1,
|
||||||
|
tm.tm_mday,
|
||||||
|
tm.tm_hour,
|
||||||
|
tm.tm_min,
|
||||||
|
tm.tm_sec,
|
||||||
|
action
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < redo_stack.size(); ++i) {
|
||||||
|
auto &item = redo_stack[redo_stack.size() - 1 - i];
|
||||||
|
uint64_t version = current + i + 1;
|
||||||
|
auto time = std::chrono::system_clock::to_time_t(item.timestamp);
|
||||||
|
std::tm tm = *std::localtime(&time);
|
||||||
|
ctx.io.write_line(
|
||||||
|
std::format(
|
||||||
|
" {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
|
||||||
|
version,
|
||||||
|
width,
|
||||||
|
tm.tm_year + 1900,
|
||||||
|
tm.tm_mon + 1,
|
||||||
|
tm.tm_mday,
|
||||||
|
tm.tm_hour,
|
||||||
|
tm.tm_min,
|
||||||
|
tm.tm_sec,
|
||||||
|
item.summary
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadonlyBuffer *GenericBuffer::get_history(uint64_t version) {
|
||||||
|
uint64_t current = base_version + undo_stack.size();
|
||||||
|
if (version < base_version)
|
||||||
|
throw ed_error("History version has been pruned.");
|
||||||
|
if (version < current) {
|
||||||
|
auto &item = undo_stack[version - base_version];
|
||||||
|
return new ReadonlyBuffer(name, item.text, item.parse_state);
|
||||||
|
}
|
||||||
|
if (version == current)
|
||||||
|
return new ReadonlyBuffer(name, root, parse);
|
||||||
|
uint64_t redo_offset = version - current - 1;
|
||||||
|
if (redo_offset >= redo_stack.size())
|
||||||
|
throw ed_error("No such history version.");
|
||||||
|
auto &item = redo_stack[redo_stack.size() - 1 - redo_offset];
|
||||||
|
return new ReadonlyBuffer(name, item.text, item.parse_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::snapshot(std::string action_) {
|
||||||
|
if (base_version == 0) {
|
||||||
|
base_version++;
|
||||||
|
} else {
|
||||||
|
vase::Shard::retain(root);
|
||||||
|
undo_stack.push_back(
|
||||||
|
HistoryItem{
|
||||||
|
syntax::retain(parse),
|
||||||
|
root,
|
||||||
|
timestamp,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (auto &item : redo_stack) {
|
||||||
|
syntax::release(item.parse_state);
|
||||||
|
vase::Shard::release(item.text);
|
||||||
|
}
|
||||||
|
redo_stack.clear();
|
||||||
|
timestamp = std::chrono::system_clock::now();
|
||||||
|
action = action_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericBuffer::undo(BEd &ctx) {
|
||||||
|
if (undo_stack.empty())
|
||||||
|
return false;
|
||||||
|
HistoryItem prev = undo_stack.back();
|
||||||
|
undo_stack.pop_back();
|
||||||
|
vase::Shard::retain(root);
|
||||||
|
redo_stack.push_back(
|
||||||
|
HistoryItem{
|
||||||
|
syntax::retain(parse),
|
||||||
|
root,
|
||||||
|
timestamp,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
);
|
||||||
|
vase::Shard::release(root);
|
||||||
|
root = prev.text;
|
||||||
|
syntax::release(parse);
|
||||||
|
parse = prev.parse_state;
|
||||||
|
timestamp = prev.timestamp;
|
||||||
|
action = prev.summary;
|
||||||
|
state = Modified;
|
||||||
|
if (!root) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = 0;
|
||||||
|
ctx.prev().end = 0;
|
||||||
|
} else {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = 1;
|
||||||
|
ctx.prev().end = root->lines + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericBuffer::redo(BEd &ctx) {
|
||||||
|
if (redo_stack.empty())
|
||||||
|
return false;
|
||||||
|
HistoryItem next = redo_stack.back();
|
||||||
|
redo_stack.pop_back();
|
||||||
|
vase::Shard::retain(root);
|
||||||
|
undo_stack.push_back(
|
||||||
|
HistoryItem{
|
||||||
|
syntax::retain(parse),
|
||||||
|
root,
|
||||||
|
timestamp,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
);
|
||||||
|
vase::Shard::release(root);
|
||||||
|
root = next.text;
|
||||||
|
syntax::release(parse);
|
||||||
|
parse = next.parse_state;
|
||||||
|
timestamp = next.timestamp;
|
||||||
|
action = next.summary;
|
||||||
|
state = Modified;
|
||||||
|
if (!root) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = 0;
|
||||||
|
ctx.prev().end = 0;
|
||||||
|
} else {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = 1;
|
||||||
|
ctx.prev().end = root->lines + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::prune(int keep) {
|
||||||
|
size_t drop =
|
||||||
|
undo_stack.size() > (size_t)keep
|
||||||
|
? undo_stack.size() - keep
|
||||||
|
: 0;
|
||||||
|
for (size_t i = 0; i < drop; ++i) {
|
||||||
|
syntax::release(undo_stack[i].parse_state);
|
||||||
|
vase::Shard::release(undo_stack[i].text);
|
||||||
|
}
|
||||||
|
undo_stack.erase(
|
||||||
|
undo_stack.begin(),
|
||||||
|
undo_stack.begin() + drop
|
||||||
|
);
|
||||||
|
base_version += drop;
|
||||||
|
for (auto &item : redo_stack) {
|
||||||
|
syntax::release(item.parse_state);
|
||||||
|
vase::Shard::release(item.text);
|
||||||
|
}
|
||||||
|
redo_stack.clear();
|
||||||
|
return undo_stack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericBuffer::waste() {
|
||||||
|
return save_path.empty()
|
||||||
|
&& root == nullptr
|
||||||
|
&& undo_stack.empty()
|
||||||
|
&& parse.lang == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::saved_hook() {
|
||||||
|
state = buffer::GenericBuffer::Unmodified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::language(BEd &ctx, std::string name) {
|
||||||
|
syntax::Language *lang = nullptr;
|
||||||
|
if (name.size()) {
|
||||||
|
auto it = ctx.languages.find(name);
|
||||||
|
if (it == ctx.languages.end())
|
||||||
|
throw ed_error("Language not found.");
|
||||||
|
lang = it->second;
|
||||||
|
}
|
||||||
|
if (lang == parse.lang)
|
||||||
|
return;
|
||||||
|
snapshot("Change buffer language.");
|
||||||
|
syntax::release(parse);
|
||||||
|
parse = syntax::make_parser(root, lines(), lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||||
|
snapshot("Load file.");
|
||||||
|
if (lines())
|
||||||
|
ctx.marks.erase(name, 1, lines());
|
||||||
|
vase::Shard::release(root);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
ctx.current() = {name, lines()};
|
||||||
|
syntax::release(parse);
|
||||||
|
parse = syntax::make_parser(root, lines(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
snapshot(std::format("Insert {} lines after line {}", (text ? text->lines + 1 : 1), line));
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = line + 1;
|
||||||
|
ctx.prev().end = line + (text ? text->lines + 1 : 1);
|
||||||
|
ctx.current() = {name, line + (text ? text->lines + 1 : 1)};
|
||||||
|
root = vase::insert(&ctx.append, root, text, line);
|
||||||
|
ctx.marks.insert(name, line, (text ? text->lines + 1 : 1));
|
||||||
|
if (parse.lang)
|
||||||
|
syntax::insert(parse, root, line, (text ? text->lines + 1 : 1));
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
snapshot(std::format("Remove lines {} to {}", start_line, end_line));
|
||||||
|
root = vase::erase(root, start_line, end_line);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
uint64_t l = std::min(start_line, lines());
|
||||||
|
ctx.prev().start = l;
|
||||||
|
ctx.prev().end = l;
|
||||||
|
ctx.current() = {name, l};
|
||||||
|
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||||
|
if (parse.lang)
|
||||||
|
syntax::erase(parse, root, start_line, end_line - start_line + 1);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) {
|
||||||
|
if (!text) {
|
||||||
|
remove(ctx, start_line, end_line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
snapshot(std::format("Replace lines {} to {} with {} lines", start_line, end_line, text->lines));
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = start_line + text->lines;
|
||||||
|
ctx.current() = {name, start_line + text->lines};
|
||||||
|
uint64_t new_count = text->lines + 1;
|
||||||
|
uint64_t old_count = end_line - start_line + 1;
|
||||||
|
root = vase::replace(root, text, start_line, end_line);
|
||||||
|
if (parse.lang) {
|
||||||
|
syntax::Edit edit(parse);
|
||||||
|
edit.erase(start_line, old_count);
|
||||||
|
edit.insert(start_line, new_count);
|
||||||
|
edit.commit(root);
|
||||||
|
}
|
||||||
|
if (new_count > old_count) {
|
||||||
|
uint64_t diff = new_count - old_count;
|
||||||
|
ctx.marks.insert(name, end_line, diff);
|
||||||
|
} else if (new_count < old_count) {
|
||||||
|
uint64_t diff = old_count - new_count;
|
||||||
|
ctx.marks.collapse(name, start_line + new_count - 1, diff);
|
||||||
|
}
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
snapshot(std::format("Join lines {} to {}", start_line, end_line));
|
||||||
|
root = vase::join(root, start_line, end_line);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = start_line;
|
||||||
|
ctx.current() = {name, start_line};
|
||||||
|
ctx.marks.collapse(name, start_line, end_line - start_line);
|
||||||
|
if (parse.lang)
|
||||||
|
syntax::erase(parse, root, start_line, end_line - start_line);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) {
|
||||||
|
snapshot(std::format("Substitute /{}/ with /{}/ in lines {} to {}", regex, replacement, start_line, end_line));
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
std::optional<syntax::Edit> edit;
|
||||||
|
if (parse.lang)
|
||||||
|
edit.emplace(parse);
|
||||||
|
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 (edit)
|
||||||
|
edit->erase(line, old_lines);
|
||||||
|
}
|
||||||
|
if (new_lines) {
|
||||||
|
ctx.marks.insert(name, line, new_lines);
|
||||||
|
if (edit)
|
||||||
|
edit->insert(line, new_lines);
|
||||||
|
}
|
||||||
|
ctx.current() = {name, line + new_lines};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (edit)
|
||||||
|
edit->commit(root);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
uint64_t ShardBuffer::lines() {
|
||||||
|
if (root)
|
||||||
|
return root->lines + 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ShardBuffer::bytes() {
|
||||||
|
if (root)
|
||||||
|
return root->length + 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vase::Shard *ShardBuffer::copy(uint64_t start_line, uint64_t end_line) {
|
||||||
|
return vase::copy(root, start_line, end_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ShardBuffer::find_next(std::string_view pattern, uint64_t start) {
|
||||||
|
return vase::find_next(root, pattern, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ShardBuffer::find_prev(std::string_view pattern, uint64_t start) {
|
||||||
|
return vase::find_prev(root, pattern, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ShardBuffer::next_closing(uint64_t start) {
|
||||||
|
if (parse.lang) {
|
||||||
|
uint64_t closing = syntax::next_closing(parse, start - 1);
|
||||||
|
return closing + 1;
|
||||||
|
} else {
|
||||||
|
start += 10;
|
||||||
|
if (start > lines())
|
||||||
|
return lines();
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ShardBuffer::prev_closing(uint64_t start) {
|
||||||
|
if (parse.lang) {
|
||||||
|
return syntax::prev_opening(parse, start - 1) + 1;
|
||||||
|
} else {
|
||||||
|
if (start > 10)
|
||||||
|
return start - 10;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShardBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
if (parse.lang) {
|
||||||
|
auto it_o = syntax::get_hl(parse, root, start_line - 1);
|
||||||
|
if (!it_o)
|
||||||
|
goto h;
|
||||||
|
auto &it = *it_o;
|
||||||
|
while (start_line <= end_line) {
|
||||||
|
it.next();
|
||||||
|
const std::string &line = it.it->line;
|
||||||
|
const auto &tokens = it.tokens;
|
||||||
|
uint32_t cursor = 0;
|
||||||
|
for (const auto &token : tokens) {
|
||||||
|
const uint32_t start = token.start;
|
||||||
|
const uint32_t end = token.end;
|
||||||
|
if (start > line.size() || end > line.size())
|
||||||
|
break;
|
||||||
|
if (cursor < start)
|
||||||
|
ctx.io.write(line.data() + cursor, start - cursor);
|
||||||
|
ctx.io.apply(token.type);
|
||||||
|
ctx.io.write(line.data() + start, end - start);
|
||||||
|
ctx.io.reset();
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
if (cursor < line.size())
|
||||||
|
ctx.io.write(line.data() + cursor, line.size() - cursor);
|
||||||
|
ctx.io.write_line("");
|
||||||
|
++start_line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h:
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(it.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShardBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
uint8_t width = 1;
|
||||||
|
for (uint64_t n = end_line; n >= 10; n /= 10)
|
||||||
|
++width;
|
||||||
|
if (parse.lang) {
|
||||||
|
std::optional<syntax::Iterator> it_o = syntax::get_hl(parse, root, start_line - 1);
|
||||||
|
if (!it_o)
|
||||||
|
goto h;
|
||||||
|
auto &it = *it_o;
|
||||||
|
while (start_line <= end_line) {
|
||||||
|
it.next();
|
||||||
|
ctx.io.write(std::format("{:>{}}\t", start_line, width));
|
||||||
|
const std::string &line = it.it->line;
|
||||||
|
const auto &tokens = it.tokens;
|
||||||
|
uint32_t cursor = 0;
|
||||||
|
for (const auto &token : tokens) {
|
||||||
|
const uint32_t start = token.start;
|
||||||
|
const uint32_t end = token.end;
|
||||||
|
if (start > line.size() || end > line.size())
|
||||||
|
break;
|
||||||
|
if (cursor < start)
|
||||||
|
ctx.io.write(line.data() + cursor, start - cursor);
|
||||||
|
ctx.io.apply(token.type);
|
||||||
|
ctx.io.write(line.data() + start, end - start);
|
||||||
|
ctx.io.reset();
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
if (cursor < line.size())
|
||||||
|
ctx.io.write(line.data() + cursor, line.size() - cursor);
|
||||||
|
ctx.io.write_line("");
|
||||||
|
++start_line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h:
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line <= end_line)
|
||||||
|
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShardBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(list_string(it.line));
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
ClipBuffer::~ClipBuffer() {}
|
||||||
|
|
||||||
|
bool ClipBuffer::waste() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::saved_hook() {}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::lines() {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
|
||||||
|
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");
|
||||||
|
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 ? text->lines + 1 : 0);
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
|
||||||
|
s = vase::insert(&ctx.append, s, text, line);
|
||||||
|
clip_write(s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
ctx.marks.insert(name, line, text->lines + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
|
||||||
|
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::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) {
|
||||||
|
if (!text) {
|
||||||
|
remove(ctx, start_line, end_line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = start_line + text->lines;
|
||||||
|
uint64_t new_count = text->lines + 1;
|
||||||
|
uint64_t old_count = end_line - start_line + 1;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
|
||||||
|
s = vase::replace(s, text, start_line, end_line);
|
||||||
|
clip_write(s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
if (new_count > old_count) {
|
||||||
|
uint64_t diff = new_count - old_count;
|
||||||
|
ctx.marks.insert(name, end_line, diff);
|
||||||
|
} else if (new_count < old_count) {
|
||||||
|
uint64_t diff = old_count - new_count;
|
||||||
|
ctx.marks.collapse(name, start_line + new_count - 1, diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
|
||||||
|
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 ®ex, 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");
|
||||||
|
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, new_lines);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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");
|
||||||
|
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
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/functions/functions.h"
|
||||||
|
#include "internal/functions/suffixes.h"
|
||||||
|
|
||||||
|
namespace bed::internal::functions {
|
||||||
|
static std::string_view address_kind_str(Function::AddressKind k) {
|
||||||
|
switch (k) {
|
||||||
|
case Function::AddressKind::None:
|
||||||
|
return "none";
|
||||||
|
case Function::AddressKind::Line:
|
||||||
|
return "1 line";
|
||||||
|
case Function::AddressKind::Range:
|
||||||
|
return "range";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view input_mode_str(Function::InputMode m) {
|
||||||
|
switch (m) {
|
||||||
|
case Function::InputMode::None:
|
||||||
|
return "";
|
||||||
|
case Function::InputMode::Text:
|
||||||
|
return "text";
|
||||||
|
case Function::InputMode::Interactive:
|
||||||
|
return "interactive";
|
||||||
|
case Function::InputMode::CommandList:
|
||||||
|
return "command list";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string_view argument_kind_str(Function::ArgumentKind a) {
|
||||||
|
switch (a) {
|
||||||
|
case Function::ArgumentKind::None:
|
||||||
|
return "";
|
||||||
|
case Function::ArgumentKind::Regex:
|
||||||
|
return "regex";
|
||||||
|
case Function::ArgumentKind::Shell:
|
||||||
|
return "shell command";
|
||||||
|
case Function::ArgumentKind::Any:
|
||||||
|
return "any";
|
||||||
|
case Function::ArgumentKind::File:
|
||||||
|
return "file";
|
||||||
|
case Function::ArgumentKind::Global:
|
||||||
|
return "ed global-style";
|
||||||
|
case Function::ArgumentKind::Mark:
|
||||||
|
return "mark name";
|
||||||
|
case Function::ArgumentKind::Number:
|
||||||
|
return "number";
|
||||||
|
case Function::ArgumentKind::Line:
|
||||||
|
return "address";
|
||||||
|
case Function::ArgumentKind::Range:
|
||||||
|
return "range";
|
||||||
|
case Function::ArgumentKind::Ruby:
|
||||||
|
return "ruby code";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_field(BEd &ctx, std::string_view label, io::Token::Kind color, std::string_view value) {
|
||||||
|
if (value.empty())
|
||||||
|
return;
|
||||||
|
ctx.io.write("\n");
|
||||||
|
ctx.io.apply(color);
|
||||||
|
ctx.io.write(label);
|
||||||
|
ctx.io.reset();
|
||||||
|
ctx.io.write(": ");
|
||||||
|
ctx.io.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void describe_function(BEd &ctx, const Function &func) {
|
||||||
|
ctx.io.write(func.desc);
|
||||||
|
ctx.io.reset();
|
||||||
|
append_field(ctx, "Default address", io::Token::Color1, func.default_address);
|
||||||
|
append_field(ctx, "Address", io::Token::Color2, address_kind_str(func.address_kind));
|
||||||
|
if (func.accept_zero)
|
||||||
|
append_field(ctx, "Zero address", io::Token::Color3, "allowed");
|
||||||
|
append_field(ctx, "Input", io::Token::Color4, input_mode_str(func.input_mode));
|
||||||
|
append_field(ctx, "Argument", io::Token::Color5, argument_kind_str(func.argument_kind));
|
||||||
|
ctx.io.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Function::register_extented(BEd &ctx) {
|
||||||
|
ctx.functions.insert(
|
||||||
|
"*",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Explain a command",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto str = std::get<std::string>(arg_);
|
||||||
|
const auto first = str.find_first_not_of(" \t");
|
||||||
|
const auto last = str.find_last_not_of(" \t");
|
||||||
|
if (first == std::string::npos)
|
||||||
|
str.clear();
|
||||||
|
else
|
||||||
|
str = str.substr(first, last - first + 1);
|
||||||
|
if (str.empty()) {
|
||||||
|
describe_function(ctx, ctx.no_op);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto len = ctx.functions.longest_match(str);
|
||||||
|
if (len == str.size()) {
|
||||||
|
describe_function(ctx, *ctx.functions.get_ptr(str));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw ed_error("Not a valid function name.");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"b",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "List active buffers",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto ¤t = std::get<std::string>(addr_);
|
||||||
|
bool current_real = false;
|
||||||
|
for (auto &[name, _] : ctx.buffers) {
|
||||||
|
if (current == name) {
|
||||||
|
ctx.io.write("* ");
|
||||||
|
current_real = true;
|
||||||
|
} else {
|
||||||
|
ctx.io.write(" ");
|
||||||
|
}
|
||||||
|
ctx.io.write_line(name);
|
||||||
|
}
|
||||||
|
if (!current_real)
|
||||||
|
ctx.io.write_line("* " + current);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"#",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Write a comment",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"`",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Ruby,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Execute given ruby code.",
|
||||||
|
.default_address = "0,0",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
auto &arg = std::get<RubyArg>(arg_);
|
||||||
|
auto pre_code = "$START=" + std::to_string(addr.start)
|
||||||
|
+ ";$END=" + std::to_string(addr.end)
|
||||||
|
+ ";$BUFNAME=\"" + addr.buffername + "\"";
|
||||||
|
scripting::run(ctx, pre_code);
|
||||||
|
auto line = scripting::run(ctx, arg.cmd);
|
||||||
|
ctx.io.write("=> ", 3);
|
||||||
|
auto parser = syntax::MiniParser(
|
||||||
|
*ctx.languages["ruby"],
|
||||||
|
vase::Shard::from_string(line.data(), line.size()),
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
const auto &tokens = parser.lines[0].second;
|
||||||
|
uint32_t cursor = 0;
|
||||||
|
for (const auto &token : tokens) {
|
||||||
|
const uint32_t start = token.start;
|
||||||
|
const uint32_t end = token.end;
|
||||||
|
if (start > line.size() || end > line.size())
|
||||||
|
break;
|
||||||
|
if (cursor < start)
|
||||||
|
ctx.io.write(line.data() + cursor, start - cursor);
|
||||||
|
ctx.io.apply(token.type);
|
||||||
|
ctx.io.write(line.data() + start, end - start);
|
||||||
|
ctx.io.reset();
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
if (cursor < line.size())
|
||||||
|
ctx.io.write(line.data() + cursor, line.size() - cursor);
|
||||||
|
ctx.io.write_line("");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"``",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Execute addressed lines as ruby code.",
|
||||||
|
.default_address = "1,$",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
auto &buf = ctx.buffer(addr.buffername);
|
||||||
|
auto code = buf.copy(addr.start, addr.end);
|
||||||
|
ctx.prev().buffername = addr.buffername;
|
||||||
|
ctx.prev().start = addr.start;
|
||||||
|
ctx.prev().end = addr.end;
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
auto pre_code = "$START=" + std::to_string(addr.start)
|
||||||
|
+ ";$END=" + std::to_string(addr.end)
|
||||||
|
+ ";$BUFNAME=\"" + addr.buffername + "\"";
|
||||||
|
scripting::run(ctx, pre_code);
|
||||||
|
scripting::run(ctx, vase::to_string(code));
|
||||||
|
vase::Shard::release(code);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"echo",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Echo the given message (replacing $1-$4 with address information)",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
auto str = std::get<std::string>(arg_);
|
||||||
|
const auto first = str.find_first_not_of(" \t");
|
||||||
|
const auto last = str.find_last_not_of(" \t");
|
||||||
|
if (first == std::string::npos)
|
||||||
|
str.clear();
|
||||||
|
else
|
||||||
|
str = str.substr(first, last - first + 1);
|
||||||
|
for (size_t i = 0; i < str.size();) {
|
||||||
|
if (str[i] == '\\') {
|
||||||
|
if (i + 1 >= str.size())
|
||||||
|
break;
|
||||||
|
str.erase(i++, 1);
|
||||||
|
if (str[i - 1] == 'n')
|
||||||
|
str[i - 1] = '\n';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (str[i] == '$' && i + 1 < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') {
|
||||||
|
char c = str[i + 1];
|
||||||
|
str.erase(i, 2);
|
||||||
|
switch (c) {
|
||||||
|
case '1': {
|
||||||
|
auto start = std::to_string(addr.start);
|
||||||
|
str.insert(i, start);
|
||||||
|
i += start.size();
|
||||||
|
} break;
|
||||||
|
case '2': {
|
||||||
|
auto end = std::to_string(addr.end);
|
||||||
|
str.insert(i, end);
|
||||||
|
i += end.size();
|
||||||
|
} break;
|
||||||
|
case '3': {
|
||||||
|
str.insert(i, addr.buffername);
|
||||||
|
i += addr.buffername.size();
|
||||||
|
} break;
|
||||||
|
case '4': {
|
||||||
|
auto &buf = ctx.buffer(addr.buffername);
|
||||||
|
auto filename = buf.filename().string();
|
||||||
|
str.insert(i, filename);
|
||||||
|
i += filename.size();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
ctx.io.write_line(str);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"cd",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Change directory",
|
||||||
|
.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 the current working directory",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
if (!getcwd(cwd, sizeof(cwd)))
|
||||||
|
throw ed_error("Can't determine current directory.");
|
||||||
|
ctx.io.write_line(cwd);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"x",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Range,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Exchange a range of lines for another",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
|
auto arg = std::get<buffer::Range>(arg_);
|
||||||
|
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
||||||
|
try {
|
||||||
|
ctx.buffer(arg.buffername).replace(ctx, text, arg.start, arg.end);
|
||||||
|
vase::Shard::release(text);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"U",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Redo the last undo modification",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Can't redo buffer");
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
if (!buf.redo(ctx))
|
||||||
|
throw ed_error("Can't redo buffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"hl",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "List available history versions",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Can't redo buffer");
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
buf.list_history(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"hp",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Number,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Prune old history versions",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto &arg = std::get<int64_t>(arg_);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Can't prune buffer.");
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
auto versions = buf.prune(arg);
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write_line(std::format("{} undo versions left.", versions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"lang",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Set buffer language",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto name = std::get<std::string>(arg_);
|
||||||
|
const auto first = name.find_first_not_of(" \t");
|
||||||
|
const auto last = name.find_last_not_of(" \t");
|
||||||
|
if (first == std::string::npos)
|
||||||
|
name.clear();
|
||||||
|
else
|
||||||
|
name = name.substr(first, last - first + 1);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Can't set language to buffer.");
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
buf.language(ctx, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::functions
|
||||||
@@ -0,0 +1,885 @@
|
|||||||
|
#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 = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
const Argument &
|
||||||
|
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
|
||||||
|
const auto &addr = std::get<buffer::Line>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr.buffername);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
return {nullptr, nullptr, nullptr};
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
|
||||||
|
return {nullptr, buf.parse.lang, state};
|
||||||
|
},
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||||
|
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 a range of lines",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
const Argument &
|
||||||
|
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
|
||||||
|
const auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr.buffername);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
return {buf_.copy(addr.start, addr.end), nullptr, nullptr};
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
void *state = syntax::state_before(buf.parse, buf.root, addr.start - 1);
|
||||||
|
return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
|
||||||
|
},
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
const auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
ctx.buffer(addr.buffername).replace(ctx, text, addr.start, addr.end);
|
||||||
|
vase::Shard::release(text); }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"d",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Delete a range of lines",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
|
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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, warn once if unsaved changes exist",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.load(ctx, s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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, discarding unsaved changes",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.load(ctx, s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"f",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Set/print a save path",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto &buf = ctx.buffer(addr);
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg))
|
||||||
|
buf.set_filename(std::get<std::filesystem::path>(arg));
|
||||||
|
else if (std::holds_alternative<ShellArg>(arg))
|
||||||
|
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.apply(internal::io::Token::Warning);
|
||||||
|
ctx.io.write_line(ctx.last_help);
|
||||||
|
ctx.io.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
ctx.io.apply(internal::io::Token::Warning);
|
||||||
|
if (ctx.help_mode)
|
||||||
|
ctx.io.write_line(ctx.last_help);
|
||||||
|
ctx.io.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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 = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
const Argument &
|
||||||
|
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
|
||||||
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
|
if (addr.number)
|
||||||
|
addr.number--;
|
||||||
|
auto &buf_ = ctx.buffer(addr.buffername);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
return {nullptr, nullptr, nullptr};
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
|
||||||
|
return {nullptr, buf.parse.lang, state};
|
||||||
|
},
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *text,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
|
if (addr.number)
|
||||||
|
addr.number--;
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||||
|
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 range of lines",
|
||||||
|
.default_address = ".,.+1",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
|
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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 = "Print a range, showing nonprinting characters unambiguously",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
&& arg.number >= addr.start && arg.number < addr.end)
|
||||||
|
throw ed_error("Can't move lines within themselves.");
|
||||||
|
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
||||||
|
ctx.mark(252, arg);
|
||||||
|
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.functions.insert(
|
||||||
|
"n",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print a range with line numbers",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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 a range",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Toggle/set prompt string",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &arg = std::get<std::string>(arg_);
|
||||||
|
if (arg.size()) {
|
||||||
|
ctx.prompt_mode = true;
|
||||||
|
ctx.prompt = [arg](BEd &) { return arg; };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.prompt_mode = !ctx.prompt_mode;
|
||||||
|
if (ctx.prompt_mode && !ctx.prompt)
|
||||||
|
ctx.prompt = [](BEd &) { return "*"; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"q",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Quit, warn once if unsaved changes exist",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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 = "Quit without saving, discarding unsaved changes",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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 a file's contents into the buffer after the given address",
|
||||||
|
.default_address = "$",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.append(ctx, s, addr.number);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"s",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Regex,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Substitute pattern in range for replacement",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg_,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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 (...) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"u",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Undo the last modification to the buffer",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<std::string>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
throw ed_error("Can't undo buffer");
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
if (!buf.undo(ctx))
|
||||||
|
throw ed_error("Can't undo buffer.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"w",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Write a range of lines to disk",
|
||||||
|
.default_address = "0,$",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
vase::Shard::release(text);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
buf.saved_hook();
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"=",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print the line number of the given address",
|
||||||
|
.default_address = "$",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto &addr = std::get<buffer::Range>(addr_);
|
||||||
|
ctx.io.apply(io::Token::BufferName);
|
||||||
|
ctx.io.write(":" + addr.buffername + ":");
|
||||||
|
ctx.io.apply(io::Token::Number);
|
||||||
|
if (addr.start == addr.end) {
|
||||||
|
ctx.io.write_line(std::format(" {}", addr.start));
|
||||||
|
} else {
|
||||||
|
ctx.io.write(std::format(" {}", addr.start));
|
||||||
|
ctx.io.apply(io::Token::AddressSeperator);
|
||||||
|
ctx.io.write(",");
|
||||||
|
ctx.io.apply(io::Token::Number);
|
||||||
|
ctx.io.write_line(std::format("{}", addr.end));
|
||||||
|
}
|
||||||
|
ctx.io.reset();
|
||||||
|
ctx.prev().buffername = addr.buffername;
|
||||||
|
ctx.prev().start = addr.start;
|
||||||
|
ctx.prev().end = addr.end;
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
if (!ctx.suppress_mode)
|
||||||
|
ctx.io.write("!\n");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.no_op = Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print given line and move the cursor to it",
|
||||||
|
.default_address = ".+1",
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &addr_,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
auto addr = std::get<buffer::Line>(addr_);
|
||||||
|
if (addr.number != 0)
|
||||||
|
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
|
||||||
|
ctx.current() = addr;
|
||||||
|
ctx.prev().buffername = addr.buffername;
|
||||||
|
ctx.prev().start = addr.number;
|
||||||
|
ctx.prev().end = addr.number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.eof_op = Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Quit, warn once if unsaved changes exist",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](
|
||||||
|
BEd &ctx,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
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
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
std::pair<std::string, bool> IO::read_pipe() {
|
||||||
|
char buf[4096];
|
||||||
|
while (true) {
|
||||||
|
auto pos = pipe_input.find('\n');
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
std::string line = pipe_input.substr(0, pos);
|
||||||
|
pipe_input.erase(0, pos + 1);
|
||||||
|
return {line, false};
|
||||||
|
}
|
||||||
|
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
pipe_input.append(buf, n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
if (pipe_input.empty())
|
||||||
|
return {"", true};
|
||||||
|
std::string line = std::move(pipe_input);
|
||||||
|
pipe_input.clear();
|
||||||
|
return {line, false};
|
||||||
|
}
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
throw fatal_error("Can't read stdin.", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent::ReadResult IO::get_next_byte(char &out) {
|
||||||
|
if (!input_queue.empty()) {
|
||||||
|
out = input_queue.front();
|
||||||
|
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
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
termios IO::orig_termios{};
|
||||||
|
termios IO::raw_termios{};
|
||||||
|
bool IO::cleaned = true;
|
||||||
|
volatile std::atomic_bool IO::resized(false);
|
||||||
|
IO::Mode IO::mode = IO::Mode::PIPE;
|
||||||
|
|
||||||
|
IO::IO(BEd &bed) : bed(bed) {
|
||||||
|
if (!isatty(STDIN_FILENO))
|
||||||
|
return;
|
||||||
|
mode = Mode::TERMINAL;
|
||||||
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
|
throw fatal_error("Can't get terminal state.", 1);
|
||||||
|
struct sigaction sa{};
|
||||||
|
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::apply(const io::Token::Kind &t) {
|
||||||
|
if (!bed.color_mode)
|
||||||
|
return;
|
||||||
|
write("\x1b[0m");
|
||||||
|
const auto &hl = bed.theme.get(t);
|
||||||
|
const uint8_t r = (hl.fg >> 16) & 0xff;
|
||||||
|
const uint8_t g = (hl.fg >> 8) & 0xff;
|
||||||
|
const uint8_t b = hl.fg & 0xff;
|
||||||
|
write(std::format("\x1b[38;2;{};{};{}m", r, g, b));
|
||||||
|
if (hl.bg != 0) {
|
||||||
|
const uint8_t br = (hl.bg >> 16) & 0xff;
|
||||||
|
const uint8_t bg = (hl.bg >> 8) & 0xff;
|
||||||
|
const uint8_t bb = hl.bg & 0xff;
|
||||||
|
write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb));
|
||||||
|
}
|
||||||
|
if (hl.flags & Highlight::Bold)
|
||||||
|
write("\x1b[1m");
|
||||||
|
if (hl.flags & Highlight::Italic)
|
||||||
|
write("\x1b[3m");
|
||||||
|
if (hl.flags & Highlight::Underline)
|
||||||
|
write("\x1b[4m");
|
||||||
|
if (hl.flags & Highlight::Strikethrough)
|
||||||
|
write("\x1b[9m");
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::reset() {
|
||||||
|
if (!bed.color_mode)
|
||||||
|
return;
|
||||||
|
write("\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enable_mouse() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no mouse in pipe mode.", 1);
|
||||||
|
const char *seq = "\x1b[?1000h";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::disable_mouse() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no mouse in pipe mode.", 1);
|
||||||
|
const char *seq = "\x1b[?1000l";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::terminal_size() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no terminal size in pipe mode.", 1);
|
||||||
|
struct winsize ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
throw fatal_error("Can't get terminal size.", 1);
|
||||||
|
return {ws.ws_row, ws.ws_col};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw fatal_error("no cursor in pipe mode.", 1);
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[6n", 4);
|
||||||
|
std::string response;
|
||||||
|
char c;
|
||||||
|
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 || mode == Mode::PIPE)
|
||||||
|
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 || mode == Mode::PIPE)
|
||||||
|
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) {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
return;
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
|
||||||
|
write_all(STDOUT_FILENO, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (mode == Mode::PIPE)
|
||||||
|
throw ed_error("Shell running not allowed in pipe mode.");
|
||||||
|
int master_fd = -1;
|
||||||
|
struct winsize ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
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
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
void Parser::locator(AddressPromise &addr) {
|
||||||
|
addr.base = AddressPromise::None{};
|
||||||
|
switch (peek()) {
|
||||||
|
case '.':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Last{};
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::LastRange{};
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Block{Direction::Backward};
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Block{Direction::Forward};
|
||||||
|
break;
|
||||||
|
case '^':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
token(io::Token::Mark);
|
||||||
|
advance();
|
||||||
|
if (('a' <= peek() && peek() <= 'z')
|
||||||
|
|| ('A' <= peek() && peek() <= 'Z'))
|
||||||
|
addr.base = AddressPromise::Mark{peek()};
|
||||||
|
else
|
||||||
|
throw ed_error("Valid mark needed after \'");
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
case '{': {
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
uint16_t j = 0;
|
||||||
|
std::string func;
|
||||||
|
std::string arg;
|
||||||
|
token(io::Token::RubyFunction);
|
||||||
|
while (peek(j) != '}') {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
throw ed_error("Scripted address not terminated");
|
||||||
|
if (peek(j) == '\\')
|
||||||
|
++j;
|
||||||
|
if (peek(j) == ':') {
|
||||||
|
func = peek_str(j);
|
||||||
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
|
token(io::Token::RubyArg);
|
||||||
|
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);
|
||||||
|
end_token();
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '/': {
|
||||||
|
token(io::Token::Regexp);
|
||||||
|
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);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '?': {
|
||||||
|
token(io::Token::Regexp);
|
||||||
|
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);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '<': {
|
||||||
|
token(io::Token::Label);
|
||||||
|
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);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '>': {
|
||||||
|
token(io::Token::Label);
|
||||||
|
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);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '+': {
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Number);
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
uint16_t j = 0;
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9') {
|
||||||
|
num = num * 10 + (peek(j) - '0');
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
addr.offset += num;
|
||||||
|
advance(j);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
case '-': {
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Number);
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
uint16_t j = 0;
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9') {
|
||||||
|
num = num * 10 + (peek(j) - '0');
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
addr.offset -= num;
|
||||||
|
advance(j);
|
||||||
|
end_token();
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
if ('0' <= peek() && peek() <= '9') {
|
||||||
|
token(io::Token::Number);
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek() && peek() <= '9') {
|
||||||
|
num = num * 10 + (peek() - '0');
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
addr.base = AddressPromise::Number{num};
|
||||||
|
end_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Parser::offset() {
|
||||||
|
int64_t offset = 0;
|
||||||
|
while (peek() == '+' || peek() == '-'
|
||||||
|
|| ('0' <= peek() && peek() <= '9')) {
|
||||||
|
bool positive = peek() != '-';
|
||||||
|
token(io::Token::AddressSymbol);
|
||||||
|
if (peek() == '+' || peek() == '-')
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Number);
|
||||||
|
uint16_t j = 0;
|
||||||
|
int64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9')
|
||||||
|
num = num * 10 + (peek(j++) - '0');
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
advance(j);
|
||||||
|
end_token();
|
||||||
|
offset += positive ? num : -num;
|
||||||
|
skip_ws();
|
||||||
|
}
|
||||||
|
skip_ws();
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::address(AddressPromise &addr) {
|
||||||
|
if (peek() == ':') {
|
||||||
|
token(io::Token::BufferName);
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != ':' && peek(j) != '\0')
|
||||||
|
j++;
|
||||||
|
addr.bufname = peek_str(j);
|
||||||
|
advance(j);
|
||||||
|
if (peek() == ':')
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
}
|
||||||
|
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() == ';';
|
||||||
|
token(io::Token::AddressSeperator);
|
||||||
|
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
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
#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())
|
||||||
|
curr.bufname = bufname;
|
||||||
|
else if (curr.bufname->empty())
|
||||||
|
curr.bufname = bufname = ctx.current().buffername;
|
||||||
|
bool is_final = idx + 1 == list.size();
|
||||||
|
if (!is_final) {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
|
if (curr.jumping)
|
||||||
|
ctx.current() = resolved;
|
||||||
|
curr.bufname = resolved.buffername;
|
||||||
|
curr.base = Number(resolved.number);
|
||||||
|
curr.offset = 0;
|
||||||
|
prev = curr;
|
||||||
|
prev_set = true;
|
||||||
|
bufname = *curr.bufname;
|
||||||
|
} else {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (prev_set) {
|
||||||
|
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())
|
||||||
|
curr.bufname = bufname;
|
||||||
|
else if (curr.bufname->empty())
|
||||||
|
curr.bufname = bufname = ctx.current().buffername;
|
||||||
|
bool is_final = idx + 1 == list.size();
|
||||||
|
if (!is_final) {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
|
curr.bufname = resolved.buffername;
|
||||||
|
curr.base = Number(resolved.number);
|
||||||
|
curr.offset = 0;
|
||||||
|
if (curr.jumping)
|
||||||
|
ctx.current() = resolved;
|
||||||
|
prev = curr;
|
||||||
|
prev_set = true;
|
||||||
|
bufname = *curr.bufname;
|
||||||
|
} else {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (prev_set) {
|
||||||
|
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
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
#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));
|
||||||
|
token(io::Token::Function);
|
||||||
|
advance(len);
|
||||||
|
end_token();
|
||||||
|
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.");
|
||||||
|
token(io::Token::Mark);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Any:
|
||||||
|
command->argument = std::string(peek_str());
|
||||||
|
token(io::Token::Data);
|
||||||
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Global: {
|
||||||
|
char delim;
|
||||||
|
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 '!':
|
||||||
|
token(io::Token::Error);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Shell);
|
||||||
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
case '\0':
|
||||||
|
command->argument = std::monostate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token(io::Token::File);
|
||||||
|
command->argument = std::filesystem::path(peek_str());
|
||||||
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
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");
|
||||||
|
token(io::Token::Regexp);
|
||||||
|
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));
|
||||||
|
std::string options;
|
||||||
|
if (peek(j) != '\0') {
|
||||||
|
advance(j + 1);
|
||||||
|
} else {
|
||||||
|
advance(j);
|
||||||
|
options = "p";
|
||||||
|
}
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Suffix);
|
||||||
|
if (peek() != '\0') {
|
||||||
|
options = std::string(peek_str());
|
||||||
|
advance(peek_str().size());
|
||||||
|
}
|
||||||
|
end_token();
|
||||||
|
std::erase_if(options, [&](char c) {
|
||||||
|
if (bed.suffixes[c - 'a'].has_value()) {
|
||||||
|
suffix = c;
|
||||||
|
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()));
|
||||||
|
auto *ruby_parser = bed.languages["ruby"];
|
||||||
|
void *state = ruby_parser->none_state();
|
||||||
|
std::vector<syntax::ParseEvent> events;
|
||||||
|
std::vector<io::Token> tokens_;
|
||||||
|
ruby_parser->parse(&state, peek_str(), false, &tokens_, &events);
|
||||||
|
for (auto &token : tokens_) {
|
||||||
|
token.start += i;
|
||||||
|
token.end += i;
|
||||||
|
tokens->push_back(token);
|
||||||
|
}
|
||||||
|
ruby_parser->destroy(state);
|
||||||
|
advance(peek_str().size());
|
||||||
|
} break;
|
||||||
|
case functions::Function::ArgumentKind::Shell:
|
||||||
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
token(io::Token::Shell);
|
||||||
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!suffix) {
|
||||||
|
suffix = peek();
|
||||||
|
token(io::Token::Suffix);
|
||||||
|
advance();
|
||||||
|
end_token();
|
||||||
|
}
|
||||||
|
if (suffix) {
|
||||||
|
auto &s = bed.suffixes[suffix - 'a'];
|
||||||
|
if (s.has_value())
|
||||||
|
command->suffix = &s.value();
|
||||||
|
else
|
||||||
|
throw ed_error("Invalid suffix.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
#include "internal/parser/parser.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
void Parser::token(io::Token::Kind k) {
|
||||||
|
end_token();
|
||||||
|
tokens->push_back({.start = i, .end = 0, .type = k});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::end_token() {
|
||||||
|
if (tokens->empty())
|
||||||
|
return;
|
||||||
|
auto &b = tokens->back();
|
||||||
|
if (!b.end)
|
||||||
|
b.end = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
char Parser::peek(uint16_t o) {
|
||||||
|
return i + o < cmd.size() ? cmd[i + o] : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
end_token();
|
||||||
|
while (peek() == ' ' || peek() == '\t')
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::parse() {
|
||||||
|
try {
|
||||||
|
skip_ws();
|
||||||
|
if (peek() == '@') {
|
||||||
|
token(io::Token::TempCurrent);
|
||||||
|
advance();
|
||||||
|
command->temp_address = true;
|
||||||
|
} else {
|
||||||
|
command->temp_address = false;
|
||||||
|
}
|
||||||
|
skip_ws();
|
||||||
|
addresses(command->addresses);
|
||||||
|
operation();
|
||||||
|
skip_ws();
|
||||||
|
if (peek() != '\0') {
|
||||||
|
token(io::Token::Error);
|
||||||
|
throw ed_error("Malformed command");
|
||||||
|
}
|
||||||
|
advance(cmd.size() - i);
|
||||||
|
end_token();
|
||||||
|
} catch (const ed_error &e) {
|
||||||
|
advance(cmd.size() - i);
|
||||||
|
end_token();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser::Parser(
|
||||||
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
|
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||||
|
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<io::Token> Parser::get_highlight(std::string_view cmd, BEd &bed) {
|
||||||
|
Command c;
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
CompletionContext completion;
|
||||||
|
try {
|
||||||
|
Parser p(cmd, bed, &c, &tokens, &completion);
|
||||||
|
p.parse();
|
||||||
|
} catch (const ed_error &e) {
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command Parser::get_command(std::string_view cmd, BEd &bed) {
|
||||||
|
Command c;
|
||||||
|
std::vector<io::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<io::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
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#include "internal/scripting/ruby.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::scripting {
|
||||||
|
static mrb_value mrb_bed_exit(mrb_state *mrb, mrb_value) {
|
||||||
|
mrb_raise(mrb, E_RUNTIME_ERROR, "Use `handle(\"q\")` to quit.");
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void raise_fatal(mrb_state *mrb, const fatal_error &e) {
|
||||||
|
struct RClass *klass = mrb_class_get(mrb, "FatalError");
|
||||||
|
mrb_value exc = mrb_exc_new_str(mrb, klass, mrb_str_new_cstr(mrb, e.what()));
|
||||||
|
mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code));
|
||||||
|
mrb_exc_raise(mrb, exc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value mrb_bed_handle(mrb_state *mrb, mrb_value) {
|
||||||
|
auto &ctx = *(BEd *)mrb->ud;
|
||||||
|
const char *command;
|
||||||
|
mrb_int len;
|
||||||
|
mrb_get_args(mrb, "s", &command, &len);
|
||||||
|
std::string_view cmd(command, len);
|
||||||
|
try {
|
||||||
|
ctx.handle(cmd, false);
|
||||||
|
} catch (const ed_error &e) {
|
||||||
|
mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what());
|
||||||
|
} catch (const fatal_error &f) {
|
||||||
|
raise_fatal(mrb, f);
|
||||||
|
}
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value hash_get(mrb_state *mrb, mrb_value hash, const char *name) {
|
||||||
|
if (mrb_nil_p(hash))
|
||||||
|
return mrb_nil_value();
|
||||||
|
return mrb_hash_get(mrb, hash, mrb_symbol_value(mrb_intern_cstr(mrb, name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static functions::Function::AddressKind parse_address_kind(mrb_state *mrb, mrb_value value) {
|
||||||
|
if (mrb_nil_p(value))
|
||||||
|
return functions::Function::AddressKind::None;
|
||||||
|
if (!mrb_symbol_p(value))
|
||||||
|
mrb_raise(mrb, E_TYPE_ERROR, "address must be a Symbol");
|
||||||
|
auto name = mrb_sym_name(mrb, mrb_symbol(value));
|
||||||
|
if (strcmp(name, "none") == 0)
|
||||||
|
return functions::Function::AddressKind::None;
|
||||||
|
if (strcmp(name, "line") == 0)
|
||||||
|
return functions::Function::AddressKind::Line;
|
||||||
|
if (strcmp(name, "range") == 0)
|
||||||
|
return functions::Function::AddressKind::Range;
|
||||||
|
mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid address type: :%s", name);
|
||||||
|
return functions::Function::AddressKind::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value mrb_bed_register(mrb_state *mrb, mrb_value) {
|
||||||
|
auto &ctx = *(BEd *)mrb->ud;
|
||||||
|
mrb_sym r_cmd_name;
|
||||||
|
mrb_value proc;
|
||||||
|
mrb_value opts = mrb_nil_value();
|
||||||
|
mrb_get_args(mrb, "n&|H", &r_cmd_name, &proc, &opts);
|
||||||
|
mrb_int cmd_name_len;
|
||||||
|
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
|
||||||
|
std::string_view cmd_name(name, cmd_name_len);
|
||||||
|
std::string desc;
|
||||||
|
mrb_value r_desc = hash_get(mrb, opts, "desc");
|
||||||
|
if (mrb_string_p(r_desc))
|
||||||
|
desc.assign(RSTRING_PTR(r_desc), RSTRING_LEN(r_desc));
|
||||||
|
std::string default_address;
|
||||||
|
mrb_value r_default_address = hash_get(mrb, opts, "default");
|
||||||
|
if (mrb_string_p(r_default_address))
|
||||||
|
default_address.assign(RSTRING_PTR(r_default_address), RSTRING_LEN(r_default_address));
|
||||||
|
ctx.functions.insert(
|
||||||
|
cmd_name,
|
||||||
|
functions::Function{
|
||||||
|
.address_kind = parse_address_kind(mrb, hash_get(mrb, opts, "address")),
|
||||||
|
.argument_kind = functions::Function::ArgumentKind::None,
|
||||||
|
.input_mode = functions::Function::InputMode::None,
|
||||||
|
.desc = desc,
|
||||||
|
.default_address = default_address,
|
||||||
|
.accept_zero = true,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [b = Block(mrb, proc)](
|
||||||
|
BEd &,
|
||||||
|
const buffer::Address &,
|
||||||
|
vase::Shard *,
|
||||||
|
const functions::Function::Argument &,
|
||||||
|
std::vector<buffer::Line> *
|
||||||
|
) {
|
||||||
|
b.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value mrb_bed_unregister(mrb_state *mrb, mrb_value) {
|
||||||
|
auto &ctx = *(BEd *)mrb->ud;
|
||||||
|
mrb_sym r_cmd_name;
|
||||||
|
mrb_get_args(mrb, "n", &r_cmd_name);
|
||||||
|
mrb_int cmd_name_len;
|
||||||
|
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
|
||||||
|
std::string_view cmd_name(name, cmd_name_len);
|
||||||
|
ctx.functions.remove(cmd_name);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_basic(BEd &ctx) {
|
||||||
|
auto mrb = ctx.mrb.state;
|
||||||
|
auto *bed_error =
|
||||||
|
mrb_define_class(mrb, "EdError", mrb_exc_get_id(mrb, MRB_ERROR_SYM(RuntimeError)));
|
||||||
|
mrb_define_class(mrb, "FatalError", bed_error);
|
||||||
|
mrb_define_method(mrb, mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method(mrb, mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method(mrb, mrb->kernel_module, "register", mrb_bed_register, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK());
|
||||||
|
mrb_define_method(mrb, mrb->kernel_module, "unregister", mrb_bed_unregister, MRB_ARGS_REQ(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string run(BEd &ctx, const std::string &str) {
|
||||||
|
mrb_state *mrb = ctx.mrb.state;
|
||||||
|
mrb_value result = mrb_load_nstring(mrb, str.data(), str.size());
|
||||||
|
if (!mrb->exc) {
|
||||||
|
mrb_value inspected = mrb_funcall(mrb, result, "inspect", 0);
|
||||||
|
std::string output(RSTRING_PTR(inspected), RSTRING_LEN(inspected));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
mrb_value exc = mrb_obj_value(mrb->exc);
|
||||||
|
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
|
||||||
|
std::string error;
|
||||||
|
if (mrb_string_p(msg))
|
||||||
|
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
|
||||||
|
auto *fatal_class = mrb_class_get(mrb, "FatalError");
|
||||||
|
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
|
||||||
|
mrb_value code =
|
||||||
|
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
int c = 1;
|
||||||
|
if (mrb_fixnum_p(code))
|
||||||
|
c = mrb_fixnum(code);
|
||||||
|
throw fatal_error(error, c);
|
||||||
|
}
|
||||||
|
auto *ed_class = mrb_class_get(mrb, "EdError");
|
||||||
|
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
throw ed_error(error);
|
||||||
|
}
|
||||||
|
mrb->exc = nullptr;
|
||||||
|
throw ed_error("Ruby Exception: " + error);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::scripting
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
|
||||||
|
: snap(p), at(target) {
|
||||||
|
at = target;
|
||||||
|
if (snap.lang)
|
||||||
|
state = ParseState::state_before(*snap.lang, snap.root, vase, at);
|
||||||
|
it = vase::Iterator(vase, at, Direction::Forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator::~Iterator() {
|
||||||
|
if (state)
|
||||||
|
snap.lang->destroy(state);
|
||||||
|
release(snap);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator::Iterator(Iterator &&other)
|
||||||
|
: snap(other.snap),
|
||||||
|
it(std::move(other.it)),
|
||||||
|
state(other.state),
|
||||||
|
tokens(std::move(other.tokens)) {
|
||||||
|
other.snap = {};
|
||||||
|
other.state = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator &Iterator::operator=(Iterator &&other) {
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
if (state)
|
||||||
|
snap.lang->destroy(state);
|
||||||
|
snap = other.snap;
|
||||||
|
it = std::move(other.it);
|
||||||
|
state = other.state;
|
||||||
|
tokens = std::move(other.tokens);
|
||||||
|
other.state = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterator::next() {
|
||||||
|
if (!state)
|
||||||
|
return;
|
||||||
|
it->next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
snap.lang->parse(&state, it->line, at++ == 0, &tokens, &events);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#include "internal/syntax/miniparser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
MiniParser::MiniParser(Language &lang, vase::Shard *vase, void *initial_state)
|
||||||
|
: lang(lang),
|
||||||
|
start_state(initial_state ? lang.copy(initial_state) : lang.none_state()) {
|
||||||
|
vase::Iterator it(vase, 0, Direction::Forward);
|
||||||
|
void *state = lang.copy(start_state);
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
const uint64_t count = vase ? vase->lines + 1 : 1;
|
||||||
|
lines.reserve(count);
|
||||||
|
for (uint64_t i = 0; i < count; ++i) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &tokens, &events);
|
||||||
|
lines.emplace_back(lang.copy(state), std::move(tokens));
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
MiniParser::~MiniParser() {
|
||||||
|
if (start_state)
|
||||||
|
lang.destroy(start_state);
|
||||||
|
for (auto &[state, tokens] : lines)
|
||||||
|
if (state)
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::dirty(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
uint64_t i = start;
|
||||||
|
for (; i < start + count; i++) {
|
||||||
|
it.next();
|
||||||
|
auto &line = lines[i];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &line.second, &events);
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
while (it.next()) {
|
||||||
|
auto &line = lines[i++];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, false, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
std::vector<std::pair<void *, std::vector<io::Token>>> lines_new;
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
lines_new.reserve(count);
|
||||||
|
for (uint64_t i = 0; i < count; ++i) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, start + i == 0, &tokens, &events);
|
||||||
|
lines_new.emplace_back(lang.copy(state), std::move(tokens));
|
||||||
|
}
|
||||||
|
uint64_t i = start;
|
||||||
|
while (it.next()) {
|
||||||
|
auto &line = lines[i++];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lines.insert(
|
||||||
|
lines.begin() + start,
|
||||||
|
std::make_move_iterator(lines_new.begin()),
|
||||||
|
std::make_move_iterator(lines_new.end())
|
||||||
|
);
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
for (uint64_t i = start; i < start + count; ++i)
|
||||||
|
if (lines[i].first)
|
||||||
|
lang.destroy(lines[i].first);
|
||||||
|
lines.erase(
|
||||||
|
lines.begin() + start,
|
||||||
|
lines.begin() + start + count
|
||||||
|
);
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
uint64_t i = start;
|
||||||
|
while (i < lines.size() && it.next()) {
|
||||||
|
auto &line = lines[i];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i++ == 0, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang) {
|
||||||
|
ParserSnapshot snap{nullptr, lang};
|
||||||
|
if (lines == 0)
|
||||||
|
return snap;
|
||||||
|
if (lang)
|
||||||
|
snap.root = ParseState::splice(*lang, nullptr, vase, 0, 0, lines);
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParserSnapshot retain(const ParserSnapshot &snap) {
|
||||||
|
if (snap.root)
|
||||||
|
ParseState::retain(snap.root);
|
||||||
|
return snap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(ParserSnapshot &snap) {
|
||||||
|
if (snap.root && snap.lang)
|
||||||
|
ParseState::release(*snap.lang, snap.root);
|
||||||
|
snap.root = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line) {
|
||||||
|
if (!snap.lang)
|
||||||
|
return nullptr;
|
||||||
|
return ParseState::state_before(*snap.lang, snap.root, vase, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) {
|
||||||
|
if (!snap.root || !snap.lang)
|
||||||
|
return line + 10;
|
||||||
|
uint64_t relative = 0;
|
||||||
|
TreeCursor c(*snap.lang, snap.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 (snap.root->lines() - line > 10 ? line + 10 : snap.root->lines() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line) {
|
||||||
|
if (!snap.root || !snap.lang)
|
||||||
|
return 0;
|
||||||
|
uint64_t relative = 0;
|
||||||
|
TreeCursor c(*snap.lang, snap.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 (line > 10 ? line - 10 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Iterator> get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target) {
|
||||||
|
if (!snap.root || !snap.lang)
|
||||||
|
return std::nullopt;
|
||||||
|
return Iterator(target, retain(snap), vase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Edit::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 Edit::insert(uint64_t start, uint64_t count) {
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
uint64_t orig_pos = (uint64_t)((int64_t)start - edit_delta);
|
||||||
|
mark_dirty(orig_pos, orig_pos);
|
||||||
|
edit_delta += (int64_t)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Edit::erase(uint64_t start, uint64_t count) {
|
||||||
|
if (count == 0 || !target->root)
|
||||||
|
return;
|
||||||
|
uint64_t orig_start = (uint64_t)((int64_t)start - edit_delta);
|
||||||
|
uint64_t orig_end = orig_start + count;
|
||||||
|
mark_dirty(orig_start, orig_end);
|
||||||
|
edit_delta -= (int64_t)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Edit::commit(vase::Shard *vase) {
|
||||||
|
if (!dirty || !target->lang)
|
||||||
|
return;
|
||||||
|
uint64_t line = dirty_start;
|
||||||
|
uint64_t original = dirty_end - dirty_start;
|
||||||
|
uint64_t final = (uint64_t)((int64_t)original + edit_delta);
|
||||||
|
ParseState *new_root =
|
||||||
|
ParseState::splice(*target->lang, target->root, vase, line, original, final);
|
||||||
|
release(*target);
|
||||||
|
target->root = new_root;
|
||||||
|
dirty = false;
|
||||||
|
edit_delta = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
Edit e(snap);
|
||||||
|
e.insert(start, count);
|
||||||
|
e.commit(vase);
|
||||||
|
}
|
||||||
|
|
||||||
|
void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
Edit e(snap);
|
||||||
|
e.erase(start, count);
|
||||||
|
e.commit(vase);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
void ParseState::retain(ParseState *n) {
|
||||||
|
if (n)
|
||||||
|
n->refs++;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ParseState::release(Language &lang, ParseState *n) {
|
||||||
|
if (!n || --n->refs > 0)
|
||||||
|
return;
|
||||||
|
if (n->is_branch()) {
|
||||||
|
auto *branch = (ParseStateBranch *)n;
|
||||||
|
release(lang, branch->left);
|
||||||
|
release(lang, branch->right);
|
||||||
|
delete branch;
|
||||||
|
} else {
|
||||||
|
auto *leaf = (ParseStateLeaf *)n;
|
||||||
|
if (leaf->state)
|
||||||
|
lang.destroy(leaf->state);
|
||||||
|
if (leaf->blocks)
|
||||||
|
free(leaf->blocks);
|
||||||
|
delete leaf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int height(ParseState *n) {
|
||||||
|
return n ? n->height : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int balance_factor(ParseState *n) {
|
||||||
|
ParseStateBranch *b = (ParseStateBranch *)n;
|
||||||
|
return height(b->left) - height(b->right);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *rotate_right(Language &lang, ParseStateBranch *z) {
|
||||||
|
ParseStateBranch *y = (ParseStateBranch *)z->left;
|
||||||
|
ParseState *middle = new ParseStateBranch(y->right, z->right);
|
||||||
|
ParseState *out = new ParseStateBranch(y->left, middle);
|
||||||
|
ParseState::release(lang, middle);
|
||||||
|
ParseState::release(lang, z);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *rotate_left(Language &lang, ParseStateBranch *z) {
|
||||||
|
ParseStateBranch *y = (ParseStateBranch *)z->right;
|
||||||
|
ParseState *middle = new ParseStateBranch(z->left, y->left);
|
||||||
|
ParseState *out = new ParseStateBranch(middle, y->right);
|
||||||
|
ParseState::release(lang, middle);
|
||||||
|
ParseState::release(lang, z);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *balance(Language &lang, ParseState *node) {
|
||||||
|
if (!node || !node->is_branch())
|
||||||
|
return node;
|
||||||
|
ParseStateBranch *b = (ParseStateBranch *)node;
|
||||||
|
int bf = balance_factor(node);
|
||||||
|
if (bf > 1) {
|
||||||
|
ParseStateBranch *left = (ParseStateBranch *)b->left;
|
||||||
|
if (balance_factor(left) < 0) {
|
||||||
|
ParseState::retain(left);
|
||||||
|
auto new_left = rotate_left(lang, left);
|
||||||
|
auto rebuilt = new ParseStateBranch(new_left, b->right);
|
||||||
|
auto result = rotate_right(lang, (ParseStateBranch *)rebuilt);
|
||||||
|
ParseState::release(lang, new_left);
|
||||||
|
ParseState::release(lang, b);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return rotate_right(lang, b);
|
||||||
|
}
|
||||||
|
if (bf < -1) {
|
||||||
|
ParseStateBranch *right = (ParseStateBranch *)b->right;
|
||||||
|
if (balance_factor(right) > 0) {
|
||||||
|
ParseState::retain(right);
|
||||||
|
auto new_right = rotate_right(lang, right);
|
||||||
|
auto rebuilt = new ParseStateBranch(b->left, new_right);
|
||||||
|
auto result = rotate_left(lang, (ParseStateBranch *)rebuilt);
|
||||||
|
ParseState::release(lang, new_right);
|
||||||
|
ParseState::release(lang, b);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return rotate_left(lang, b);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *ParseState::build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi) {
|
||||||
|
if (hi - lo == 1)
|
||||||
|
return pieces[lo];
|
||||||
|
uint64_t mid = lo + (hi - lo) / 2;
|
||||||
|
ParseState *left = build(lang, pieces, lo, mid);
|
||||||
|
ParseState *right = build(lang, pieces, mid, hi);
|
||||||
|
ParseState *node = concat(lang, left, right);
|
||||||
|
release(lang, left);
|
||||||
|
release(lang, right);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *ParseState::splice(
|
||||||
|
Language &lang, ParseState *root, vase::Shard *vase,
|
||||||
|
uint64_t line, uint64_t original, uint64_t final
|
||||||
|
) {
|
||||||
|
if (!vase)
|
||||||
|
return nullptr;
|
||||||
|
if (!root || (line == 0 && root->lines() == original)) {
|
||||||
|
vase::Iterator it(vase, 0, Direction::Forward);
|
||||||
|
void *state = lang.none_state();
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
ParsePieceBuilder builder(lang, 0, state);
|
||||||
|
for (uint64_t at = 0; at < final; ++at) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, at == 0, &tokens, &events);
|
||||||
|
builder.add(state, at, events);
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
return builder.finish();
|
||||||
|
}
|
||||||
|
uint64_t at;
|
||||||
|
void *state;
|
||||||
|
ParseState *prefix;
|
||||||
|
{
|
||||||
|
uint64_t offset;
|
||||||
|
TreeCursor c = TreeCursor(lang, root, line, &offset);
|
||||||
|
prefix = c.prefix();
|
||||||
|
at = line - offset;
|
||||||
|
state = lang.copy(c.leaf->state);
|
||||||
|
}
|
||||||
|
vase::Iterator it(vase, at, Direction::Forward);
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
uint64_t end_in_tree = line + original;
|
||||||
|
uint64_t end_extra;
|
||||||
|
TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra);
|
||||||
|
uint64_t end_in_vase = line + final;
|
||||||
|
ParsePieceBuilder builder(lang, at, state);
|
||||||
|
while (at < end_in_vase + end_extra) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, at == 0, &tokens, &events);
|
||||||
|
builder.add(state, at, events);
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
c.next();
|
||||||
|
while (c.leaf) {
|
||||||
|
if (lang.equal(state, c.leaf->state))
|
||||||
|
break;
|
||||||
|
for (uint64_t i = 0; i < c.leaf->lines(); ++i) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, at == 0, &tokens, &events);
|
||||||
|
builder.add(state, at, events);
|
||||||
|
++at;
|
||||||
|
}
|
||||||
|
c.next();
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
ParseState *suffix = c.suffix();
|
||||||
|
ParseState *new_stuff = builder.finish();
|
||||||
|
auto a = concat(lang, prefix, new_stuff);
|
||||||
|
release(lang, prefix);
|
||||||
|
release(lang, new_stuff);
|
||||||
|
auto result = concat(lang, a, suffix);
|
||||||
|
release(lang, a);
|
||||||
|
release(lang, suffix);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *b) {
|
||||||
|
if (!a)
|
||||||
|
return (retain(b), b);
|
||||||
|
if (!b)
|
||||||
|
return (retain(a), a);
|
||||||
|
if (a->height > b->height + 1) {
|
||||||
|
ParseStateBranch *ba = (ParseStateBranch *)a;
|
||||||
|
ParseState *r = concat(lang, ba->right, b);
|
||||||
|
ParseState *out = balance(lang, new ParseStateBranch(ba->left, r));
|
||||||
|
release(lang, r);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
if (b->height > a->height + 1) {
|
||||||
|
ParseStateBranch *bb = (ParseStateBranch *)b;
|
||||||
|
ParseState *l = concat(lang, a, bb->left);
|
||||||
|
ParseState *out = balance(lang, new ParseStateBranch(l, bb->right));
|
||||||
|
release(lang, l);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return balance(lang, new ParseStateBranch(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
void *ParseState::state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line) {
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t at;
|
||||||
|
void *state;
|
||||||
|
if (!root) {
|
||||||
|
at = 0;
|
||||||
|
state = lang.none_state();
|
||||||
|
} else {
|
||||||
|
TreeCursor c = TreeCursor(lang, root, line, &offset);
|
||||||
|
at = line - offset;
|
||||||
|
state = lang.copy(c.leaf->state);
|
||||||
|
}
|
||||||
|
vase::Iterator it(vase, at, Direction::Forward);
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
for (; at < line; ++at) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, at == 0, &tokens, &events);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
|||||||
|
#include "internal/syntax/decl.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
TreeCursor::~TreeCursor() {
|
||||||
|
ParseState::release(lang, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeCursor::TreeCursor(
|
||||||
|
Language &lang, ParseState *root,
|
||||||
|
uint64_t target_line, uint64_t *relative
|
||||||
|
) : lang(lang), root(root) {
|
||||||
|
if (!root) {
|
||||||
|
*relative = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ParseState::retain(root);
|
||||||
|
ParseState *node = root;
|
||||||
|
while (node->is_branch()) {
|
||||||
|
auto *branch = (ParseStateBranch *)node;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *TreeCursor::prefix() {
|
||||||
|
ParseState *result = nullptr;
|
||||||
|
for (uint8_t i = 0; i < depth; ++i) {
|
||||||
|
if (went_left[i])
|
||||||
|
continue;
|
||||||
|
auto *branch = stack[i];
|
||||||
|
ParseState *piece = branch->left;
|
||||||
|
if (!result) {
|
||||||
|
ParseState::retain(piece);
|
||||||
|
result = piece;
|
||||||
|
} else {
|
||||||
|
ParseState *next = ParseState::concat(lang, result, piece);
|
||||||
|
ParseState::release(lang, result);
|
||||||
|
result = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseState *TreeCursor::suffix() {
|
||||||
|
ParseState *result = nullptr;
|
||||||
|
for (uint8_t i = depth; i-- > 0;) {
|
||||||
|
if (!went_left[i])
|
||||||
|
continue;
|
||||||
|
auto *branch = stack[i];
|
||||||
|
ParseState *piece = branch->right;
|
||||||
|
if (!result) {
|
||||||
|
ParseState::retain(piece);
|
||||||
|
result = piece;
|
||||||
|
} else {
|
||||||
|
ParseState *next = ParseState::concat(lang, result, piece);
|
||||||
|
ParseState::release(lang, result);
|
||||||
|
result = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
#include "internal/theme/theme.h"
|
||||||
|
|
||||||
|
namespace bed::internal::theme {
|
||||||
|
Theme::Theme() {
|
||||||
|
hl.fill({
|
||||||
|
.fg = 0xF0F0F0,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
io::Highlight Theme::get(const io::Token::Kind &token) const {
|
||||||
|
return hl[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
Theme Theme::default_theme() {
|
||||||
|
Theme theme;
|
||||||
|
|
||||||
|
constexpr uint32_t PINK = 0xECA1AE;
|
||||||
|
constexpr uint32_t GREY = 0xAFB7D3;
|
||||||
|
constexpr uint32_t BLUE = 0x799EDB;
|
||||||
|
constexpr uint32_t LIGHT_BLUE = 0xB5BFFE;
|
||||||
|
constexpr uint32_t RED = 0xF38CAA;
|
||||||
|
constexpr uint32_t YELLOW = 0xF9E3B1;
|
||||||
|
constexpr uint32_t PURPLE = 0xA286C7;
|
||||||
|
constexpr uint32_t CYAN = 0x89DBEA;
|
||||||
|
constexpr uint32_t LIGHT_GREEN = 0x98CE94;
|
||||||
|
constexpr uint32_t TURQUOISE = 0x00BDB7;
|
||||||
|
constexpr uint32_t BRIGHT_GREEN = 0x4AF6CA;
|
||||||
|
|
||||||
|
theme.hl[io::Token::TempCurrent] = {
|
||||||
|
.fg = PINK,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::BufferName] = {
|
||||||
|
.fg = PURPLE,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::AddressSeperator] = {
|
||||||
|
.fg = GREY,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::AddressSymbol] = {
|
||||||
|
.fg = BLUE,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Mark] = {
|
||||||
|
.fg = LIGHT_BLUE,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::RubyFunction] = {
|
||||||
|
.fg = RED,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::RubyArg] = {
|
||||||
|
.fg = LIGHT_GREEN,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Shell] = {
|
||||||
|
.fg = BRIGHT_GREEN,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::File] = {
|
||||||
|
.fg = GREY,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Suffix] = {
|
||||||
|
.fg = TURQUOISE,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color1] = {
|
||||||
|
.fg = 0x7AA2F7,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color2] = {
|
||||||
|
.fg = 0xAAD94C,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color3] = {
|
||||||
|
.fg = 0xFF9E64,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color4] = {
|
||||||
|
.fg = 0xBB9AF7,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Color5] = {
|
||||||
|
.fg = 0xFF757F,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Warning] = {
|
||||||
|
.fg = 0xF1C55A,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Data] = {
|
||||||
|
.fg = GREY,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Shebang] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Error] = {
|
||||||
|
.fg = 0xEF5168,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Comment] = {
|
||||||
|
.fg = 0xAAAAAA,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::Italic,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::String] = {
|
||||||
|
.fg = 0xAAD94C,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Escape] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Interpolation] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Regexp] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Number] = {
|
||||||
|
.fg = 0xE6C08A,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::True] = {
|
||||||
|
.fg = 0x7AE93C,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::False] = {
|
||||||
|
.fg = 0xEF5168,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Char] = {
|
||||||
|
.fg = 0xFFAF70,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Keyword] = {
|
||||||
|
.fg = 0xFF8F40,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::KeywordOperator] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Operator] = {
|
||||||
|
.fg = 0xFFFFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::Italic,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Function] = {
|
||||||
|
.fg = 0xFFAF70,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Type] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Constant] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::VariableInstance] = {
|
||||||
|
.fg = 0x95E6CB,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::VariableGlobal] = {
|
||||||
|
.fg = 0xF07178,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Annotation] = {
|
||||||
|
.fg = 0x7DCFFF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Directive] = {
|
||||||
|
.fg = 0xFF8F40,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Label] = {
|
||||||
|
.fg = 0xD2A6FF,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Brace1] = {
|
||||||
|
.fg = BRIGHT_GREEN,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Brace2] = {
|
||||||
|
.fg = YELLOW,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Brace3] = {
|
||||||
|
.fg = 0xFFFF00,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Brace4] = {
|
||||||
|
.fg = CYAN,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
theme.hl[io::Token::Brace5] = {
|
||||||
|
.fg = 0xFF0F0F,
|
||||||
|
.bg = 0x000000,
|
||||||
|
.flags = io::Highlight::None,
|
||||||
|
};
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
Theme Theme::from_name(std::string_view name) {
|
||||||
|
if (name == "default")
|
||||||
|
return default_theme();
|
||||||
|
throw ed_error("Unknown theme: " + std::string(name));
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::theme
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#include "internal/ui/command.h"
|
||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
CommandIO::CommandIO(BEd &bed) : bed(bed) {
|
||||||
|
if (bed.prompt_mode)
|
||||||
|
prompt = bed.prompt(bed);
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, bool> CommandIO::run() {
|
||||||
|
if (!bed.io.interactive())
|
||||||
|
return run_pipe();
|
||||||
|
return run_terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, bool> CommandIO::run_pipe() {
|
||||||
|
bed.io.write(prompt);
|
||||||
|
return bed.io.read_pipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, bool> CommandIO::run_terminal() {
|
||||||
|
auto [row, col] = bed.io.cursor_position();
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
if (row > rows)
|
||||||
|
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);
|
||||||
|
const auto tokens = parser::Parser::get_highlight(cmd, bed);
|
||||||
|
uint32_t pos = 0;
|
||||||
|
for (const auto &token : tokens) {
|
||||||
|
const uint32_t tstart = token.start;
|
||||||
|
const uint32_t tend = token.end;
|
||||||
|
if (tstart > cmd.size())
|
||||||
|
break;
|
||||||
|
if (pos < tstart)
|
||||||
|
bed.io.write(cmd.data() + pos, tstart - pos);
|
||||||
|
bed.io.apply(token.type);
|
||||||
|
bed.io.write(cmd.data() + tstart, tend - tstart);
|
||||||
|
bed.io.reset();
|
||||||
|
pos = tend;
|
||||||
|
}
|
||||||
|
if (pos < cmd.size())
|
||||||
|
bed.io.write(cmd.data() + pos, cmd.size() - pos);
|
||||||
|
bed.io.move_cursor(start, prompt.size() + cursor + 1);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -0,0 +1,428 @@
|
|||||||
|
#include "internal/ui/text_mode.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui::text_mode {
|
||||||
|
// TODO: make this into a proper layout engine, in order to avoid so many expensive calculations repeatedly.
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
static void for_each_cluster(std::string_view s, F &&f) {
|
||||||
|
size_t cluster_start = 0;
|
||||||
|
while (cluster_start < s.size()) {
|
||||||
|
size_t cluster_len =
|
||||||
|
grapheme_next_character_break_utf8(
|
||||||
|
s.data() + cluster_start,
|
||||||
|
s.size() - cluster_start
|
||||||
|
);
|
||||||
|
if (cluster_len == 0)
|
||||||
|
break;
|
||||||
|
size_t cluster_end = cluster_start + cluster_len;
|
||||||
|
unicode_width_state_t state;
|
||||||
|
unicode_width_init(&state);
|
||||||
|
size_t i = cluster_start;
|
||||||
|
int width = 0;
|
||||||
|
while (i < cluster_end) {
|
||||||
|
uint_least32_t cp;
|
||||||
|
size_t decoded =
|
||||||
|
grapheme_decode_utf8(s.data() + i, cluster_end - i, &cp);
|
||||||
|
if (decoded == 0)
|
||||||
|
decoded = 1;
|
||||||
|
int w = unicode_width_process(&state, cp);
|
||||||
|
if (w > width)
|
||||||
|
width = w;
|
||||||
|
i += decoded;
|
||||||
|
}
|
||||||
|
if (width < 0)
|
||||||
|
width = 0;
|
||||||
|
if (!f(cluster_start, cluster_len, width))
|
||||||
|
return;
|
||||||
|
cluster_start = cluster_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t previous_cluster(std::string_view line, size_t col) {
|
||||||
|
size_t previous = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int) {
|
||||||
|
if (start >= col)
|
||||||
|
return false;
|
||||||
|
previous = start;
|
||||||
|
return start + len < col;
|
||||||
|
});
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t next_cluster(std::string_view line, size_t col) {
|
||||||
|
size_t next = line.size();
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int) {
|
||||||
|
if (start >= col) {
|
||||||
|
next = start + len;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t visual_column(std::string_view line, size_t col) {
|
||||||
|
size_t width = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t, int cluster_width) {
|
||||||
|
if (start >= col)
|
||||||
|
return false;
|
||||||
|
width += cluster_width;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t byte_column(std::string_view line, size_t target_width) {
|
||||||
|
size_t width = 0;
|
||||||
|
size_t col = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int cluster_width) {
|
||||||
|
if (width + cluster_width > target_width)
|
||||||
|
return false;
|
||||||
|
width += cluster_width;
|
||||||
|
col = start + len;
|
||||||
|
if (width == target_width)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::pair<size_t, size_t>> wrap_line(std::string_view line, size_t avail) {
|
||||||
|
std::vector<std::pair<size_t, size_t>> result;
|
||||||
|
if (avail == 0) {
|
||||||
|
result.push_back({0, line.size()});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
size_t begin = 0;
|
||||||
|
size_t end = 0;
|
||||||
|
int col = 0;
|
||||||
|
for_each_cluster(line, [&](size_t i, size_t len, int width) {
|
||||||
|
if (col > 0 && col + width > int(avail)) {
|
||||||
|
result.push_back({begin, i});
|
||||||
|
begin = i;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
col += width;
|
||||||
|
end = i + len;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (begin < line.size() || result.empty())
|
||||||
|
result.push_back({begin, end});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::layout_lines() {
|
||||||
|
vase::Iterator it(vase, 0, Direction::Forward);
|
||||||
|
lines.clear();
|
||||||
|
while (it.next()) {
|
||||||
|
auto wrapped = wrap_line(it.line, term_width);
|
||||||
|
lines.push_back({});
|
||||||
|
for (auto [begin, end] : wrapped)
|
||||||
|
lines.back().push_back({begin, end - begin});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextMode::TextMode(
|
||||||
|
BEd &bed, vase::Shard *vase, syntax::Language *lang, void *state
|
||||||
|
) : vase(vase), bed(bed) {
|
||||||
|
if (lang)
|
||||||
|
parser.emplace(*lang, vase, state);
|
||||||
|
cursor = vase::eof_point(vase);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vase::Shard *, bool> TextMode::run() {
|
||||||
|
if (!bed.io.interactive())
|
||||||
|
return run_pipe();
|
||||||
|
return run_terminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
|
||||||
|
vase::Shard::release(vase);
|
||||||
|
vase = nullptr;
|
||||||
|
while (true) {
|
||||||
|
auto [str, eof] = bed.io.read_pipe();
|
||||||
|
if (str == "." || eof)
|
||||||
|
break;
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, str.data(), str.length());
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, '\n');
|
||||||
|
}
|
||||||
|
return {vase, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||||
|
auto [row, col] = bed.io.cursor_position();
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
if (row > rows)
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
start = row;
|
||||||
|
height = rows - row + 1;
|
||||||
|
term_width = cols;
|
||||||
|
term_height = rows;
|
||||||
|
redraw();
|
||||||
|
bool running = true;
|
||||||
|
bool cancelled = false;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
redraw();
|
||||||
|
io::KeyEvent res = bed.io.read_key();
|
||||||
|
switch (res.type) {
|
||||||
|
case io::KeyEvent::KeyType::EOF_:
|
||||||
|
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:
|
||||||
|
if (res.text[0] == 'c') {
|
||||||
|
cancelled = true;
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::Modifier::NONE:
|
||||||
|
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
||||||
|
if (!vase)
|
||||||
|
break;
|
||||||
|
if (cursor.row || cursor.col) {
|
||||||
|
auto last = cursor;
|
||||||
|
if (last.col) {
|
||||||
|
vase::Iterator it(vase, last.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
last.col = previous_cluster(it.line, last.col);
|
||||||
|
} else if (last.row) {
|
||||||
|
--last.row;
|
||||||
|
vase::Iterator it(vase, last.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
last.col = it.line.size();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vase::Range r = {last, cursor};
|
||||||
|
vase = vase::erase(vase, r);
|
||||||
|
if (parser) {
|
||||||
|
if (cursor.row == last.row) {
|
||||||
|
parser->dirty(vase, cursor.row, 1);
|
||||||
|
} else {
|
||||||
|
parser->erase(vase, cursor.row, 1);
|
||||||
|
parser->dirty(vase, last.row, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor = last;
|
||||||
|
}
|
||||||
|
} else if (res.text[0] == '\n') {
|
||||||
|
if (vase && cursor.row == vase->lines) {
|
||||||
|
vase::Iterator it(vase, vase->lines, Direction::Backward);
|
||||||
|
if (it.next() && it.line == ".") {
|
||||||
|
vase = vase::erase(vase, vase->lines + 1, vase->lines + 1);
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, '\n');
|
||||||
|
if (parser)
|
||||||
|
parser->insert(vase, cursor.row - 1, 1);
|
||||||
|
} else {
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||||
|
if (parser)
|
||||||
|
parser->dirty(vase, cursor.row, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::PASTE: {
|
||||||
|
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||||
|
if (parser) {
|
||||||
|
if (lines) {
|
||||||
|
parser->insert(vase, cursor.row - lines, lines);
|
||||||
|
parser->dirty(vase, cursor.row - lines, 1);
|
||||||
|
} else {
|
||||||
|
parser->dirty(vase, cursor.row, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case io::KeyEvent::KeyType::SPECIAL:
|
||||||
|
if (!vase)
|
||||||
|
break;
|
||||||
|
switch (res.special_key) {
|
||||||
|
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::RIGHT: {
|
||||||
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = next_cluster(it.line, cursor.col);
|
||||||
|
} break;
|
||||||
|
case io::KeyEvent::SpecialKey::LEFT: {
|
||||||
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = previous_cluster(it.line, cursor.col);
|
||||||
|
} break;
|
||||||
|
case io::KeyEvent::SpecialKey::UP: {
|
||||||
|
if (!cursor.row)
|
||||||
|
break;
|
||||||
|
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!current.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
size_t wanted = visual_column(current.line, cursor.col);
|
||||||
|
--cursor.row;
|
||||||
|
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!previous.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = byte_column(previous.line, wanted);
|
||||||
|
} break;
|
||||||
|
case io::KeyEvent::SpecialKey::DOWN: {
|
||||||
|
if (cursor.row >= vase->lines)
|
||||||
|
break;
|
||||||
|
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!current.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
size_t wanted = visual_column(current.line, cursor.col);
|
||||||
|
++cursor.row;
|
||||||
|
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!previous.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = byte_column(previous.line, wanted);
|
||||||
|
} break;
|
||||||
|
case io::KeyEvent::SpecialKey::DELETE:
|
||||||
|
auto next = cursor;
|
||||||
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
break;
|
||||||
|
if (cursor.col < it.line.size()) {
|
||||||
|
vase::Iterator it(vase, next.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
next.col = next_cluster(it.line, cursor.col);
|
||||||
|
} else if (cursor.row < vase->lines) {
|
||||||
|
++next.row;
|
||||||
|
next.col = 0;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vase::Range r = {cursor, next};
|
||||||
|
vase = vase::erase(vase, r);
|
||||||
|
if (parser) {
|
||||||
|
if (cursor.row != next.row)
|
||||||
|
parser->erase(vase, cursor.row + 1, 1);
|
||||||
|
parser->dirty(vase, cursor.row, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layout_lines();
|
||||||
|
size_t required_height = 0;
|
||||||
|
for (const auto &elem : lines)
|
||||||
|
required_height += elem.size();
|
||||||
|
uint16_t last_row = start + required_height;
|
||||||
|
bed.io.move_cursor(last_row, 1);
|
||||||
|
bed.io.write("\n");
|
||||||
|
return {vase, cancelled};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::grow() {
|
||||||
|
size_t required_height = 0;
|
||||||
|
for (const auto &elem : lines)
|
||||||
|
required_height += elem.size();
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
term_height = rows;
|
||||||
|
term_width = cols;
|
||||||
|
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");
|
||||||
|
start -= overflow;
|
||||||
|
if (start < 1)
|
||||||
|
start = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::redraw() {
|
||||||
|
layout_lines();
|
||||||
|
grow();
|
||||||
|
bed.io.write("\x1b[?25l", 6);
|
||||||
|
for (uint16_t i = 0; i < height; ++i) {
|
||||||
|
bed.io.move_cursor(start + i, 1);
|
||||||
|
bed.io.write("\x1b[2K", 4);
|
||||||
|
}
|
||||||
|
uint16_t screen_line = start;
|
||||||
|
uint64_t line = 0;
|
||||||
|
size_t cursor_screen_row = start;
|
||||||
|
size_t cursor_screen_col = 0;
|
||||||
|
vase::Iterator it(vase, line, Direction::Forward);
|
||||||
|
while (it.next()) {
|
||||||
|
if (screen_line >= start + height)
|
||||||
|
break;
|
||||||
|
std::string_view l(it.line);
|
||||||
|
const std::vector<io::Token> *tokens = nullptr;
|
||||||
|
if (parser && parser->lines.size() > line)
|
||||||
|
tokens = &parser->lines[line].second;
|
||||||
|
size_t token_index = 0;
|
||||||
|
for (auto &row : lines[line]) {
|
||||||
|
const size_t row_start = row.start;
|
||||||
|
const size_t row_end = row.start + row.length;
|
||||||
|
bed.io.move_cursor(screen_line, 1);
|
||||||
|
if (tokens) {
|
||||||
|
while (token_index < tokens->size() && (*tokens)[token_index].end <= row_start)
|
||||||
|
++token_index;
|
||||||
|
size_t pos = row_start;
|
||||||
|
for (size_t i = token_index; i < tokens->size(); ++i) {
|
||||||
|
const auto &token = (*tokens)[i];
|
||||||
|
if (token.start >= row_end)
|
||||||
|
break;
|
||||||
|
const size_t begin = std::max<size_t>(
|
||||||
|
token.start,
|
||||||
|
row_start
|
||||||
|
);
|
||||||
|
const size_t end = std::min<size_t>(
|
||||||
|
token.end,
|
||||||
|
row_end
|
||||||
|
);
|
||||||
|
if (begin > pos)
|
||||||
|
bed.io.write(l.data() + pos, begin - pos);
|
||||||
|
if (begin < end) {
|
||||||
|
bed.io.apply(token.type);
|
||||||
|
bed.io.write(l.data() + begin, end - begin);
|
||||||
|
bed.io.reset();
|
||||||
|
pos = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos < row_end)
|
||||||
|
bed.io.write(l.data() + pos, row_end - pos);
|
||||||
|
bed.io.reset();
|
||||||
|
} else {
|
||||||
|
bed.io.write(l.substr(row_start, row.length));
|
||||||
|
}
|
||||||
|
if (line == cursor.row && cursor.col >= row_start && cursor.col <= row_end) {
|
||||||
|
cursor_screen_row = screen_line;
|
||||||
|
cursor_screen_col = 0;
|
||||||
|
for_each_cluster(
|
||||||
|
l.substr(row_start, cursor.col - row_start),
|
||||||
|
[&](size_t, size_t, int width) {
|
||||||
|
cursor_screen_col += width;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++screen_line;
|
||||||
|
}
|
||||||
|
++line;
|
||||||
|
}
|
||||||
|
cursor_screen_col = std::min(cursor_screen_col, size_t(term_width - 1));
|
||||||
|
bed.io.move_cursor(cursor_screen_row, cursor_screen_col + 1);
|
||||||
|
bed.io.write("\x1b[?25h", 6);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::ui::text_mode
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
#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);
|
||||||
|
uint64_t current_line = 1;
|
||||||
|
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;
|
||||||
|
current_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);
|
||||||
|
uint64_t report_line = (uint64_t)((int64_t)current_line + line_delta);
|
||||||
|
if (on_edit)
|
||||||
|
on_edit(report_line, old_lines, new_lines);
|
||||||
|
line_delta += (int64_t)new_lines - (int64_t)old_lines;
|
||||||
|
current_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,12 +1,11 @@
|
|||||||
#include "vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
std::vector<RegexMatch> Vase::_regex_search(
|
namespace bed::internal::vase {
|
||||||
std::string_view pattern, Range range, std::string_view options
|
std::vector<RegexMatch> _regex_search(
|
||||||
|
Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
|
||||||
) {
|
) {
|
||||||
bool global = false;
|
bool global = false;
|
||||||
uint64_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) {
|
||||||
@@ -17,7 +16,7 @@ std::vector<RegexMatch> Vase::_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;
|
||||||
@@ -39,57 +38,9 @@ std::vector<RegexMatch> Vase::_regex_search(
|
|||||||
int errornumber;
|
int errornumber;
|
||||||
PCRE2_SIZE erroroffset;
|
PCRE2_SIZE erroroffset;
|
||||||
|
|
||||||
std::string out;
|
|
||||||
out.reserve(pattern.size() * 2);
|
|
||||||
bool escaped = false;
|
|
||||||
bool in_class = false;
|
|
||||||
bool in_quote = false;
|
|
||||||
for (size_t i = 0; i < pattern.size(); i++) {
|
|
||||||
char c = pattern[i];
|
|
||||||
if (escaped) {
|
|
||||||
out += c;
|
|
||||||
escaped = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == '\\') {
|
|
||||||
out += c;
|
|
||||||
if (i + 1 < pattern.size()) {
|
|
||||||
char next = pattern[i + 1];
|
|
||||||
if (next == 'Q')
|
|
||||||
in_quote = true;
|
|
||||||
else if (next == 'E')
|
|
||||||
in_quote = false;
|
|
||||||
out += next;
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (in_quote) {
|
|
||||||
out += c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == '[') {
|
|
||||||
in_class = true;
|
|
||||||
out += c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == ']' && in_class) {
|
|
||||||
in_class = false;
|
|
||||||
out += c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == '.' && !in_class) {
|
|
||||||
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,
|
||||||
@@ -101,12 +52,7 @@ std::vector<RegexMatch> Vase::_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);
|
||||||
|
|
||||||
uint64_t start_offset = offset_of(range.start);
|
PetalIterator it(root, Direction::Forward);
|
||||||
uint64_t end_offset = offset_of(range.end);
|
|
||||||
|
|
||||||
std::cout << (int)start_offset << ":" << (int)end_offset;
|
|
||||||
|
|
||||||
ChunkIterator it(root, Direction::Forward);
|
|
||||||
it.seek_offset(start_offset);
|
it.seek_offset(start_offset);
|
||||||
|
|
||||||
const char *data;
|
const char *data;
|
||||||
@@ -118,7 +64,7 @@ std::vector<RegexMatch> Vase::_regex_search(
|
|||||||
uint64_t offset = UINT64_MAX;
|
uint64_t offset = UINT64_MAX;
|
||||||
|
|
||||||
auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
|
auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
|
||||||
if (global_offset + (uint64_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 + (uint64_t)ovector[0],
|
.start = global_offset + (uint64_t)ovector[0],
|
||||||
@@ -128,8 +74,8 @@ std::vector<RegexMatch> Vase::_regex_search(
|
|||||||
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 + (uint64_t)s;
|
match.groups[i - 1].start = global_offset + (uint64_t)s;
|
||||||
match.groups[i].end = global_offset + (uint64_t)e;
|
match.groups[i - 1].end = global_offset + (uint64_t)e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.push_back(std::move(match));
|
results.push_back(std::move(match));
|
||||||
@@ -295,3 +241,4 @@ std::vector<RegexMatch> Vase::_regex_search(
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
} // namespace bed::internal::vase
|
||||||
@@ -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) {
|
||||||
|
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 (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();
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
throw ed_error("Couldn't open file.");
|
||||||
|
}
|
||||||
|
uint64_t total = std::filesystem::file_size(path);
|
||||||
|
if (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) {
|
||||||
|
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 (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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
#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 = end - last_line;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point eof_point(Shard *root) {
|
||||||
|
if (!root)
|
||||||
|
return {0, 0};
|
||||||
|
Point result{
|
||||||
|
.row = root->lines,
|
||||||
|
.col = 0
|
||||||
|
};
|
||||||
|
PetalIterator it(root, Direction::Forward);
|
||||||
|
it.seek_line(root->lines);
|
||||||
|
const char *data;
|
||||||
|
uint64_t len;
|
||||||
|
while (it.next(&data, &len))
|
||||||
|
result.col += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t offset_of(Shard *root, Point point) {
|
||||||
|
return offset_of(root, point.row) + point.col;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *replace(Shard *root, Shard *text, uint64_t start, uint64_t end) {
|
||||||
|
if (!start || !end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
start--;
|
||||||
|
end--;
|
||||||
|
if (!root)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t line_count = root->lines + 1;
|
||||||
|
if (start > end || end >= line_count)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t start_offset = offset_of(root, start);
|
||||||
|
uint64_t end_offset =
|
||||||
|
(end + 1 == line_count)
|
||||||
|
? root->length
|
||||||
|
: offset_of(root, end + 1) - 1;
|
||||||
|
auto [left, rest] = Shard::split(root, start_offset);
|
||||||
|
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
|
||||||
|
Shard *a = Shard::concat(left, text);
|
||||||
|
Shard *new_root = Shard::concat(a, right);
|
||||||
|
Shard::release(left);
|
||||||
|
Shard::release(rest);
|
||||||
|
Shard::release(middle);
|
||||||
|
Shard::release(right);
|
||||||
|
Shard::release(a);
|
||||||
|
Shard::release(root);
|
||||||
|
return new_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shard *erase(Shard *root, uint64_t start, uint64_t end) {
|
||||||
|
if (!start || !end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
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
|
||||||
+16
-16
@@ -1,21 +1,21 @@
|
|||||||
|
#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::BEd ed(args);
|
||||||
Vase vase = Vase(std::string(argv[1]));
|
ed.run();
|
||||||
|
} catch (bed::fatal_error &e) {
|
||||||
auto matches = vase.regex_search("hello", {{0, 0}, {vase.root->lines, UINT64_MAX}}, "g");
|
if (e.code)
|
||||||
|
printf("Fatal error: %s\n", e.what());
|
||||||
std::cout << "\n"
|
return e.code;
|
||||||
<< (int)vase.length() << " bytes\n"
|
}
|
||||||
<< (int)vase.root->lines << " lines\n"
|
#ifndef DEBUG
|
||||||
<< (int)matches.size() << " matches\n"
|
catch (...) {
|
||||||
<< "\n";
|
printf("Unexpected error.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t AppendBuffer::append(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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t AppendBuffer::append(const char *text, uint64_t length) {
|
|
||||||
uint64_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;
|
|
||||||
}
|
|
||||||
uint64_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(uint64_t pos, uint64_t *out_len) {
|
|
||||||
if (pos >= current_offset)
|
|
||||||
return nullptr;
|
|
||||||
uint64_t local_offset = pos % APPEND_CHUNK_SIZE;
|
|
||||||
if (out_len) {
|
|
||||||
uint64_t remaining = current_offset - pos;
|
|
||||||
uint64_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 uint64_t AppendBuffer::length() {
|
|
||||||
return current_offset;
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "vase/buffer/original.h"
|
|
||||||
#include "io/file.h"
|
|
||||||
|
|
||||||
OriginalBuffer::OriginalBuffer() {
|
|
||||||
char tmp_path[] = "/tmp/tbuf.XXXXXX";
|
|
||||||
fd = mkstemp(tmp_path);
|
|
||||||
if (fd == -1)
|
|
||||||
exit(1);
|
|
||||||
unlink(tmp_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
OriginalBuffer::~OriginalBuffer() {
|
|
||||||
if (buf)
|
|
||||||
munmap((char *)buf, len);
|
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OriginalBuffer::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");
|
|
||||||
}
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *OriginalBuffer::read(uint64_t pos, uint64_t *out_len) {
|
|
||||||
if (pos >= len)
|
|
||||||
return nullptr;
|
|
||||||
if (out_len)
|
|
||||||
*out_len = len - pos;
|
|
||||||
return buf + pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t OriginalBuffer::length() {
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
@@ -1,213 +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(uint64_t offset) {
|
|
||||||
stack.clear();
|
|
||||||
petal = nullptr;
|
|
||||||
petal_offset = 0;
|
|
||||||
global_offset = 0;
|
|
||||||
global_line = 0;
|
|
||||||
at_end = false;
|
|
||||||
if (!root)
|
|
||||||
return;
|
|
||||||
if (offset >= root->length)
|
|
||||||
offset = root->length - 1;
|
|
||||||
uint64_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;
|
|
||||||
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 ChunkIterator::seek_line(uint64_t line) {
|
|
||||||
stack.clear();
|
|
||||||
petal = nullptr;
|
|
||||||
petal_offset = 0;
|
|
||||||
global_offset = 0;
|
|
||||||
at_end = false;
|
|
||||||
bool last_line = false;
|
|
||||||
if (!root)
|
|
||||||
return;
|
|
||||||
if (dir == Direction::Backward) {
|
|
||||||
if (line > root->lines + 1)
|
|
||||||
line = root->lines + 1;
|
|
||||||
if (line == root->lines + 1)
|
|
||||||
last_line = true;
|
|
||||||
} else {
|
|
||||||
if (line > root->lines)
|
|
||||||
line = root->lines;
|
|
||||||
if (line == root->lines)
|
|
||||||
last_line = true;
|
|
||||||
}
|
|
||||||
Shard *curr = root;
|
|
||||||
while (curr) {
|
|
||||||
if (curr->kind == Shard::ShardKind::Petal) {
|
|
||||||
petal = (Petal *)curr;
|
|
||||||
if (last_line && dir == Direction::Backward) {
|
|
||||||
petal_offset = petal->length;
|
|
||||||
global_offset += petal->length;
|
|
||||||
} else {
|
|
||||||
const char *text = nullptr;
|
|
||||||
uint64_t remaining = 0;
|
|
||||||
uint64_t offset = 0;
|
|
||||||
while (line) {
|
|
||||||
if (!text) {
|
|
||||||
uint64_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;
|
|
||||||
if (last_line && dir == Direction::Forward)
|
|
||||||
at_end = true;
|
|
||||||
petal_offset = offset;
|
|
||||||
global_offset += 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t ChunkIterator::byte_offset() {
|
|
||||||
return global_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t ChunkIterator::line_offset() {
|
|
||||||
return global_line;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChunkIterator::next(const char **data, uint64_t *out_len) {
|
|
||||||
if (dir == Direction::Forward) {
|
|
||||||
while (true) {
|
|
||||||
if (petal) {
|
|
||||||
if (petal_offset == petal->length) {
|
|
||||||
if (at_end) {
|
|
||||||
*data = nullptr;
|
|
||||||
*out_len = 0;
|
|
||||||
at_end = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
petal = nullptr;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
uint64_t remaining = petal->length - petal_offset;
|
|
||||||
uint64_t got = 0;
|
|
||||||
const char *chunk = petal->source->read(petal->pos + petal_offset, &got);
|
|
||||||
if (got == 0) {
|
|
||||||
petal = nullptr;
|
|
||||||
petal_offset = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
uint64_t take = std::min(got, remaining);
|
|
||||||
*data = chunk;
|
|
||||||
*out_len = take;
|
|
||||||
petal_offset += take;
|
|
||||||
global_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;
|
|
||||||
}
|
|
||||||
uint64_t got = 0;
|
|
||||||
*data = petal->source->read(petal->pos, &got);
|
|
||||||
if (petal_offset > got) {
|
|
||||||
uint64_t got2 = 0;
|
|
||||||
*data = petal->source->read(petal->pos + got, &got2);
|
|
||||||
*out_len = std::min(got2, petal_offset - got);
|
|
||||||
petal_offset = got;
|
|
||||||
global_offset -= *out_len;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
*out_len = std::min(got, petal_offset);
|
|
||||||
petal_offset = 0;
|
|
||||||
global_offset -= *out_len;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
#include "vase/iterators/line.h"
|
|
||||||
|
|
||||||
LineIterator::LineIterator(Shard *r, uint64_t start_line, Direction dir)
|
|
||||||
: it(r, dir), dir(dir) {
|
|
||||||
int b = start_line == UINT64_MAX ? 0 : dir == Direction::Backward;
|
|
||||||
it.seek_line(start_line + b);
|
|
||||||
if (!it.next(&chunk, &len)) {
|
|
||||||
chunk = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (chunk == nullptr)
|
|
||||||
at_end = true;
|
|
||||||
if (dir == Direction::Forward)
|
|
||||||
chunk_offset = it.byte_offset() - len;
|
|
||||||
else
|
|
||||||
chunk_offset = it.byte_offset();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t LineIterator::byte_offset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LineIterator::next(std::string &line) {
|
|
||||||
line.clear();
|
|
||||||
if (at_end) {
|
|
||||||
offset = chunk_offset;
|
|
||||||
at_end = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!chunk)
|
|
||||||
return false;
|
|
||||||
if (dir == Direction::Forward) {
|
|
||||||
offset = chunk_offset;
|
|
||||||
while (true) {
|
|
||||||
const char *nl = (const char *)memchr(chunk, '\n', len);
|
|
||||||
if (!nl) {
|
|
||||||
if (len && chunk[len - 1] == '\r')
|
|
||||||
--len;
|
|
||||||
line.append(chunk, len);
|
|
||||||
chunk_offset += len;
|
|
||||||
if (!it.next(&chunk, &len)) {
|
|
||||||
chunk = nullptr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (chunk == nullptr)
|
|
||||||
return true;
|
|
||||||
chunk_offset = it.byte_offset() - len;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const char *end = nl;
|
|
||||||
if (end > chunk && *(end - 1) == '\r')
|
|
||||||
--end;
|
|
||||||
line.append(chunk, end);
|
|
||||||
uint64_t consumed = (nl - chunk) + 1;
|
|
||||||
chunk += consumed;
|
|
||||||
len -= consumed;
|
|
||||||
chunk_offset += consumed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (true) {
|
|
||||||
#if defined(__GLIBC__) || defined(__APPLE__)
|
|
||||||
const char *nl = (const char *)memrchr(chunk, '\n', len);
|
|
||||||
#else
|
|
||||||
const char *nl = nullptr;
|
|
||||||
uint64_t i = len;
|
|
||||||
while (i--) {
|
|
||||||
if (chunk[i] == '\n') {
|
|
||||||
nl = chunk + i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!nl) {
|
|
||||||
if (len && chunk[len - 1] == '\r')
|
|
||||||
--len;
|
|
||||||
line.insert(0, chunk, len);
|
|
||||||
chunk_offset -= len;
|
|
||||||
if (!it.next(&chunk, &len)) {
|
|
||||||
chunk = nullptr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
chunk_offset = it.byte_offset();
|
|
||||||
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;
|
|
||||||
offset = chunk_offset + (start - chunk);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#include "vase/vase.h"
|
|
||||||
|
|
||||||
std::vector<ReplacePart> Vase::parse_replace(std::string_view s) {
|
|
||||||
std::vector<ReplacePart> parts;
|
|
||||||
std::string constant;
|
|
||||||
auto flush_constant = [&]() {
|
|
||||||
if (!constant.empty()) {
|
|
||||||
uint64_t lines = 0;
|
|
||||||
uint64_t pos = append->append(constant.data(), (uint64_t)constant.size());
|
|
||||||
parts.push_back(
|
|
||||||
ReplacePart{
|
|
||||||
.type = ReplacePart::PartType::Constant,
|
|
||||||
.value = new Petal((uint64_t)constant.size(), lines, append, 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, Range range,
|
|
||||||
std::string_view replace, std::string_view options
|
|
||||||
) {
|
|
||||||
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
|
|
||||||
if (matches.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::vector<ReplacePart> replace_parts = parse_replace(replace);
|
|
||||||
|
|
||||||
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] = 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) {
|
|
||||||
uint64_t ls = group.start - match.start;
|
|
||||||
uint64_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Range> Vase::regex_search(
|
|
||||||
std::string_view pattern, Range range, std::string_view options
|
|
||||||
) {
|
|
||||||
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
|
|
||||||
if (matches.empty())
|
|
||||||
return {};
|
|
||||||
std::vector<Range> result;
|
|
||||||
result.reserve(matches.size());
|
|
||||||
for (auto match : matches)
|
|
||||||
result.push_back({point_of(match.start), point_of(match.end)});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,277 +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, 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::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;
|
|
||||||
uint64_t count[2]{0};
|
|
||||||
uint64_t read_offset = 0;
|
|
||||||
while (read_offset < p->length) {
|
|
||||||
uint64_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;
|
|
||||||
}
|
|
||||||
uint64_t nl_pos = read_offset + (uint64_t)(nl - c);
|
|
||||||
if (nl_pos < offset)
|
|
||||||
count[0]++;
|
|
||||||
else
|
|
||||||
count[1]++;
|
|
||||||
cursor = nl + 1;
|
|
||||||
}
|
|
||||||
read_offset += (uint64_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, uint64_t lo, uint64_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_file_shards(std::string &path, OriginalBuffer *o) {
|
|
||||||
int dest_fd = o->fd;
|
|
||||||
if (dest_fd == -1)
|
|
||||||
return nullptr;
|
|
||||||
int src_fd = open(path.c_str(), O_RDONLY);
|
|
||||||
if (src_fd == -1)
|
|
||||||
return nullptr;
|
|
||||||
struct stat st;
|
|
||||||
if (fstat(src_fd, &st) == -1)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
uint64_t total = (uint64_t)st.st_size;
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
ssize_t written = write(dest_fd, buf, take);
|
|
||||||
if (written != (ssize_t)take) {
|
|
||||||
close(src_fd);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
pieces.push_back(new Petal(take, lines, o, pos));
|
|
||||||
pos += take;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(src_fd);
|
|
||||||
|
|
||||||
if (pieces.empty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
o->initialize();
|
|
||||||
|
|
||||||
if (pieces.size() == 1)
|
|
||||||
return pieces[0];
|
|
||||||
return build_balanced(pieces.data(), 0, pieces.size());
|
|
||||||
}
|
|
||||||
@@ -1,398 +0,0 @@
|
|||||||
#include "vase/vase.h"
|
|
||||||
#include "utils/utils.h"
|
|
||||||
#include "vase/iterators/line.h"
|
|
||||||
|
|
||||||
Vase::Vase(std::string path) {
|
|
||||||
append = new AppendBuffer();
|
|
||||||
original = new OriginalBuffer();
|
|
||||||
root = create_file_shards(path, original);
|
|
||||||
history.push_back(root);
|
|
||||||
Shard::retain(root);
|
|
||||||
history_top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vase::~Vase() {
|
|
||||||
Shard::release(root);
|
|
||||||
for (auto s : history)
|
|
||||||
Shard::release(s);
|
|
||||||
delete original;
|
|
||||||
delete append;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Vase::length() {
|
|
||||||
return root->length;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Vase::to_string() {
|
|
||||||
std::string out;
|
|
||||||
ChunkIterator 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 Vase::to_string(Range range) {
|
|
||||||
std::string out;
|
|
||||||
ChunkIterator it(root, Direction::Forward);
|
|
||||||
uint64_t start = offset_of(range.start);
|
|
||||||
it.seek_offset(start);
|
|
||||||
const char *data;
|
|
||||||
uint64_t len;
|
|
||||||
uint64_t remaining = offset_of(range.end) - start;
|
|
||||||
while (remaining && it.next(&data, &len)) {
|
|
||||||
uint64_t n = std::min(len, remaining);
|
|
||||||
out.append(data, n);
|
|
||||||
remaining -= n;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
LineIterator Vase::iterate(uint64_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(uint64_t n) {
|
|
||||||
n = std::min(n, history_top);
|
|
||||||
for (uint64_t i = 0; i < n; ++i)
|
|
||||||
Shard::release(history[i]);
|
|
||||||
history.erase(history.begin(), history.begin() + n);
|
|
||||||
history_top -= n;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vase::save(std::string path) {
|
|
||||||
std::ofstream file(path, std::ios::binary);
|
|
||||||
if (!file)
|
|
||||||
return false;
|
|
||||||
ChunkIterator it(root, Direction::Forward);
|
|
||||||
it.seek_offset(0);
|
|
||||||
const char *data;
|
|
||||||
uint64_t len;
|
|
||||||
while (it.next(&data, &len)) {
|
|
||||||
file.write(data, len);
|
|
||||||
if (!file)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vase::save_swap(std::string path) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::insert(Point *point, char key) {
|
|
||||||
if (key == '\n')
|
|
||||||
*point = {point->row + 1, 0};
|
|
||||||
else
|
|
||||||
point->col++;
|
|
||||||
uint64_t pos = append->append(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, uint64_t len) {
|
|
||||||
while (len) {
|
|
||||||
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
|
|
||||||
_insert(point, data, chunk_size);
|
|
||||||
len -= chunk_size;
|
|
||||||
data += chunk_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::_insert(Point *point, const char *data, uint64_t len) {
|
|
||||||
if (len == 0)
|
|
||||||
return;
|
|
||||||
uint64_t offset = offset_of(*point);
|
|
||||||
uint64_t pos = append->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, 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, uint64_t amount, Direction dir) {
|
|
||||||
if (amount == 0)
|
|
||||||
return;
|
|
||||||
Point start = *point;
|
|
||||||
Point end = *point;
|
|
||||||
if (dir == Direction::Forward)
|
|
||||||
move_clusters(&end, amount, Direction::Forward);
|
|
||||||
else
|
|
||||||
move_clusters(&start, amount, Direction::Backward);
|
|
||||||
uint64_t start_offset = offset_of(start);
|
|
||||||
uint64_t end_offset = offset_of(end);
|
|
||||||
if (start_offset > end_offset)
|
|
||||||
std::swap(start_offset, end_offset);
|
|
||||||
uint64_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 (dir == Direction::Backward)
|
|
||||||
*point = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::erase(Range range) {
|
|
||||||
Point start = range.start;
|
|
||||||
Point end = range.end;
|
|
||||||
uint64_t start_offset = offset_of(start);
|
|
||||||
uint64_t end_offset = offset_of(end);
|
|
||||||
uint64_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, uint64_t len) {
|
|
||||||
erase(range);
|
|
||||||
insert(&range.start, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Vase::offset_of(Point point) {
|
|
||||||
LineIterator it(root, point.row, Direction::Forward);
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
uint64_t offset = 0;
|
|
||||||
if (it.next(line)) {
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.length();
|
|
||||||
while (point.col && remaining) {
|
|
||||||
uint64_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point Vase::point_of(uint64_t offset) {
|
|
||||||
ChunkIterator it(root, Direction::Backward);
|
|
||||||
it.seek_offset(offset);
|
|
||||||
Point p;
|
|
||||||
p.row = it.line_offset();
|
|
||||||
std::string line;
|
|
||||||
const char *chunk;
|
|
||||||
uint64_t len = 0;
|
|
||||||
if (!it.next(&chunk, &len))
|
|
||||||
return p;
|
|
||||||
while (true) {
|
|
||||||
#if defined(__GLIBC__) || defined(__APPLE__)
|
|
||||||
const char *nl = (const char *)memrchr(chunk, '\n', len);
|
|
||||||
#else
|
|
||||||
const char *nl = nullptr;
|
|
||||||
uint64_t i = len;
|
|
||||||
while (i--) {
|
|
||||||
if (chunk[i] == '\n') {
|
|
||||||
nl = chunk + i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!nl) {
|
|
||||||
if (len && chunk[len - 1] == '\r')
|
|
||||||
--len;
|
|
||||||
line.insert(0, chunk, len);
|
|
||||||
if (!it.next(&chunk, &len))
|
|
||||||
break;
|
|
||||||
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);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.length();
|
|
||||||
while (remaining) {
|
|
||||||
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
remaining -= next_len;
|
|
||||||
ptr += next_len;
|
|
||||||
p.col++;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) {
|
|
||||||
if (amount == 0)
|
|
||||||
return;
|
|
||||||
if (dir == Direction::Backward) {
|
|
||||||
LineIterator it(root, point->row, Direction::Backward);
|
|
||||||
while (amount) {
|
|
||||||
std::string line;
|
|
||||||
if (!it.next(line))
|
|
||||||
return;
|
|
||||||
std::vector<uint64_t> clusters;
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.size();
|
|
||||||
uint64_t byte = 0;
|
|
||||||
while (remaining) {
|
|
||||||
clusters.push_back(byte);
|
|
||||||
uint64_t len =
|
|
||||||
grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
byte += len;
|
|
||||||
}
|
|
||||||
while (amount && point->col) {
|
|
||||||
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) {
|
|
||||||
std::string line;
|
|
||||||
if (!it.next(line))
|
|
||||||
return;
|
|
||||||
if (point->col || amount) {
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.size();
|
|
||||||
uint64_t col = 0;
|
|
||||||
while (col < point->col && remaining) {
|
|
||||||
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
col++;
|
|
||||||
}
|
|
||||||
while (amount && remaining) {
|
|
||||||
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
point->col++;
|
|
||||||
amount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (amount) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
uint64_t clusters = 0;
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.size();
|
|
||||||
while (remaining) {
|
|
||||||
uint64_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, uint64_t amount, Direction dir) {
|
|
||||||
if (dir == Direction::Forward) {
|
|
||||||
point->row += amount;
|
|
||||||
} else {
|
|
||||||
if (amount > point->row)
|
|
||||||
point->row = 0;
|
|
||||||
else
|
|
||||||
point->row -= amount;
|
|
||||||
}
|
|
||||||
clamp(point);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user