Compare commits
2 Commits
main
...
experimental
| Author | SHA1 | Date | |
|---|---|---|---|
|
48fb8e3501
|
|||
|
cd13557e15
|
+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
|
||||||
|
|||||||
@@ -1,37 +1,4 @@
|
|||||||
# Crib
|
## BEd
|
||||||
|
|
||||||
A set of text editing tools (still in early development).
|
|
||||||
|
|
||||||
### Modules
|
|
||||||
|
|
||||||
#### `Vase`
|
|
||||||
|
|
||||||
Vase is a avl piece tree like structure which handles loading, insertion, erasure, regex searching, regex replacing etc.
|
|
||||||
<br/>
|
|
||||||
done.
|
|
||||||
|
|
||||||
#### `hl`
|
|
||||||
|
|
||||||
`hl` is a stateful super fast syntax highlighter.
|
|
||||||
<br/>
|
|
||||||
Not done yet.
|
|
||||||
|
|
||||||
#### `lsp`
|
|
||||||
|
|
||||||
`lsp` is a generic plug for lsp's.
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
It should support:
|
|
||||||
- Lsp over ssh or stdin/out or unix sockets etc.
|
|
||||||
- Handling multiple lsps at once (including over a single file.)
|
|
||||||
- Converting offsets encoding between utf16 - utf8 according to lsp standards.
|
|
||||||
- Loading lsp features etc. automatically.
|
|
||||||
- Error handling.
|
|
||||||
- And more.
|
|
||||||
|
|
||||||
Not done yet.
|
|
||||||
|
|
||||||
### BEd
|
|
||||||
|
|
||||||
Better ed.<br/>
|
Better ed.<br/>
|
||||||
An ed implementation (mostly posix compliant) but with:
|
An ed implementation (mostly posix compliant) but with:
|
||||||
@@ -65,11 +32,42 @@ An ed implementation (mostly posix compliant) but with:
|
|||||||
- File operations, directory & files listing.
|
- File operations, directory & files listing.
|
||||||
- Nerdfonts & themable colorized outputs.
|
- Nerdfonts & themable colorized outputs.
|
||||||
- Better address marking with ranges.
|
- Better address marking with ranges.
|
||||||
|
- text copy/paste & registers.
|
||||||
- & A lot more cool stuff.
|
- & A lot more cool stuff.
|
||||||
|
|
||||||
#### Implementation status:
|
### Implementation status:
|
||||||
|
|
||||||
- Internal buffer:
|
- Internal buffer:
|
||||||
- Super fast and memory efficient buffer implementation done.
|
- Super fast and memory efficient buffer implementation done.
|
||||||
- Loading from files/subshell commands done.
|
- Loading from files/subshell commands done.
|
||||||
- Most of the posix ed commands except regex ones done.
|
- Most of the posix ed commands except regex ones done.
|
||||||
|
|
||||||
|
### Modules
|
||||||
|
|
||||||
|
#### `Vase`
|
||||||
|
|
||||||
|
Vase is a avl piece tree like structure which handles loading, insertion, erasure, regex searching, regex replacing etc.
|
||||||
|
<br/>
|
||||||
|
done.
|
||||||
|
|
||||||
|
#### `hl`
|
||||||
|
|
||||||
|
`hl` is a stateful super fast syntax highlighter.
|
||||||
|
<br/>
|
||||||
|
mostly done.
|
||||||
|
|
||||||
|
#### `lsp`
|
||||||
|
|
||||||
|
`lsp` is a generic plug for lsp's.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
It should support:
|
||||||
|
- Lsp over ssh or stdin/out or unix sockets etc.
|
||||||
|
- Handling multiple lsps at once (including over a single file.)
|
||||||
|
- Converting offsets encoding between utf16 - utf8 according to lsp standards.
|
||||||
|
- Loading lsp features etc. automatically.
|
||||||
|
- Error handling.
|
||||||
|
- And more.
|
||||||
|
|
||||||
|
Not done yet.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
description = "Crib - An IDE.";
|
description = "BEd - Better Ed.";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
@@ -47,17 +47,16 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
crib = pkgs.stdenv.mkDerivation {
|
bed = pkgs.stdenv.mkDerivation {
|
||||||
pname = "crib";
|
pname = "bed";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
nativeBuildInputs = with hostPkgs; [
|
nativeBuildInputs = with hostPkgs; [
|
||||||
|
bash
|
||||||
pkg-config
|
pkg-config
|
||||||
gnumake
|
gnumake
|
||||||
xxd
|
|
||||||
bash
|
|
||||||
findutils
|
findutils
|
||||||
binutils
|
binutils
|
||||||
];
|
];
|
||||||
@@ -78,19 +77,19 @@
|
|||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp bin/crib $out/bin/crib
|
cp bin/bed $out/bin/bed
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
default = crib;
|
default = bed;
|
||||||
crib = crib;
|
bed = bed;
|
||||||
mruby = mruby;
|
mruby = mruby;
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.${system}.default = hostPkgs.mkShell {
|
devShells.${system}.default = hostPkgs.mkShell {
|
||||||
inputsFrom = [ crib ];
|
inputsFrom = [ bed ];
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
hostPkgs.clang-tools
|
hostPkgs.clang-tools
|
||||||
@@ -99,7 +98,6 @@
|
|||||||
hostPkgs.gnumake
|
hostPkgs.gnumake
|
||||||
hostPkgs.bear
|
hostPkgs.bear
|
||||||
hostPkgs.ruby
|
hostPkgs.ruby
|
||||||
hostPkgs.xxd
|
|
||||||
hostPkgs.gdb
|
hostPkgs.gdb
|
||||||
hostPkgs.binutils
|
hostPkgs.binutils
|
||||||
hostPkgs.findutils
|
hostPkgs.findutils
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
#include "internal/commands/commands.h"
|
||||||
|
#include "internal/commands/suffixes.h"
|
||||||
|
#include "internal/marks/marks.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
struct BEd {
|
||||||
|
internal::trie::Trie<internal::commands::Command> commands;
|
||||||
|
internal::commands::Command no_op;
|
||||||
|
internal::commands::Command eof_op;
|
||||||
|
std::array<std::optional<internal::commands::Suffix>, 26> suffixes;
|
||||||
|
|
||||||
|
bool help_mode = false;
|
||||||
|
std::string last_help = "";
|
||||||
|
bool prompt_mode = true;
|
||||||
|
std::function<std::string(BEd &)> prompt = nullptr;
|
||||||
|
bool suppress_mode = false;
|
||||||
|
std::string last_regex = "";
|
||||||
|
|
||||||
|
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
||||||
|
internal::buffer::Buffer *active;
|
||||||
|
|
||||||
|
BEd(std::vector<std::string> args);
|
||||||
|
~BEd();
|
||||||
|
void handle(std::string_view cmd, bool eof);
|
||||||
|
void run();
|
||||||
|
};
|
||||||
|
} // namespace bed
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace crib::cli {
|
|
||||||
struct Command {
|
|
||||||
void (*run)(std::vector<std::string>);
|
|
||||||
std::string (*summary)();
|
|
||||||
void (*help)();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct cli_error : std::runtime_error {
|
|
||||||
int exit_code;
|
|
||||||
cli_error(std::string msg, int code)
|
|
||||||
: std::runtime_error(std::move(msg)), exit_code(code) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern bool use_colors;
|
|
||||||
void help();
|
|
||||||
int execute(const std::string &name, const Command &cmd, int argc, char *argv[]);
|
|
||||||
int run(int argc, char *argv[]);
|
|
||||||
} // namespace crib::cli
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "internal/vase/vase.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
|
||||||
struct bed_error : std::runtime_error {
|
|
||||||
bed_error(const char *msg) : std::runtime_error(msg) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Command {
|
|
||||||
enum struct Type : uint8_t {
|
|
||||||
Invalid,
|
|
||||||
Quit,
|
|
||||||
ForceQuit,
|
|
||||||
PromptToggle,
|
|
||||||
HelpToggle,
|
|
||||||
Help,
|
|
||||||
Print,
|
|
||||||
List,
|
|
||||||
Number,
|
|
||||||
Append,
|
|
||||||
Insert,
|
|
||||||
Change,
|
|
||||||
Delete,
|
|
||||||
Write,
|
|
||||||
Join,
|
|
||||||
Mark,
|
|
||||||
Edit,
|
|
||||||
ForceEdit,
|
|
||||||
Filename,
|
|
||||||
Dump,
|
|
||||||
None
|
|
||||||
} type;
|
|
||||||
|
|
||||||
struct Address {
|
|
||||||
enum struct Type : uint8_t {
|
|
||||||
None,
|
|
||||||
Current,
|
|
||||||
Last,
|
|
||||||
Number,
|
|
||||||
Mark,
|
|
||||||
SearchForward,
|
|
||||||
SearchBackward
|
|
||||||
} type;
|
|
||||||
int64_t number = 0;
|
|
||||||
std::string regex = "";
|
|
||||||
int64_t offset = 0;
|
|
||||||
char mark = '\0';
|
|
||||||
};
|
|
||||||
|
|
||||||
Address start{};
|
|
||||||
Address end{};
|
|
||||||
static constexpr uint8_t SEMICOLON = 0b01;
|
|
||||||
static constexpr uint8_t RANGE = 0b10;
|
|
||||||
uint8_t address_flags = 0;
|
|
||||||
char suffix = '\0';
|
|
||||||
std::string argument;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BEd {
|
|
||||||
crib::internal::vase::Vase vase;
|
|
||||||
uint64_t line = 0;
|
|
||||||
bool modified = false;
|
|
||||||
bool quitting = false;
|
|
||||||
bool editing = false;
|
|
||||||
uint64_t marks[26]{0};
|
|
||||||
std::string last_regex = "";
|
|
||||||
bool help_mode = false;
|
|
||||||
bool suppress_mode = false;
|
|
||||||
bool prompt_mode = false;
|
|
||||||
std::string prompt = "*";
|
|
||||||
std::filesystem::path save_path = "";
|
|
||||||
std::string last_message = "";
|
|
||||||
|
|
||||||
BEd(std::string file_path, bool suppress_mode, std::string prompt_)
|
|
||||||
: vase("/tmp"), suppress_mode(suppress_mode), prompt(prompt_) {
|
|
||||||
if (prompt == "")
|
|
||||||
prompt = "*";
|
|
||||||
else
|
|
||||||
prompt_mode = true;
|
|
||||||
if (file_path != "")
|
|
||||||
handle("E " + file_path, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr);
|
|
||||||
void resolve_address(Command::Address addr, uint64_t *out_line);
|
|
||||||
Command parse(std::string cmd, bool eof);
|
|
||||||
void append(std::string text, uint64_t line);
|
|
||||||
void remove(uint64_t start_line, uint64_t end_line);
|
|
||||||
std::string list_string(std::string_view s);
|
|
||||||
void join(uint64_t start_line, uint64_t end_line);
|
|
||||||
bool handle(std::string cmd, bool eof);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string summary();
|
|
||||||
void help();
|
|
||||||
void run(std::vector<std::string>);
|
|
||||||
} // namespace crib::commands::bed
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/vase/vase.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
struct fatal_error : std::runtime_error {
|
||||||
|
uint8_t code;
|
||||||
|
fatal_error(std::string msg, uint8_t code)
|
||||||
|
: std::runtime_error(msg), code(code) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ed_error : std::runtime_error {
|
||||||
|
ed_error(std::string msg) : std::runtime_error(msg) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BEd;
|
||||||
|
} // namespace bed
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/generic.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::address {
|
||||||
|
struct address_error : ed_error {
|
||||||
|
address_error(const char *msg) : ed_error(msg) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Address {
|
||||||
|
// % refers to range of whatever was resulted from the previous modification.
|
||||||
|
// it expands in theory to a Num,Num and so can be followed by chaining more adresses the ed way.
|
||||||
|
// this is handled and resolved by the handle function,
|
||||||
|
// the constuctor and resolve etc. are also only called in the handle function,
|
||||||
|
// i.e. handle is the only public facing API from this namespace & class for now.
|
||||||
|
//
|
||||||
|
// in case of any issue an instance of address_error(const char *) is thrown.
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
std::array<uint64_t, 2> data{};
|
||||||
|
uint8_t size{0};
|
||||||
|
std::span<const uint64_t> span(uint8_t max = UINT8_MAX) const {
|
||||||
|
return {data.data(), std::min(size, max)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct None {};
|
||||||
|
|
||||||
|
// Posix ed types of addressing.
|
||||||
|
struct Current {}; // .
|
||||||
|
|
||||||
|
struct Last {}; // $
|
||||||
|
|
||||||
|
struct Number { // n
|
||||||
|
uint64_t i;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mark { // 'm
|
||||||
|
char m;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RegexLine { // /re/ or ?re?
|
||||||
|
internal::Direction dir;
|
||||||
|
std::string re;
|
||||||
|
};
|
||||||
|
|
||||||
|
// BEd Extended types of addressing.
|
||||||
|
struct Regex { // &/re/ or &?re?
|
||||||
|
internal::Direction dir; // returns next/previous line containing the regex
|
||||||
|
std::string re; // (not only if it matches full line)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Diagnostic { // #n# for diagnostic number n. (From lsp.)
|
||||||
|
uint16_t n;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiagnosticNext { // ^ 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:n> goto nth symbol reference
|
||||||
|
uint16_t n;
|
||||||
|
std::string sym;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SymbolReferenceNext { // >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.
|
||||||
|
};
|
||||||
|
|
||||||
|
std::variant<
|
||||||
|
std::monostate,
|
||||||
|
None,
|
||||||
|
Current,
|
||||||
|
Last,
|
||||||
|
Number,
|
||||||
|
Mark,
|
||||||
|
RegexLine,
|
||||||
|
Regex,
|
||||||
|
Diagnostic,
|
||||||
|
DiagnosticNext,
|
||||||
|
SymbolDefinition,
|
||||||
|
SymbolReference,
|
||||||
|
SymbolReferenceNext,
|
||||||
|
Block,
|
||||||
|
Scripted>
|
||||||
|
base{};
|
||||||
|
|
||||||
|
// they can then be followed by any number of +n or -n etc accumulating in.
|
||||||
|
int64_t offset = 0;
|
||||||
|
|
||||||
|
Address(std::string &cmd, uint64_t &i);
|
||||||
|
Address() = default;
|
||||||
|
|
||||||
|
uint64_t resolve(BEd &ctx);
|
||||||
|
|
||||||
|
static Result handle(BEd &ctx, std::string &cmd, uint64_t &i);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::address
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/marks/marks.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct Buffer {
|
||||||
|
internal::vase::Vase vase;
|
||||||
|
internal::marks::MarksEngine marks;
|
||||||
|
uint64_t line = 0;
|
||||||
|
bool modified;
|
||||||
|
std::filesystem::path save_path = "";
|
||||||
|
struct {
|
||||||
|
uint64_t start{0};
|
||||||
|
uint64_t end{0};
|
||||||
|
} prev_range;
|
||||||
|
|
||||||
|
Buffer();
|
||||||
|
Buffer(std::string command);
|
||||||
|
Buffer(std::filesystem::path path);
|
||||||
|
|
||||||
|
~Buffer() = default;
|
||||||
|
|
||||||
|
void load(std::string command);
|
||||||
|
void load(std::filesystem::path path);
|
||||||
|
void jump(uint64_t n_line);
|
||||||
|
void join(uint64_t start_line, uint64_t end_line);
|
||||||
|
void remove(uint64_t start_line, uint64_t end_line);
|
||||||
|
void append(std::string text, uint64_t line);
|
||||||
|
void print(uint64_t start_line, uint64_t end_line);
|
||||||
|
std::string list_string(std::string_view s);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/trie/trie.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::commands {
|
||||||
|
struct Command {
|
||||||
|
enum struct AddressMode : uint8_t {
|
||||||
|
None,
|
||||||
|
Single,
|
||||||
|
Range
|
||||||
|
} address_mode;
|
||||||
|
|
||||||
|
enum struct SuffixKind : uint8_t {
|
||||||
|
None,
|
||||||
|
Suffix,
|
||||||
|
Argument,
|
||||||
|
Continuation
|
||||||
|
} suffix;
|
||||||
|
|
||||||
|
std::string desc;
|
||||||
|
|
||||||
|
bool accept_zero;
|
||||||
|
|
||||||
|
void (*handle)(BEd &, std::span<const uint64_t>, std::string_view);
|
||||||
|
|
||||||
|
static void register_posix(BEd &ctx);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::commands
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::commands {
|
||||||
|
struct Suffix {
|
||||||
|
std::string desc;
|
||||||
|
void (*handle)(BEd &);
|
||||||
|
|
||||||
|
static void register_suffixes(BEd &ctx);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::commands
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../shard.h"
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal {
|
||||||
enum struct Direction : uint8_t {
|
enum struct Direction : uint8_t {
|
||||||
Forward,
|
Forward,
|
||||||
Backward
|
Backward
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::marks {
|
||||||
|
struct MarksEngine {};
|
||||||
|
} // namespace bed::internal::marks
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
#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>;
|
||||||
|
|
||||||
|
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<std::string> search(std::string_view prefix) {
|
||||||
|
std::vector<std::string> 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(
|
||||||
|
const Node &node,
|
||||||
|
std::string &key,
|
||||||
|
std::vector<std::string> &result
|
||||||
|
) {
|
||||||
|
if (node.value)
|
||||||
|
result.push_back(key);
|
||||||
|
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) {
|
||||||
|
Node *current = &root;
|
||||||
|
uint64_t pos = 0;
|
||||||
|
uint64_t longest = 0;
|
||||||
|
if (current->value)
|
||||||
|
longest = 0;
|
||||||
|
while (pos < input.size()) {
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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(Node &node, char first) {
|
||||||
|
for (auto *child : node.children)
|
||||||
|
if (equal_char(child->edge.front(), first))
|
||||||
|
return child;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::trie
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct AppendBuffer : Buffer {
|
struct AppendBuffer : Buffer {
|
||||||
char *buf = nullptr;
|
char *buf = nullptr;
|
||||||
uint64_t allocated_capacity = 0;
|
uint64_t allocated_capacity = 0;
|
||||||
@@ -22,4 +22,4 @@ struct AppendBuffer : Buffer {
|
|||||||
private:
|
private:
|
||||||
void grow(uint64_t len);
|
void grow(uint64_t len);
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
virtual const char *read(uint64_t pos) = 0;
|
virtual const char *read(uint64_t pos) = 0;
|
||||||
virtual uint64_t length() = 0;
|
virtual uint64_t length() = 0;
|
||||||
virtual ~Buffer() = default;
|
virtual ~Buffer() = default;
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct OriginalBuffer : Buffer {
|
struct OriginalBuffer : Buffer {
|
||||||
const char *buf;
|
const char *buf;
|
||||||
uint64_t len;
|
uint64_t len;
|
||||||
@@ -16,4 +16,4 @@ struct OriginalBuffer : Buffer {
|
|||||||
const char *read(uint64_t pos) override;
|
const char *read(uint64_t pos) override;
|
||||||
uint64_t length() override;
|
uint64_t length() override;
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024;
|
constexpr uint64_t PETAL_SIZE_MAX = 32 * 1024;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "petal.h"
|
#include "petal.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct LineIterator {
|
struct LineIterator {
|
||||||
PetalIterator it;
|
PetalIterator it;
|
||||||
const char *chunk;
|
const char *chunk;
|
||||||
@@ -70,4 +70,4 @@ struct Iterator {
|
|||||||
private:
|
private:
|
||||||
LineIterator it;
|
LineIterator it;
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../shard.h"
|
#include "../shard.h"
|
||||||
#include "iter.h"
|
#include "internal/generic.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct PetalIterator {
|
struct PetalIterator {
|
||||||
Direction dir;
|
Direction dir;
|
||||||
Shard *root = nullptr;
|
Shard *root = nullptr;
|
||||||
@@ -26,4 +26,4 @@ struct PetalIterator {
|
|||||||
private:
|
private:
|
||||||
Petal *_next(uint64_t *offset);
|
Petal *_next(uint64_t *offset);
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Shard {
|
struct Shard {
|
||||||
enum struct Kind : uint8_t {
|
enum struct Kind : uint8_t {
|
||||||
Branch,
|
Branch,
|
||||||
@@ -63,4 +63,4 @@ struct Petal : Shard {
|
|||||||
source(source), pos(pos) {};
|
source(source), pos(pos) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "shard.h"
|
#include "shard.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Point {
|
struct Point {
|
||||||
uint64_t row;
|
uint64_t row;
|
||||||
uint64_t col;
|
uint64_t col;
|
||||||
@@ -114,4 +114,4 @@ private:
|
|||||||
std::string_view pattern, Range range, std::string_view options
|
std::string_view pattern, Range range, std::string_view options
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/address/address.h"
|
||||||
|
#include "internal/commands/commands.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
void BEd::handle(std::string_view cmd_, bool eof) {
|
||||||
|
using namespace internal::commands;
|
||||||
|
std::string cmd(cmd_);
|
||||||
|
if (eof) {
|
||||||
|
eof_op.handle(*this, {}, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t i = 0;
|
||||||
|
auto skip_space = [&] {
|
||||||
|
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
||||||
|
++i;
|
||||||
|
};
|
||||||
|
skip_space();
|
||||||
|
auto addresses = internal::address::Address::handle(*this, cmd, i);
|
||||||
|
if (i >= cmd.size()) {
|
||||||
|
if (!no_op.accept_zero)
|
||||||
|
for (auto l : addresses.span())
|
||||||
|
if (l == 0)
|
||||||
|
throw ed_error("Invalid address given.");
|
||||||
|
switch (no_op.address_mode) {
|
||||||
|
case Command::AddressMode::None:
|
||||||
|
no_op.handle(*this, addresses.span(0), "");
|
||||||
|
break;
|
||||||
|
case Command::AddressMode::Single:
|
||||||
|
no_op.handle(*this, addresses.span(1), "");
|
||||||
|
break;
|
||||||
|
case Command::AddressMode::Range:
|
||||||
|
no_op.handle(*this, addresses.span(2), "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t len = commands.longest_match(cmd.substr(i));
|
||||||
|
if (len == 0)
|
||||||
|
throw ed_error("Command not found.");
|
||||||
|
std::optional<Command> command_opt = commands.get(cmd.substr(i, len));
|
||||||
|
i += len;
|
||||||
|
if (!command_opt)
|
||||||
|
throw ed_error("Command error.");
|
||||||
|
Command command = command_opt.value();
|
||||||
|
std::string argument;
|
||||||
|
Suffix *suffix = nullptr;
|
||||||
|
switch (command.suffix) {
|
||||||
|
case Command::SuffixKind::None:
|
||||||
|
skip_space();
|
||||||
|
if (i < cmd.size())
|
||||||
|
throw ed_error("Command error.");
|
||||||
|
break;
|
||||||
|
case Command::SuffixKind::Suffix:
|
||||||
|
skip_space();
|
||||||
|
if (i < cmd.size()) {
|
||||||
|
if (suffixes[cmd[i] - 'a'].has_value())
|
||||||
|
suffix = &(suffixes[cmd[i++] - 'a'].value());
|
||||||
|
skip_space();
|
||||||
|
std::cout << i << " " << cmd << std::endl;
|
||||||
|
if (i < cmd.size())
|
||||||
|
throw ed_error("Command error.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::SuffixKind::Argument:
|
||||||
|
if (i < cmd.size()) {
|
||||||
|
if (cmd[i] == ' ' || cmd[i] == '\t')
|
||||||
|
skip_space();
|
||||||
|
else
|
||||||
|
throw ed_error("Command Error.");
|
||||||
|
argument = cmd.substr(i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Command::SuffixKind::Continuation:
|
||||||
|
argument = cmd.substr(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!command.accept_zero)
|
||||||
|
for (auto l : addresses.span())
|
||||||
|
if (l == 0)
|
||||||
|
throw ed_error("Invalid address given.");
|
||||||
|
switch (command.address_mode) {
|
||||||
|
case Command::AddressMode::None:
|
||||||
|
command.handle(*this, addresses.span(0), argument);
|
||||||
|
break;
|
||||||
|
case Command::AddressMode::Single:
|
||||||
|
command.handle(*this, addresses.span(1), argument);
|
||||||
|
break;
|
||||||
|
case Command::AddressMode::Range:
|
||||||
|
command.handle(*this, addresses.span(2), argument);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (suffix)
|
||||||
|
suffix->handle(*this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} // namespace bed
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed {
|
||||||
|
BEd::BEd(std::vector<std::string> args) {
|
||||||
|
internal::commands::Command::register_posix(*this);
|
||||||
|
internal::commands::Suffix::register_suffixes(*this);
|
||||||
|
std::string prompt_ = "";
|
||||||
|
std::string file = "";
|
||||||
|
bool suppress = false;
|
||||||
|
for (size_t i = 1; i < args.size(); i++) {
|
||||||
|
if (args[i] == "-p") {
|
||||||
|
i++;
|
||||||
|
if (i >= args.size())
|
||||||
|
throw fatal_error("Prompt not specified!", 1);
|
||||||
|
prompt_ = args[i];
|
||||||
|
} else if (args[i] == "-s") {
|
||||||
|
suppress = true;
|
||||||
|
} else {
|
||||||
|
if (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;
|
||||||
|
active = new internal::buffer::Buffer();
|
||||||
|
buffers.emplace("0", active);
|
||||||
|
if (file != "")
|
||||||
|
handle("E " + file, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEd::~BEd() {
|
||||||
|
for (auto &[_, buffer] : buffers)
|
||||||
|
delete buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BEd::run() {
|
||||||
|
while (true) {
|
||||||
|
std::string cmd;
|
||||||
|
if (prompt_mode)
|
||||||
|
std::cout << prompt(*this);
|
||||||
|
bool eof = !std::getline(std::cin, cmd);
|
||||||
|
try {
|
||||||
|
handle(cmd, eof);
|
||||||
|
} catch (ed_error &e) {
|
||||||
|
std::cout << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#include "cli.h"
|
|
||||||
#include "commands/bed/bed.h"
|
|
||||||
|
|
||||||
namespace crib::cli {
|
|
||||||
static const std::unordered_map<std::string, Command> commands = {
|
|
||||||
{"bed",
|
|
||||||
{crib::commands::bed::run,
|
|
||||||
crib::commands::bed::summary,
|
|
||||||
crib::commands::bed::help}},
|
|
||||||
};
|
|
||||||
|
|
||||||
bool use_color() {
|
|
||||||
if (std::getenv("NO_COLOR"))
|
|
||||||
return false;
|
|
||||||
if (std::getenv("CLICOLOR_FORCE"))
|
|
||||||
return true;
|
|
||||||
if (!isatty(STDERR_FILENO))
|
|
||||||
return false;
|
|
||||||
const char *term = std::getenv("TERM");
|
|
||||||
if (!term || std::string_view(term) == "dumb" || std::string_view(term) == "xterm")
|
|
||||||
return false;
|
|
||||||
if (const char *clicolor = std::getenv("CLICOLOR"))
|
|
||||||
return std::string_view(clicolor) != "0";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool use_colors = use_color();
|
|
||||||
|
|
||||||
void help() {
|
|
||||||
std::cout << "Usage: crib <command> [options]\n\n"
|
|
||||||
<< "Where command is one of\n";
|
|
||||||
for (auto &[name, cmd] : commands)
|
|
||||||
std::cout << " " << name << " - " << cmd.summary() << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
int execute(const std::string &name, const Command &cmd, int argc, char *argv[]) {
|
|
||||||
try {
|
|
||||||
std::vector<std::string> args(argv, argv + argc);
|
|
||||||
cmd.run(args);
|
|
||||||
return 0;
|
|
||||||
} catch (const cli_error &e) {
|
|
||||||
std::cerr << "Error running " << name << ": " << e.what() << '\n';
|
|
||||||
return e.exit_code;
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
std::cerr << "Error running " << name << ": " << e.what() << '\n';
|
|
||||||
return 1;
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "Unknown error running " << name << '\n';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int run(int argc, char *argv[]) {
|
|
||||||
std::string invoked_as = std::filesystem::path(argv[0]).filename();
|
|
||||||
|
|
||||||
if (auto it = commands.find(invoked_as); it != commands.end())
|
|
||||||
return execute(it->first, it->second, argc, argv);
|
|
||||||
|
|
||||||
if (argc > 1)
|
|
||||||
if (auto it = commands.find(argv[1]); it != commands.end())
|
|
||||||
return execute(it->first, it->second, argc - 1, argv + 1);
|
|
||||||
|
|
||||||
if (argc > 1 && std::string(argv[1]) == "--help") {
|
|
||||||
help();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cerr << "Invalid command given.\n\n";
|
|
||||||
help();
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
} // namespace crib::cli
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#include "commands/bed/bed.h"
|
|
||||||
#include "cli.h"
|
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
|
||||||
std::string summary() {
|
|
||||||
return "Better Ed, a modern take on a posix compliant line editor.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void help() {}
|
|
||||||
|
|
||||||
void run(std::vector<std::string> args) {
|
|
||||||
std::string prompt = "";
|
|
||||||
std::string file;
|
|
||||||
bool suppress = false;
|
|
||||||
for (size_t i = 1; i < args.size(); i++) {
|
|
||||||
if (args[i] == "-p") {
|
|
||||||
i++;
|
|
||||||
if (i >= args.size())
|
|
||||||
throw crib::cli::cli_error("Prompt not specified!", 1);
|
|
||||||
prompt = args[i];
|
|
||||||
} else if (args[i] == "-s") {
|
|
||||||
suppress = true;
|
|
||||||
} else {
|
|
||||||
if (file.size())
|
|
||||||
throw crib::cli::cli_error("Invalid arguments given.", 1);
|
|
||||||
file = args[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BEd bed(file, suppress, prompt);
|
|
||||||
std::string command;
|
|
||||||
while (true) {
|
|
||||||
std::string cmd;
|
|
||||||
if (bed.prompt_mode)
|
|
||||||
std::cout << bed.prompt;
|
|
||||||
bool eof = !std::getline(std::cin, cmd);
|
|
||||||
if (!bed.handle(cmd, eof))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace crib::commands::bed
|
|
||||||
@@ -1,384 +0,0 @@
|
|||||||
#include "commands/bed/bed.h"
|
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
|
||||||
bool BEd::handle(std::string cmd, bool eof) {
|
|
||||||
using namespace crib::internal::vase;
|
|
||||||
try {
|
|
||||||
if (line > vase.lines())
|
|
||||||
line = vase.lines();
|
|
||||||
Command command = parse(cmd, eof);
|
|
||||||
bool was_quitting = quitting;
|
|
||||||
quitting = false;
|
|
||||||
bool was_editing = editing;
|
|
||||||
editing = false;
|
|
||||||
switch (command.type) {
|
|
||||||
case Command::Type::Quit:
|
|
||||||
if (modified && !was_quitting) {
|
|
||||||
quitting = true;
|
|
||||||
throw bed_error("Buffer modified.");
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
case Command::Type::ForceQuit:
|
|
||||||
return false;
|
|
||||||
case Command::Type::None: {
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line);
|
|
||||||
else
|
|
||||||
resolve_address(command.start, &line);
|
|
||||||
} else {
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
if (line == 0)
|
|
||||||
throw bed_error("Line 0 is invalid.");
|
|
||||||
if (line > vase.lines())
|
|
||||||
throw bed_error("Line position too high.");
|
|
||||||
Iterator it = vase.iterate(line - 1, Direction::Forward);
|
|
||||||
it.next();
|
|
||||||
std::cout << it.line << std::endl;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Invalid:
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
case Command::Type::HelpToggle:
|
|
||||||
help_mode = !help_mode;
|
|
||||||
if (help_mode && last_message != "")
|
|
||||||
std::cout << last_message << std::endl;
|
|
||||||
break;
|
|
||||||
case Command::Type::Help:
|
|
||||||
if (last_message != "")
|
|
||||||
std::cout << last_message << std::endl;
|
|
||||||
break;
|
|
||||||
case Command::Type::PromptToggle:
|
|
||||||
prompt_mode = !prompt_mode;
|
|
||||||
break;
|
|
||||||
case Command::Type::Print: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_start == 0)
|
|
||||||
throw bed_error("Line 0 is invalid.");
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
|
||||||
while (it.next() && line_start++ <= line_end)
|
|
||||||
std::cout << it.line << std::endl;
|
|
||||||
line = line_end;
|
|
||||||
} break;
|
|
||||||
case Command::Type::List: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_start == 0)
|
|
||||||
throw bed_error("Line 0 is invalid.");
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
|
||||||
while (it.next() && line_start++ <= line_end)
|
|
||||||
std::cout << list_string(it.line) << std::endl;
|
|
||||||
line = line_end;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Number: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_start == 0)
|
|
||||||
throw bed_error("Line 0 is invalid.");
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
|
||||||
while (it.next() && line_start <= line_end)
|
|
||||||
std::cout << line_start++ << "\t" << it.line << std::endl;
|
|
||||||
line = line_end;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Append: {
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line);
|
|
||||||
else
|
|
||||||
resolve_address(command.start, &line);
|
|
||||||
}
|
|
||||||
std::string text;
|
|
||||||
std::string cline;
|
|
||||||
uint64_t line_count = 0;
|
|
||||||
while (std::getline(std::cin, cline)) {
|
|
||||||
if (cline == ".")
|
|
||||||
break;
|
|
||||||
line_count++;
|
|
||||||
text.append(cline);
|
|
||||||
text.push_back('\n');
|
|
||||||
}
|
|
||||||
if (text.empty()) {
|
|
||||||
if (line == 0)
|
|
||||||
line = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
vase.snapshot();
|
|
||||||
append(text, line);
|
|
||||||
modified = true;
|
|
||||||
line += line_count;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Insert: {
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line);
|
|
||||||
else
|
|
||||||
resolve_address(command.start, &line);
|
|
||||||
}
|
|
||||||
if (line == 0)
|
|
||||||
line = 1;
|
|
||||||
std::string text;
|
|
||||||
std::string cline;
|
|
||||||
uint64_t line_count = 0;
|
|
||||||
while (std::getline(std::cin, cline)) {
|
|
||||||
if (cline == ".")
|
|
||||||
break;
|
|
||||||
line_count++;
|
|
||||||
text.append(cline);
|
|
||||||
text.push_back('\n');
|
|
||||||
}
|
|
||||||
if (text.empty())
|
|
||||||
break;
|
|
||||||
vase.snapshot();
|
|
||||||
append(text, line - 1);
|
|
||||||
modified = true;
|
|
||||||
line += line_count - 1;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Change: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
if (line_start == 0)
|
|
||||||
line_start = 1;
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
std::string text;
|
|
||||||
std::string cline;
|
|
||||||
uint64_t line_count = 0;
|
|
||||||
while (std::getline(std::cin, cline)) {
|
|
||||||
if (cline == ".")
|
|
||||||
break;
|
|
||||||
line_count++;
|
|
||||||
text.append(cline);
|
|
||||||
text.push_back('\n');
|
|
||||||
}
|
|
||||||
vase.snapshot();
|
|
||||||
remove(line_start, line_end);
|
|
||||||
line = line_start;
|
|
||||||
if (text.empty())
|
|
||||||
break;
|
|
||||||
append(text, line);
|
|
||||||
modified = true;
|
|
||||||
line += line_count;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Delete: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
if (line_start == 0)
|
|
||||||
line_start = 1;
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
vase.snapshot();
|
|
||||||
vase.erase({{line_start - 1, 0}, {line_end, 0}});
|
|
||||||
modified = true;
|
|
||||||
line = line_start;
|
|
||||||
if (line > vase.lines())
|
|
||||||
line = vase.lines();
|
|
||||||
} break;
|
|
||||||
case Command::Type::Write: {
|
|
||||||
std::string path = command.argument;
|
|
||||||
if (path.empty()) {
|
|
||||||
path = save_path;
|
|
||||||
if (path.empty())
|
|
||||||
throw bed_error("Need file to write to.");
|
|
||||||
}
|
|
||||||
uint64_t line_start = 1;
|
|
||||||
uint64_t line_end = vase.lines();
|
|
||||||
if (vase.lines() == 0)
|
|
||||||
line_start = line_end = 0;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (vase.lines() && line_start == 0)
|
|
||||||
throw bed_error("Line 0 is invalid.");
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
uint64_t bytes = 0;
|
|
||||||
if (path[0] == '!') {
|
|
||||||
FILE *pipe = popen(path.c_str() + 1, "w");
|
|
||||||
if (!pipe)
|
|
||||||
throw bed_error("Error starting command.");
|
|
||||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
|
||||||
while (it.next() && line_start++ <= line_end) {
|
|
||||||
it.line += '\n';
|
|
||||||
bytes += it.line.size();
|
|
||||||
if (fputs(it.line.c_str(), pipe) == EOF) {
|
|
||||||
pclose(pipe);
|
|
||||||
throw bed_error("Error writing to command.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pclose(pipe) == -1)
|
|
||||||
throw bed_error("Error closing command.");
|
|
||||||
} else {
|
|
||||||
std::ofstream file(path, std::ios::out | std::ios::trunc);
|
|
||||||
if (!file)
|
|
||||||
throw bed_error("Error writing to file.");
|
|
||||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
|
||||||
while (it.next() && line_start++ <= line_end) {
|
|
||||||
file << it.line << '\n';
|
|
||||||
bytes += it.line.size() + 1;
|
|
||||||
}
|
|
||||||
if (!file)
|
|
||||||
throw bed_error("Error writing to file.");
|
|
||||||
save_path = path;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
if (!suppress_mode)
|
|
||||||
std::cout << bytes << std::endl;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Join: {
|
|
||||||
uint64_t line_start = line;
|
|
||||||
uint64_t line_end = line + 1;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
resolve_address(command.start, &line_start);
|
|
||||||
line_end = line_start;
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &line_end);
|
|
||||||
}
|
|
||||||
if (line_end < line_start)
|
|
||||||
throw bed_error("Invalid address range.");
|
|
||||||
if (line_start == 0)
|
|
||||||
throw bed_error("Invalid line number given.");
|
|
||||||
if (line_end == line_start)
|
|
||||||
break;
|
|
||||||
vase.snapshot();
|
|
||||||
join(line_start, line_end);
|
|
||||||
line = line_start;
|
|
||||||
modified = true;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Mark: {
|
|
||||||
uint64_t mark_line = line;
|
|
||||||
if (command.start.type != Command::Address::Type::None) {
|
|
||||||
if (command.address_flags & Command::RANGE)
|
|
||||||
resolve_address(command.end, &mark_line);
|
|
||||||
else
|
|
||||||
resolve_address(command.start, &mark_line);
|
|
||||||
}
|
|
||||||
if (mark_line == 0)
|
|
||||||
throw bed_error("Invalid line number given.");
|
|
||||||
char mark = command.argument[0];
|
|
||||||
marks[mark - 'a'] = mark_line;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Edit: {
|
|
||||||
if (modified && !was_editing) {
|
|
||||||
editing = true;
|
|
||||||
throw bed_error("Buffer modified.");
|
|
||||||
}
|
|
||||||
std::string path = command.argument;
|
|
||||||
if (path.empty()) {
|
|
||||||
path = save_path;
|
|
||||||
if (path.empty())
|
|
||||||
throw bed_error("Need file to read from.");
|
|
||||||
}
|
|
||||||
if (path[0] == '!') {
|
|
||||||
path.erase(1);
|
|
||||||
Vase new_vase(std::string(path), "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
Vase new_vase(std::filesystem::path(path), "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
save_path = path;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
line = vase.lines();
|
|
||||||
if (!suppress_mode)
|
|
||||||
std::cout << vase.length() << std::endl;
|
|
||||||
} break;
|
|
||||||
case Command::Type::ForceEdit: {
|
|
||||||
std::string path = command.argument;
|
|
||||||
if (path.empty()) {
|
|
||||||
path = save_path;
|
|
||||||
if (path.empty())
|
|
||||||
throw bed_error("Need file to read from.");
|
|
||||||
}
|
|
||||||
if (path[0] == '!') {
|
|
||||||
path.erase(0, 1);
|
|
||||||
Vase new_vase(std::string(path), "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
modified = true;
|
|
||||||
} else {
|
|
||||||
Vase new_vase(std::filesystem::path(path), "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
save_path = path;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
line = vase.lines();
|
|
||||||
std::fill(std::begin(marks), std::end(marks), '\0');
|
|
||||||
if (!suppress_mode)
|
|
||||||
std::cout << vase.length() << std::endl;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Filename: {
|
|
||||||
std::string path = command.argument;
|
|
||||||
if (path.empty())
|
|
||||||
throw bed_error("No filename given.");
|
|
||||||
save_path = path;
|
|
||||||
std::cout << save_path.string() << std::endl;
|
|
||||||
} break;
|
|
||||||
case Command::Type::Dump:
|
|
||||||
Shard::dump(vase.root);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (command.suffix) {
|
|
||||||
Iterator it = vase.iterate(line - 1, Direction::Forward);
|
|
||||||
it.next();
|
|
||||||
switch (command.suffix) {
|
|
||||||
case 'p':
|
|
||||||
std::cout << it.line << std::endl;
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
std::cout << line << "\t" << it.line << std::endl;
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
std::cout << list_string(it.line) << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (bed_error &e) {
|
|
||||||
last_message = e.what();
|
|
||||||
std::cout << "?" << std::endl;
|
|
||||||
if (help_mode)
|
|
||||||
std::cout << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace crib::commands::bed
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#include "commands/bed/bed.h"
|
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
|
||||||
void BEd::append(std::string text, uint64_t line) {
|
|
||||||
using namespace crib::internal::vase;
|
|
||||||
Point p = {line, 0};
|
|
||||||
if (!vase.lines()) {
|
|
||||||
text.pop_back();
|
|
||||||
} else if (line == vase.lines()) {
|
|
||||||
p.row--;
|
|
||||||
p.col = UINT64_MAX;
|
|
||||||
text = "\n" + text;
|
|
||||||
text.pop_back();
|
|
||||||
}
|
|
||||||
vase.insert(&p, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BEd::remove(uint64_t start_line, uint64_t end_line) {
|
|
||||||
vase.erase({{start_line - 1, 0}, {end_line, 0}});
|
|
||||||
}
|
|
||||||
|
|
||||||
void BEd::join(uint64_t start_line, uint64_t end_line) {
|
|
||||||
vase.regex_search_replace("\\n", {{start_line - 1, 0}, {end_line, 0}}, "", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string BEd::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 crib::commands::bed
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
#include "commands/bed/bed.h"
|
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
|
||||||
Command BEd::parse(std::string cmd, bool eof) {
|
|
||||||
Command command = {};
|
|
||||||
if (eof)
|
|
||||||
return (command.type = Command::Type::Quit, command);
|
|
||||||
uint64_t i = 0;
|
|
||||||
auto skip_space = [&] {
|
|
||||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
|
||||||
++i;
|
|
||||||
};
|
|
||||||
Command::Address first;
|
|
||||||
Command::Address second;
|
|
||||||
parse_address(cmd, i, first);
|
|
||||||
bool have_range = false;
|
|
||||||
while (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
|
|
||||||
char sep = cmd[i++];
|
|
||||||
if (sep == ';')
|
|
||||||
resolve_address(first, &line);
|
|
||||||
command.address_flags = sep == ';' ? Command::SEMICOLON : 0;
|
|
||||||
if (have_range)
|
|
||||||
first = second;
|
|
||||||
parse_address(cmd, i, second);
|
|
||||||
have_range = true;
|
|
||||||
}
|
|
||||||
if (have_range) {
|
|
||||||
if (first.type != Command::Address::Type::None
|
|
||||||
&& second.type == Command::Address::Type::None) {
|
|
||||||
second = first;
|
|
||||||
} else if (first.type == Command::Address::Type::None) {
|
|
||||||
if (command.address_flags & Command::SEMICOLON) {
|
|
||||||
first.type = Command::Address::Type::Current;
|
|
||||||
} else {
|
|
||||||
first.type = Command::Address::Type::Number;
|
|
||||||
first.number = 1;
|
|
||||||
}
|
|
||||||
if (second.type == Command::Address::Type::None)
|
|
||||||
second.type = Command::Address::Type::Last;
|
|
||||||
}
|
|
||||||
command.address_flags |= Command::RANGE;
|
|
||||||
command.start = first;
|
|
||||||
command.end = second;
|
|
||||||
} else {
|
|
||||||
command.start = first;
|
|
||||||
}
|
|
||||||
if (i >= cmd.size()) {
|
|
||||||
command.type = Command::Type::None;
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
switch (cmd[i]) {
|
|
||||||
case 'q':
|
|
||||||
command.type = Command::Type::Quit;
|
|
||||||
skip_space();
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
case 'Q':
|
|
||||||
command.type = Command::Type::ForceQuit;
|
|
||||||
skip_space();
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
case 'p':
|
|
||||||
command.type = Command::Type::Print;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'l':
|
|
||||||
command.type = Command::Type::List;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
command.type = Command::Type::Number;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'P':
|
|
||||||
command.type = Command::Type::PromptToggle;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'H':
|
|
||||||
command.type = Command::Type::HelpToggle;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
command.type = Command::Type::Help;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
command.type = Command::Type::Append;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
command.type = Command::Type::Insert;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
command.type = Command::Type::Change;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
command.type = Command::Type::Delete;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'j':
|
|
||||||
command.type = Command::Type::Join;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
command.type = Command::Type::Write;
|
|
||||||
i++;
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
|
||||||
skip_space();
|
|
||||||
else
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
command.argument = cmd.substr(i);
|
|
||||||
return command;
|
|
||||||
case 'e':
|
|
||||||
command.type = Command::Type::Edit;
|
|
||||||
i++;
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
|
||||||
skip_space();
|
|
||||||
else
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
command.argument = cmd.substr(i);
|
|
||||||
return command;
|
|
||||||
case 'E':
|
|
||||||
command.type = Command::Type::ForceEdit;
|
|
||||||
i++;
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
|
||||||
skip_space();
|
|
||||||
else
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
command.argument = cmd.substr(i);
|
|
||||||
return command;
|
|
||||||
case 'f':
|
|
||||||
command.type = Command::Type::Filename;
|
|
||||||
i++;
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return command;
|
|
||||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
|
||||||
skip_space();
|
|
||||||
else
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
command.argument = cmd.substr(i);
|
|
||||||
return command;
|
|
||||||
case 'k':
|
|
||||||
command.type = Command::Type::Mark;
|
|
||||||
i++;
|
|
||||||
if (i >= cmd.size())
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
if ('a' <= cmd[i] && cmd[i] <= 'z')
|
|
||||||
command.argument = cmd[i];
|
|
||||||
else
|
|
||||||
throw bed_error("Invalid mark used.");
|
|
||||||
break;
|
|
||||||
case '#':
|
|
||||||
command.type = Command::Type::Dump;
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i < cmd.size()) {
|
|
||||||
if (cmd[i] == 'l' || cmd[i] == 'n' || cmd[i] == 'p')
|
|
||||||
command.suffix = cmd[i++];
|
|
||||||
skip_space();
|
|
||||||
if (i != cmd.size())
|
|
||||||
throw bed_error("Invalid command.");
|
|
||||||
}
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
} // namespace crib::commands::bed
|
|
||||||
@@ -1,36 +1,33 @@
|
|||||||
#include "commands/bed/bed.h"
|
#include "internal/address/address.h"
|
||||||
|
|
||||||
namespace crib::commands::bed {
|
namespace bed::internal::address {
|
||||||
void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr) {
|
Address::Address(std::string &cmd, uint64_t &i) {
|
||||||
|
base = None{};
|
||||||
auto skip_space = [&] {
|
auto skip_space = [&] {
|
||||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
||||||
++i;
|
++i;
|
||||||
};
|
};
|
||||||
skip_space();
|
skip_space();
|
||||||
if (i >= cmd.size()) {
|
if (i >= cmd.size())
|
||||||
addr.type = Command::Address::Type::None;
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
switch (cmd[i]) {
|
switch (cmd[i]) {
|
||||||
case '.':
|
case '.':
|
||||||
addr.type = Command::Address::Type::Current;
|
base = Current();
|
||||||
i++;
|
i++;
|
||||||
break;
|
break;
|
||||||
case '$':
|
case '$':
|
||||||
addr.type = Command::Address::Type::Last;
|
base = Last();
|
||||||
i++;
|
i++;
|
||||||
break;
|
break;
|
||||||
case '\'':
|
case '\'': {
|
||||||
addr.type = Command::Address::Type::Mark;
|
|
||||||
i++;
|
i++;
|
||||||
if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z')
|
if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z')
|
||||||
i++;
|
i++;
|
||||||
else
|
else
|
||||||
throw bed_error("Invalid mark.");
|
throw address_error("Invalid mark.");
|
||||||
addr.mark = cmd[i];
|
base = Mark(cmd[i]);
|
||||||
break;
|
} break;
|
||||||
case '/': {
|
case '/': {
|
||||||
addr.type = Command::Address::Type::SearchForward;
|
|
||||||
i++;
|
i++;
|
||||||
uint64_t start = i;
|
uint64_t start = i;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -49,10 +46,9 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
|
|||||||
else
|
else
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
addr.regex = cmd.substr(start, i - start);
|
base = RegexLine(Direction::Forward, cmd.substr(start, i - start));
|
||||||
} break;
|
} break;
|
||||||
case '?': {
|
case '?': {
|
||||||
addr.type = Command::Address::Type::SearchBackward;
|
|
||||||
i++;
|
i++;
|
||||||
uint64_t start = i;
|
uint64_t start = i;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -71,9 +67,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
|
|||||||
else
|
else
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
addr.regex = cmd.substr(start, i - start);
|
base = RegexLine(Direction::Backward, cmd.substr(start, i - start));
|
||||||
} break;
|
} break;
|
||||||
case '+': {
|
case '+': {
|
||||||
|
base = Current();
|
||||||
i++;
|
i++;
|
||||||
skip_space();
|
skip_space();
|
||||||
uint64_t start = i;
|
uint64_t start = i;
|
||||||
@@ -84,9 +81,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
|
|||||||
}
|
}
|
||||||
if (start == i)
|
if (start == i)
|
||||||
num = 1;
|
num = 1;
|
||||||
addr.offset += num;
|
offset += num;
|
||||||
} break;
|
} break;
|
||||||
case '-': {
|
case '-': {
|
||||||
|
base = Current();
|
||||||
i++;
|
i++;
|
||||||
skip_space();
|
skip_space();
|
||||||
uint64_t start = i;
|
uint64_t start = i;
|
||||||
@@ -97,19 +95,17 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
|
|||||||
}
|
}
|
||||||
if (start == i)
|
if (start == i)
|
||||||
num = 1;
|
num = 1;
|
||||||
addr.offset -= num;
|
offset -= num;
|
||||||
} break;
|
} break;
|
||||||
default: {
|
default: {
|
||||||
if ('0' <= cmd[i] && cmd[i] <= '9') {
|
if ('0' <= cmd[i] && cmd[i] <= '9') {
|
||||||
int64_t num = 0;
|
uint64_t num = 0;
|
||||||
addr.type = Command::Address::Type::Number;
|
|
||||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
||||||
num = num * 10 + (cmd[i] - '0');
|
num = num * 10 + (cmd[i] - '0');
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
addr.number = num;
|
base = Number(num);
|
||||||
} else {
|
} else {
|
||||||
addr.type = Command::Address::Type::None;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -130,49 +126,8 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
|
|||||||
}
|
}
|
||||||
if (start == i)
|
if (start == i)
|
||||||
num = 1;
|
num = 1;
|
||||||
addr.offset += positive ? num : -num;
|
offset += positive ? num : -num;
|
||||||
skip_space();
|
skip_space();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // namespace bed::internal::address
|
||||||
void BEd::resolve_address(Command::Address addr, uint64_t *out_line) {
|
|
||||||
switch (addr.type) {
|
|
||||||
case Command::Address::Type::None:
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::Current:
|
|
||||||
*out_line = line;
|
|
||||||
if (*out_line > vase.lines())
|
|
||||||
*out_line = vase.lines();
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::Number:
|
|
||||||
*out_line = addr.number;
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::Last:
|
|
||||||
*out_line = vase.lines();
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::Mark:
|
|
||||||
*out_line = marks[addr.mark - 'a'];
|
|
||||||
if (!*out_line)
|
|
||||||
throw bed_error("Unset mark used.");
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::SearchForward:
|
|
||||||
if (addr.regex == "")
|
|
||||||
addr.regex = last_regex;
|
|
||||||
last_regex = addr.regex;
|
|
||||||
// TODO: use vase.regex_search(regex, range, options);
|
|
||||||
break;
|
|
||||||
case Command::Address::Type::SearchBackward:
|
|
||||||
if (addr.regex == "")
|
|
||||||
addr.regex = last_regex;
|
|
||||||
last_regex = addr.regex;
|
|
||||||
// TODO
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((int64_t)*out_line + addr.offset < 0)
|
|
||||||
throw bed_error("Line position can't be negative.");
|
|
||||||
else
|
|
||||||
*out_line += addr.offset;
|
|
||||||
if (*out_line > vase.lines())
|
|
||||||
throw bed_error("Line position too high.");
|
|
||||||
}
|
|
||||||
} // namespace crib::commands::bed
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/address/address.h"
|
||||||
|
|
||||||
|
namespace bed::internal::address {
|
||||||
|
Address::Result Address::handle(BEd &ctx, std::string &cmd, uint64_t &i) {
|
||||||
|
bool prev_given = false;
|
||||||
|
Address prev;
|
||||||
|
Address curr;
|
||||||
|
while (i < cmd.size()) {
|
||||||
|
if (cmd[i] == '%') {
|
||||||
|
prev_given = true;
|
||||||
|
prev.base = Number(ctx.active->prev_range.start);
|
||||||
|
prev.offset = 0;
|
||||||
|
curr.base = Number(ctx.active->prev_range.end);
|
||||||
|
curr.offset = 0;
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
curr = Address(cmd, i);
|
||||||
|
}
|
||||||
|
if (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (cmd[i] == ',')
|
||||||
|
curr.base = Number(1);
|
||||||
|
else
|
||||||
|
curr.base = Current();
|
||||||
|
curr.offset = 0;
|
||||||
|
prev_given = false;
|
||||||
|
} else {
|
||||||
|
if (cmd[i] == ';')
|
||||||
|
ctx.active->jump(curr.resolve(ctx));
|
||||||
|
prev_given = true;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
prev = std::move(curr);
|
||||||
|
} else {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (std::holds_alternative<std::monostate>(prev.base))
|
||||||
|
return {{}, 0};
|
||||||
|
if (prev_given) {
|
||||||
|
curr = prev;
|
||||||
|
} else {
|
||||||
|
curr.base = Last();
|
||||||
|
curr.offset = 0;
|
||||||
|
}
|
||||||
|
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
|
||||||
|
}
|
||||||
|
if (prev_given)
|
||||||
|
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
|
||||||
|
return {{curr.resolve(ctx)}, 1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}; // namespace bed::internal::address
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/address/address.h"
|
||||||
|
|
||||||
|
namespace bed::internal::address {
|
||||||
|
uint64_t Address::resolve(BEd &ctx) {
|
||||||
|
uint64_t result = std::visit(
|
||||||
|
[&](auto const &addr) -> uint64_t {
|
||||||
|
uint64_t line = 0;
|
||||||
|
|
||||||
|
using T = std::decay_t<decltype(addr)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, std::monostate>) {
|
||||||
|
throw address_error("empty address");
|
||||||
|
} else if constexpr (std::is_same_v<T, None>) {
|
||||||
|
throw address_error("no address");
|
||||||
|
} else if constexpr (std::is_same_v<T, Current>) {
|
||||||
|
line = ctx.active->line;
|
||||||
|
} else if constexpr (std::is_same_v<T, Last>) {
|
||||||
|
line = ctx.active->vase.lines();
|
||||||
|
} else if constexpr (std::is_same_v<T, Number>) {
|
||||||
|
line = addr.i;
|
||||||
|
} else {
|
||||||
|
throw ed_error("Unhandled address given.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0 && line < (uint64_t)-offset)
|
||||||
|
throw address_error("Can't have negative addresses");
|
||||||
|
line += offset;
|
||||||
|
|
||||||
|
if (line > ctx.active->vase.lines())
|
||||||
|
throw address_error("Line number too high.");
|
||||||
|
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
base
|
||||||
|
);
|
||||||
|
|
||||||
|
return result + offset;
|
||||||
|
}
|
||||||
|
}; // namespace bed::internal::address
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
Buffer::Buffer() : vase("/tmp") {
|
||||||
|
line = vase.lines();
|
||||||
|
modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer(std::string command) : vase(command, "/tmp") {
|
||||||
|
line = vase.lines();
|
||||||
|
if (!line)
|
||||||
|
return;
|
||||||
|
prev_range.start = 1;
|
||||||
|
prev_range.end = line;
|
||||||
|
modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") {
|
||||||
|
line = vase.lines();
|
||||||
|
if (!line)
|
||||||
|
return;
|
||||||
|
prev_range.start = 1;
|
||||||
|
prev_range.end = line;
|
||||||
|
save_path = path;
|
||||||
|
modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::load(std::string command) {
|
||||||
|
vase::Vase new_vase = vase::Vase(command, "/tmp");
|
||||||
|
vase = std::move(new_vase);
|
||||||
|
line = vase.lines();
|
||||||
|
if (!line) {
|
||||||
|
prev_range.start = 0;
|
||||||
|
prev_range.end = 0;
|
||||||
|
} else {
|
||||||
|
prev_range.start = 1;
|
||||||
|
prev_range.end = line;
|
||||||
|
}
|
||||||
|
modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::load(std::filesystem::path path) {
|
||||||
|
vase::Vase new_vase = vase::Vase(path, "/tmp");
|
||||||
|
vase = std::move(new_vase);
|
||||||
|
line = vase.lines();
|
||||||
|
if (!line) {
|
||||||
|
prev_range.start = 0;
|
||||||
|
prev_range.end = 0;
|
||||||
|
} else {
|
||||||
|
prev_range.start = 1;
|
||||||
|
prev_range.end = line;
|
||||||
|
}
|
||||||
|
save_path = path;
|
||||||
|
modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::jump(uint64_t n_line) {
|
||||||
|
if (n_line > vase.lines())
|
||||||
|
throw ed_error("Line number too high.");
|
||||||
|
line = n_line;
|
||||||
|
prev_range.start = line;
|
||||||
|
prev_range.end = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::append(std::string text, uint64_t line) {
|
||||||
|
using namespace bed::internal::vase;
|
||||||
|
Point p = {line, 0};
|
||||||
|
if (!vase.lines()) {
|
||||||
|
text.pop_back();
|
||||||
|
} else if (line == vase.lines()) {
|
||||||
|
p.row--;
|
||||||
|
p.col = UINT64_MAX;
|
||||||
|
text = "\n" + text;
|
||||||
|
text.pop_back();
|
||||||
|
}
|
||||||
|
prev_range.start = p.row + 1;
|
||||||
|
vase.insert(&p, text);
|
||||||
|
prev_range.end = p.row + 1;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::remove(uint64_t start_line, uint64_t end_line) {
|
||||||
|
vase.erase({{start_line - 1, 0}, {end_line, 0}});
|
||||||
|
prev_range.start = start_line;
|
||||||
|
prev_range.end = start_line;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::join(uint64_t start_line, uint64_t end_line) {
|
||||||
|
vase.regex_search_replace(R"(\n)", {{start_line - 1, 0}, {end_line, 0}}, "", "g");
|
||||||
|
prev_range.start = start_line;
|
||||||
|
prev_range.end = start_line;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::print(uint64_t start_line, uint64_t end_line) {
|
||||||
|
vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
std::cout << it.line << std::endl;
|
||||||
|
prev_range.start = start_line;
|
||||||
|
prev_range.end = end_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Buffer::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,135 @@
|
|||||||
|
#include "internal/commands/commands.h"
|
||||||
|
#include "bed.h"
|
||||||
|
#include "internal/commands/suffixes.h"
|
||||||
|
|
||||||
|
namespace bed::internal::commands {
|
||||||
|
void Suffix::register_suffixes(BEd &ctx) {
|
||||||
|
ctx.suffixes['p' - 'a'] = Suffix{
|
||||||
|
.desc = "Prints current line.",
|
||||||
|
.handle = [](BEd &ctx) {
|
||||||
|
auto line = ctx.active->line;
|
||||||
|
ctx.active->print(line, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Command::register_posix(BEd &ctx) {
|
||||||
|
ctx.no_op = Command{
|
||||||
|
.address_mode = Command::AddressMode::Single,
|
||||||
|
.suffix = Command::SuffixKind::None,
|
||||||
|
.desc = "Prints a line and jumps to it (default: .+1)",
|
||||||
|
.accept_zero = false,
|
||||||
|
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
|
||||||
|
uint64_t line;
|
||||||
|
if (addresses.size())
|
||||||
|
line = addresses[0];
|
||||||
|
else
|
||||||
|
line = ctx.active->line + 1;
|
||||||
|
if (line == 0)
|
||||||
|
throw ed_error("Line 0 is invalid.");
|
||||||
|
ctx.active->jump(line);
|
||||||
|
ctx.active->print(line, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.eof_op = Command{
|
||||||
|
.address_mode = Command::AddressMode::None,
|
||||||
|
.suffix = Command::SuffixKind::None,
|
||||||
|
.desc = "Try quitting.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
|
||||||
|
for (auto &[name, buffer] : ctx.buffers)
|
||||||
|
if (buffer->modified)
|
||||||
|
throw ed_error("Buffer " + name + " modified.");
|
||||||
|
throw fatal_error("Quitting", 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.commands.insert(
|
||||||
|
"a",
|
||||||
|
Command{
|
||||||
|
.address_mode = Command::AddressMode::Single,
|
||||||
|
.suffix = Command::SuffixKind::Suffix,
|
||||||
|
.desc = "Append lines at address (default: .)",
|
||||||
|
.accept_zero = true,
|
||||||
|
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
|
||||||
|
// TODO: start a text editing session, then append its stuff.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.commands.insert(
|
||||||
|
"q",
|
||||||
|
Command{
|
||||||
|
.address_mode = Command::AddressMode::None,
|
||||||
|
.suffix = Command::SuffixKind::None,
|
||||||
|
.desc = "Try quitting.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
|
||||||
|
for (auto &[name, buffer] : ctx.buffers)
|
||||||
|
if (buffer->modified)
|
||||||
|
throw ed_error("Buffer " + name + " modified.");
|
||||||
|
throw fatal_error("Quitting", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.commands.insert(
|
||||||
|
"Q",
|
||||||
|
Command{
|
||||||
|
.address_mode = Command::AddressMode::None,
|
||||||
|
.suffix = Command::SuffixKind::None,
|
||||||
|
.desc = "Force quitting.",
|
||||||
|
.accept_zero = false,
|
||||||
|
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
|
||||||
|
throw fatal_error("Force Quitting", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.commands.insert(
|
||||||
|
"p",
|
||||||
|
Command{
|
||||||
|
.address_mode = Command::AddressMode::Range,
|
||||||
|
.suffix = Command::SuffixKind::Suffix,
|
||||||
|
.desc = "Print range (default .,.)",
|
||||||
|
.accept_zero = false,
|
||||||
|
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
|
||||||
|
uint64_t start_line = ctx.active->line;
|
||||||
|
uint64_t end_line = ctx.active->line;
|
||||||
|
if (!addresses.size())
|
||||||
|
ctx.active->print(start_line, end_line);
|
||||||
|
else if (addresses.size() == 1)
|
||||||
|
ctx.active->print(addresses[0], addresses[0]);
|
||||||
|
else
|
||||||
|
ctx.active->print(addresses[0], addresses[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.commands.insert(
|
||||||
|
"E",
|
||||||
|
Command{
|
||||||
|
.address_mode = Command::AddressMode::None,
|
||||||
|
.suffix = Command::SuffixKind::Argument,
|
||||||
|
.desc = "Load a file into the current buffer.",
|
||||||
|
.accept_zero = true,
|
||||||
|
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view file) {
|
||||||
|
bool empty = false;
|
||||||
|
if (file.empty())
|
||||||
|
empty = true;
|
||||||
|
uint64_t i = 0;
|
||||||
|
while (i < file.length() && (file[i] == ' ' || file[i] == '\t'))
|
||||||
|
i++;
|
||||||
|
if (i >= file.size())
|
||||||
|
empty = true;
|
||||||
|
if (empty) {
|
||||||
|
if (ctx.active->save_path == "")
|
||||||
|
throw ed_error("Need filename!");
|
||||||
|
ctx.active->load(ctx.active->save_path);
|
||||||
|
} else if (file[i] == '!') {
|
||||||
|
file = file.substr(1);
|
||||||
|
ctx.active->load(file);
|
||||||
|
} else {
|
||||||
|
ctx.active->load(std::filesystem::path(file));
|
||||||
|
}
|
||||||
|
std::cout << ctx.active->vase.length() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::commands
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
|
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
|
||||||
base_dir /= "tapp.XXXXXX";
|
base_dir /= "tapp.XXXXXX";
|
||||||
char *s = strdup(base_dir.c_str());
|
char *s = strdup(base_dir.c_str());
|
||||||
@@ -69,4 +69,4 @@ const char *AppendBuffer::read(uint64_t pos) {
|
|||||||
uint64_t AppendBuffer::length() {
|
uint64_t AppendBuffer::length() {
|
||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
|
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
|
||||||
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(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.");
|
throw std::runtime_error("Swap directory does not exist or is not a directory.");
|
||||||
@@ -44,4 +44,4 @@ const char *OriginalBuffer::read(uint64_t pos) {
|
|||||||
uint64_t OriginalBuffer::length() {
|
uint64_t OriginalBuffer::length() {
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir)
|
LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir)
|
||||||
: it(root, dir), dir(dir) {
|
: it(root, dir), dir(dir) {
|
||||||
it.seek_line(line_num);
|
it.seek_line(line_num);
|
||||||
@@ -72,4 +72,4 @@ bool LineIterator::next(std::string *line) {
|
|||||||
uint64_t LineIterator::byte_offset() {
|
uint64_t LineIterator::byte_offset() {
|
||||||
return last_line_offset;
|
return last_line_offset;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
PetalIterator::PetalIterator(Shard *r, Direction dir)
|
PetalIterator::PetalIterator(Shard *r, Direction dir)
|
||||||
: dir(dir), root(r) {
|
: dir(dir), root(r) {
|
||||||
if (!root)
|
if (!root)
|
||||||
@@ -168,4 +168,4 @@ bool PetalIterator::next(const char **data, uint64_t *out_len) {
|
|||||||
uint64_t PetalIterator::byte_offset() {
|
uint64_t PetalIterator::byte_offset() {
|
||||||
return last_offset;
|
return last_offset;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
||||||
std::vector<ReplacePart> parts;
|
std::vector<ReplacePart> parts;
|
||||||
std::string constant;
|
std::string constant;
|
||||||
@@ -156,4 +156,4 @@ std::vector<Range> Vase::regex_search(
|
|||||||
result.push_back({point_of(match.start), point_of(match.end)});
|
result.push_back({point_of(match.start), point_of(match.end)});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
std::vector<Vase::RegexMatch> Vase::_regex_search(
|
std::vector<Vase::RegexMatch> Vase::_regex_search(
|
||||||
std::string_view pattern, Range range, std::string_view options
|
std::string_view pattern, Range range, std::string_view options
|
||||||
) {
|
) {
|
||||||
@@ -244,4 +244,4 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
|||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
void Shard::retain(Shard *n) {
|
void Shard::retain(Shard *n) {
|
||||||
if (n)
|
if (n)
|
||||||
n->refs++;
|
n->refs++;
|
||||||
@@ -424,4 +424,4 @@ void Shard::dump(Shard *node, int depth) {
|
|||||||
<< "\"\n";
|
<< "\"\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace crib::internal::vase {
|
namespace bed::internal::vase {
|
||||||
Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
|
Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
|
||||||
: path(path), swapdir(swapdir) {
|
: path(path), swapdir(swapdir) {
|
||||||
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
|
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
|
||||||
@@ -507,4 +507,4 @@ void Vase::move_lines(Point *point, uint64_t amount, Direction dir) {
|
|||||||
}
|
}
|
||||||
clamp(point);
|
clamp(point);
|
||||||
}
|
}
|
||||||
} // namespace crib::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
+14
-2
@@ -1,6 +1,18 @@
|
|||||||
#include "cli.h"
|
#include "bed.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
return crib::cli::run(argc, argv);
|
std::vector<std::string> args(argv, argv + argc);
|
||||||
|
try {
|
||||||
|
bed::BEd ed(args);
|
||||||
|
ed.run();
|
||||||
|
} catch (bed::fatal_error &e) {
|
||||||
|
if (e.code)
|
||||||
|
std::cout << "Fatal error: " << e.what() << std::endl;
|
||||||
|
return e.code;
|
||||||
|
} catch (...) {
|
||||||
|
std::cout << "Unexpected error." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user