Compare commits
39 Commits
cpp-rewrit
...
v1.5.5
| Author | SHA1 | Date | |
|---|---|---|---|
| e079726ced | |||
| 3c79e368fa | |||
| bd68b809cd | |||
| f200a7bfcd | |||
| 58daeac6ad | |||
| 3ef6bab265 | |||
| 3800751bcf | |||
| 87272a669b | |||
| b77748f7c0 | |||
| c70b502006 | |||
| e14d620887 | |||
| db38266849 | |||
| 6d1b7f8e56 | |||
| 64647f77b0 | |||
| 2c3b2ae0f0 | |||
| a605e47458 | |||
| 40ee1e8d7b | |||
| 4464159301 | |||
| dc9fb58a41 | |||
| bbd682cec7 | |||
| ace25c5c65 | |||
| 09881706f2 | |||
| 2579bb01c8 | |||
| a528b3fc3e | |||
| faa70af9b7 | |||
| e1abaae6aa | |||
| 329b92f382 | |||
| 12553f301b | |||
| 75b19042b9 | |||
| 1a184b1a08 | |||
| acde895fb7 | |||
| 78d522ba98 | |||
| feb6667a24 | |||
|
|
66c79e0762 | ||
| a51b98c31f | |||
| 707362574c | |||
| 56db8bd8d2 | |||
| a32ef2ff53 | |||
| 0c0c3d9ce5 |
@@ -1,74 +1,30 @@
|
|||||||
# CMake configuration for Kyle's Editor (ke)
|
|
||||||
cmake_minimum_required(VERSION 3.15)
|
cmake_minimum_required(VERSION 3.15)
|
||||||
project(ke VERSION 1.3.3 LANGUAGES CXX)
|
project(ke C) # Specify C language explicitly
|
||||||
|
|
||||||
# Project metadata
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(KE_VERSION "1.3.3")
|
set(KE_VERSION "1.5.5")
|
||||||
|
|
||||||
# C++17 standard requirement
|
set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g")
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_DEFAULT_SOURCE -D_XOPEN_SOURCE")
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# Source files
|
# Optionally enable AddressSanitizer (ASan)
|
||||||
set(KE_SOURCES
|
|
||||||
main.cc
|
|
||||||
abuf.cc
|
|
||||||
erow.cc
|
|
||||||
terminal.cc
|
|
||||||
input_handler.cc
|
|
||||||
display.cc
|
|
||||||
file_io.cc
|
|
||||||
killring.cc
|
|
||||||
)
|
|
||||||
|
|
||||||
# Header files
|
|
||||||
set(KE_HEADERS
|
|
||||||
ke_constants.h
|
|
||||||
abuf.h
|
|
||||||
erow.h
|
|
||||||
terminal.h
|
|
||||||
input_handler.h
|
|
||||||
display.h
|
|
||||||
file_io.h
|
|
||||||
killring.h
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build options
|
|
||||||
option(ENABLE_ASAN "Enable AddressSanitizer for builds" OFF)
|
option(ENABLE_ASAN "Enable AddressSanitizer for builds" OFF)
|
||||||
|
|
||||||
# Create executable target
|
if (ENABLE_ASAN)
|
||||||
add_executable(ke ${KE_SOURCES})
|
|
||||||
|
|
||||||
# Target compile features
|
|
||||||
target_compile_features(ke PRIVATE cxx_std_17)
|
|
||||||
|
|
||||||
# Target compile options
|
|
||||||
target_compile_options(ke PRIVATE
|
|
||||||
-Wall
|
|
||||||
-Wextra
|
|
||||||
-pedantic
|
|
||||||
-Wshadow
|
|
||||||
-Werror
|
|
||||||
-g
|
|
||||||
)
|
|
||||||
|
|
||||||
# Target compile definitions
|
|
||||||
target_compile_definitions(ke PRIVATE
|
|
||||||
KE_VERSION="ke version ${KE_VERSION}"
|
|
||||||
_DEFAULT_SOURCE
|
|
||||||
_XOPEN_SOURCE
|
|
||||||
)
|
|
||||||
|
|
||||||
# AddressSanitizer support
|
|
||||||
if(ENABLE_ASAN)
|
|
||||||
message(STATUS "ASan enabled")
|
message(STATUS "ASan enabled")
|
||||||
target_compile_options(ke PRIVATE -fsanitize=address -fno-omit-frame-pointer)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||||
target_link_options(ke PRIVATE -fsanitize=address)
|
# Ensure the sanitizer is linked too (especially important on some platforms)
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Installation rules
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# Add executable
|
||||||
|
add_executable(ke main.c)
|
||||||
|
target_compile_definitions(ke PRIVATE KE_VERSION="ke version ${KE_VERSION}")
|
||||||
|
install(TARGETS ke RUNTIME DESTINATION bin)
|
||||||
|
install(FILES ke.1 TYPE MAN)
|
||||||
|
|
||||||
install(TARGETS ke RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS ke RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install(FILES ke.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
install(FILES ke.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||||
|
|
||||||
|
|||||||
46
Makefile
46
Makefile
@@ -2,53 +2,17 @@ TARGET := ke
|
|||||||
KE_VERSION := devel
|
KE_VERSION := devel
|
||||||
DEST := $(HOME)/.local/bin/$(TARGET)
|
DEST := $(HOME)/.local/bin/$(TARGET)
|
||||||
|
|
||||||
CC := gcc
|
|
||||||
CXX := g++
|
|
||||||
|
|
||||||
CFLAGS := -Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g
|
CFLAGS := -Wall -Wextra -pedantic -Wshadow -Werror -std=c99 -g
|
||||||
CFLAGS += -Wno-unused-result
|
CFLAGS += -Wno-unused-result
|
||||||
CFLAGS += -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
|
CFLAGS += -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
|
||||||
CFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
CFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||||
|
|
||||||
CXXFLAGS := -Wall -Wextra -pedantic -Wshadow -Werror -std=c++17 -g
|
|
||||||
CXXFLAGS += -Wno-unused-result
|
|
||||||
CXXFLAGS += -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
|
|
||||||
CXXFLAGS += -DKE_VERSION='"ke $(KE_VERSION)"'
|
|
||||||
CXXFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
|
||||||
|
|
||||||
LDFLAGS := -fsanitize=address
|
LDFLAGS := -fsanitize=address
|
||||||
|
|
||||||
SOURCES := main.cc abuf.cc erow.cc terminal.cc input_handler.cc display.cc file_io.cc killring.cc
|
|
||||||
OBJECTS := main.o abuf.o erow.o terminal.o input_handler.o display.o file_io.o killring.o
|
|
||||||
|
|
||||||
all: $(TARGET) test.txt
|
all: $(TARGET) test.txt
|
||||||
|
|
||||||
$(TARGET): $(OBJECTS)
|
$(TARGET): main.c
|
||||||
$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJECTS) $(LDFLAGS)
|
$(CC) $(CFLAGS) -o $(TARGET) $(LDFLAGS) main.c
|
||||||
|
|
||||||
main.o: main.cc ke_constants.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c main.cc
|
|
||||||
|
|
||||||
abuf.o: abuf.cc abuf.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c abuf.cc
|
|
||||||
|
|
||||||
erow.o: erow.cc erow.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c erow.cc
|
|
||||||
|
|
||||||
terminal.o: terminal.cc terminal.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c terminal.cc
|
|
||||||
|
|
||||||
input_handler.o: input_handler.cc input_handler.h ke_constants.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c input_handler.cc
|
|
||||||
|
|
||||||
display.o: display.cc display.h ke_constants.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c display.cc
|
|
||||||
|
|
||||||
file_io.o: file_io.cc file_io.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c file_io.cc
|
|
||||||
|
|
||||||
killring.o: killring.cc killring.h
|
|
||||||
$(CXX) $(CXXFLAGS) -c killring.cc
|
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
#install: $(TARGET)
|
#install: $(TARGET)
|
||||||
@@ -57,9 +21,13 @@ install:
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGET)
|
rm -f $(TARGET)
|
||||||
rm -f $(OBJECTS)
|
|
||||||
rm -f asan.log*
|
rm -f asan.log*
|
||||||
|
|
||||||
.PHONY: test.txt
|
.PHONY: test.txt
|
||||||
test.txt:
|
test.txt:
|
||||||
cp test.txt.bak $@
|
cp test.txt.bak $@
|
||||||
|
|
||||||
|
.PHONY: gdb
|
||||||
|
gdb:
|
||||||
|
@test -f $(TARGET).pid || (echo "error: $(TARGET).pid not found" >&2; exit 1)
|
||||||
|
@gdb -p $$(cat $(TARGET).pid | tr -d ' \t\n\r') ./$(TARGET)
|
||||||
|
|||||||
@@ -16,3 +16,9 @@ To get verbose ASAN messages:
|
|||||||
export LSAN_OPTIONS=verbosity=1:log_threads=1
|
export LSAN_OPTIONS=verbosity=1:log_threads=1
|
||||||
|
|
||||||
Released under an ISC license.
|
Released under an ISC license.
|
||||||
|
|
||||||
|
Started by following along with kilo:
|
||||||
|
https://viewsourcecode.org/snaptoken/kilo/
|
||||||
|
|
||||||
|
E.g., in the devlogs
|
||||||
|
https://log.wntrmute.dev/2020/02/20200207
|
||||||
|
|||||||
17
TODO
17
TODO
@@ -1,15 +1,18 @@
|
|||||||
[X] goto-line
|
[X] goto-line
|
||||||
[X] text-corruption bug
|
[X] text-corruption bug
|
||||||
[ ] alt-modifiers
|
[ ] alt-modifiers
|
||||||
[ ] refresh-screen
|
[x] refresh-screen
|
||||||
[ ] functions -> keymapping?
|
[ ] functions -> keymapping? (what did this even mean)
|
||||||
[X] rendering: need to skip over control characters like we do with tabs
|
[X] rendering: need to skip over control characters like we do with tabs
|
||||||
[X] control-d -> delete
|
[X] control-d -> delete
|
||||||
[ ] control-g -> exit buf prompt
|
[x] control-g -> exit buf prompt
|
||||||
[ ] load-file prompt on dirty buffer
|
[x] load-file prompt on dirty buffer
|
||||||
[ ] multiple files
|
[ ] multiple files
|
||||||
|
[x] utf-8
|
||||||
|
[x] kill ring
|
||||||
|
[x] mark/region
|
||||||
|
|
||||||
What would it take for ke to be your daily drives?
|
What would it take for ke to be your daily drives?
|
||||||
+ C-u
|
[x] C-u (repeat actions)
|
||||||
+ Alt nav (backspace, delete, f, b, etc)
|
[x] Alt nav (backspace, delete, f, b, etc)
|
||||||
|
[-] undo tree (C-k u/U)
|
||||||
|
|||||||
17
abuf.cc
17
abuf.cc
@@ -1,17 +0,0 @@
|
|||||||
#include "abuf.h"
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
void abuf::append(std::string_view s) {
|
|
||||||
buffer_.append(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void abuf::append(const char* s, std::size_t len) {
|
|
||||||
buffer_.append(s, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void abuf::append(char c) {
|
|
||||||
buffer_.push_back(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
57
abuf.h
57
abuf.h
@@ -1,57 +0,0 @@
|
|||||||
#ifndef ABUF_HPP
|
|
||||||
#define ABUF_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append buffer class for efficient string building.
|
|
||||||
* C++17 implementation with RAII memory management.
|
|
||||||
*/
|
|
||||||
class abuf {
|
|
||||||
public:
|
|
||||||
// Constructors
|
|
||||||
abuf() noexcept = default;
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment (use move semantics)
|
|
||||||
abuf(const abuf&) = delete;
|
|
||||||
abuf& operator=(const abuf&) = delete;
|
|
||||||
|
|
||||||
// Move constructor and assignment
|
|
||||||
abuf(abuf&&) noexcept = default;
|
|
||||||
abuf& operator=(abuf&&) noexcept = default;
|
|
||||||
|
|
||||||
// Destructor (automatic cleanup via std::string)
|
|
||||||
~abuf() = default;
|
|
||||||
|
|
||||||
// Append methods
|
|
||||||
void append(std::string_view s);
|
|
||||||
void append(const char* s, std::size_t len);
|
|
||||||
void append(char c);
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
[[nodiscard]] const char* data() const noexcept { return buffer_.data(); }
|
|
||||||
[[nodiscard]] std::size_t size() const noexcept { return buffer_.size(); }
|
|
||||||
[[nodiscard]] std::size_t length() const noexcept { return buffer_.length(); }
|
|
||||||
[[nodiscard]] bool empty() const noexcept { return buffer_.empty(); }
|
|
||||||
[[nodiscard]] std::size_t capacity() const noexcept { return buffer_.capacity(); }
|
|
||||||
|
|
||||||
// Get the underlying string
|
|
||||||
[[nodiscard]] const std::string& str() const noexcept { return buffer_; }
|
|
||||||
|
|
||||||
// Clear the buffer
|
|
||||||
void clear() noexcept { buffer_.clear(); }
|
|
||||||
|
|
||||||
// Reserve capacity
|
|
||||||
void reserve(std::size_t new_cap) { buffer_.reserve(new_cap); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string buffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // ABUF_HPP
|
|
||||||
27
default.nix
27
default.nix
@@ -1,16 +1,31 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
installShellFiles,
|
|
||||||
stdenv,
|
stdenv,
|
||||||
|
cmake,
|
||||||
|
installShellFiles,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
cmakeContent = builtins.readFile ./CMakeLists.txt;
|
||||||
|
cmakeLines = lib.splitString "\n" cmakeContent;
|
||||||
|
versionLine = lib.findFirst (l: builtins.match ".*set\\(KE_VERSION \".+\"\\).*" l != null) (throw "KE_VERSION not found in CMakeLists.txt") cmakeLines;
|
||||||
|
version = builtins.head (builtins.match ".*set\\(KE_VERSION \"(.+)\"\\).*" versionLine);
|
||||||
|
in
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
pname = "ke";
|
pname = "ke";
|
||||||
version = "1.3.3";
|
inherit version;
|
||||||
|
|
||||||
src = lib.cleanSource ./.;
|
src = lib.cleanSource ./.;
|
||||||
|
|
||||||
nativeBuildInputs = [ installShellFiles ];
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
"-DENABLE_ASAN=on"
|
||||||
|
"-DCMAKE_BUILD_TYPE=Debug"
|
||||||
|
];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
@@ -18,10 +33,8 @@ stdenv.mkDerivation {
|
|||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp ke $out/bin/
|
cp ke $out/bin/
|
||||||
|
|
||||||
|
installManPage ../ke.1
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postInstall = ''
|
|
||||||
installManPage ke.1
|
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
|
|||||||
237
display.cc
237
display.cc
@@ -1,237 +0,0 @@
|
|||||||
#include "display.h"
|
|
||||||
#include "ke_constants.h"
|
|
||||||
#include "abuf.h"
|
|
||||||
#include "erow.h"
|
|
||||||
#include <termios.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <ctime>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// Need access to main.cc structures and functions
|
|
||||||
extern void erow_update(struct erow *row);
|
|
||||||
extern int erow_render_to_cursor(struct erow *row, int cx);
|
|
||||||
|
|
||||||
// erow definition (needed for accessing row fields)
|
|
||||||
struct erow {
|
|
||||||
char *line;
|
|
||||||
char *render;
|
|
||||||
int size;
|
|
||||||
int rsize;
|
|
||||||
int cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
// editor_t definition (needed for display operations)
|
|
||||||
struct editor_t {
|
|
||||||
struct termios entry_term;
|
|
||||||
int rows, cols;
|
|
||||||
int curx, cury;
|
|
||||||
int rx;
|
|
||||||
int mode;
|
|
||||||
int nrows;
|
|
||||||
int rowoffs, coloffs;
|
|
||||||
struct erow *row;
|
|
||||||
struct erow *killring;
|
|
||||||
int kill;
|
|
||||||
int no_kill;
|
|
||||||
char *filename;
|
|
||||||
int dirty;
|
|
||||||
int dirtyex;
|
|
||||||
char msg[80];
|
|
||||||
int mark_set;
|
|
||||||
int mark_curx, mark_cury;
|
|
||||||
time_t msgtm;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
void Display::clear(ke::abuf* ab) {
|
|
||||||
if (ab == nullptr) {
|
|
||||||
(void)write(STDOUT_FILENO, ESCSEQ "2J", 4);
|
|
||||||
(void)write(STDOUT_FILENO, ESCSEQ "H", 3);
|
|
||||||
} else {
|
|
||||||
ab->append(ESCSEQ "2J", 4);
|
|
||||||
ab->append(ESCSEQ "H", 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::draw_rows(editor_t* editor, ke::abuf* ab) {
|
|
||||||
assert(editor->cols >= 0);
|
|
||||||
|
|
||||||
char* buf = new char[editor->cols];
|
|
||||||
int buflen, filerow, padding;
|
|
||||||
int y;
|
|
||||||
|
|
||||||
for (y = 0; y < editor->rows; y++) {
|
|
||||||
filerow = y + editor->rowoffs;
|
|
||||||
if (filerow >= editor->nrows) {
|
|
||||||
if ((editor->nrows == 0) && (y == editor->rows / 3)) {
|
|
||||||
buflen = snprintf(buf,
|
|
||||||
editor->cols,
|
|
||||||
"%s",
|
|
||||||
KE_VERSION);
|
|
||||||
padding = (editor->rows - buflen) / 2;
|
|
||||||
|
|
||||||
if (padding) {
|
|
||||||
ab->append("|", 1);
|
|
||||||
padding--;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (padding--)
|
|
||||||
ab->append(" ", 1);
|
|
||||||
ab->append(buf, buflen);
|
|
||||||
} else {
|
|
||||||
ab->append("|", 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
erow_update(&editor->row[filerow]);
|
|
||||||
buflen = editor->row[filerow].rsize - editor->coloffs;
|
|
||||||
if (buflen < 0) {
|
|
||||||
buflen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buflen > editor->cols) {
|
|
||||||
buflen = editor->cols;
|
|
||||||
}
|
|
||||||
ab->append(editor->row[filerow].render + editor->coloffs,
|
|
||||||
buflen);
|
|
||||||
}
|
|
||||||
ab->append(ESCSEQ "K", 3);
|
|
||||||
ab->append("\r\n", 2);
|
|
||||||
}
|
|
||||||
delete[] buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
char Display::status_mode_char(int mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_NORMAL:
|
|
||||||
return 'N';
|
|
||||||
case MODE_KCOMMAND:
|
|
||||||
return 'K';
|
|
||||||
case MODE_ESCAPE:
|
|
||||||
return 'E';
|
|
||||||
default:
|
|
||||||
return '?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::draw_status_bar(editor_t* editor, ke::abuf* ab) {
|
|
||||||
char* status = new char[editor->cols];
|
|
||||||
char* rstatus = new char[editor->cols];
|
|
||||||
char* mstatus = new char[editor->cols];
|
|
||||||
|
|
||||||
int len, rlen;
|
|
||||||
|
|
||||||
len = snprintf(status,
|
|
||||||
editor->cols,
|
|
||||||
"%c%cke: %.20s - %d lines",
|
|
||||||
status_mode_char(editor->mode),
|
|
||||||
editor->dirty ? '!' : '-',
|
|
||||||
editor->filename ? editor->filename : "[no file]",
|
|
||||||
editor->nrows);
|
|
||||||
|
|
||||||
if (editor->mark_set) {
|
|
||||||
snprintf(mstatus,
|
|
||||||
editor->cols,
|
|
||||||
" | M: %d, %d ",
|
|
||||||
editor->mark_curx + 1,
|
|
||||||
editor->mark_cury + 1);
|
|
||||||
} else {
|
|
||||||
snprintf(mstatus, editor->cols, " | M:clear ");
|
|
||||||
}
|
|
||||||
|
|
||||||
rlen = snprintf(rstatus,
|
|
||||||
editor->cols,
|
|
||||||
"L%d/%d C%d %s",
|
|
||||||
editor->cury + 1,
|
|
||||||
editor->nrows,
|
|
||||||
editor->curx + 1,
|
|
||||||
mstatus);
|
|
||||||
|
|
||||||
if (len > editor->cols) {
|
|
||||||
len = editor->cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
ab->append(ESCSEQ "7m", 4);
|
|
||||||
ab->append(status, len);
|
|
||||||
while (len < editor->cols) {
|
|
||||||
if (editor->cols - len == rlen) {
|
|
||||||
ab->append(rstatus, rlen);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
ab->append(" ", 1);
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
ab->append(ESCSEQ "m", 3);
|
|
||||||
ab->append("\r\n", 2);
|
|
||||||
delete[] status;
|
|
||||||
delete[] rstatus;
|
|
||||||
delete[] mstatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::draw_message_line(editor_t* editor, ke::abuf* ab) {
|
|
||||||
int len = strlen(editor->msg);
|
|
||||||
|
|
||||||
ab->append(ESCSEQ "K", 3);
|
|
||||||
if (len > editor->cols) {
|
|
||||||
len = editor->cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len && ((time(nullptr) - editor->msgtm) < MSG_TIMEO)) {
|
|
||||||
ab->append(editor->msg, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::scroll(editor_t* editor) {
|
|
||||||
editor->rx = 0;
|
|
||||||
if (editor->cury < editor->nrows) {
|
|
||||||
editor->rx = erow_render_to_cursor(
|
|
||||||
&editor->row[editor->cury],
|
|
||||||
editor->curx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->cury < editor->rowoffs) {
|
|
||||||
editor->rowoffs = editor->cury;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->cury >= editor->rowoffs + editor->rows) {
|
|
||||||
editor->rowoffs = editor->cury - editor->rows + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->rx < editor->coloffs) {
|
|
||||||
editor->coloffs = editor->rx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->rx >= editor->coloffs + editor->cols) {
|
|
||||||
editor->coloffs = editor->rx - editor->cols + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::refresh(editor_t* editor) {
|
|
||||||
char buf[32];
|
|
||||||
ke::abuf ab;
|
|
||||||
|
|
||||||
scroll(editor);
|
|
||||||
|
|
||||||
ab.append(ESCSEQ "?25l", 6);
|
|
||||||
clear(&ab);
|
|
||||||
|
|
||||||
draw_rows(editor, &ab);
|
|
||||||
draw_status_bar(editor, &ab);
|
|
||||||
draw_message_line(editor, &ab);
|
|
||||||
|
|
||||||
snprintf(buf,
|
|
||||||
sizeof(buf),
|
|
||||||
ESCSEQ "%d;%dH",
|
|
||||||
(editor->cury - editor->rowoffs) + 1,
|
|
||||||
(editor->rx - editor->coloffs) + 1);
|
|
||||||
ab.append(buf, strnlen(buf, 32));
|
|
||||||
ab.append(ESCSEQ "?25h", 6);
|
|
||||||
|
|
||||||
(void)write(STDOUT_FILENO, ab.data(), ab.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
42
display.h
42
display.h
@@ -1,42 +0,0 @@
|
|||||||
#ifndef DISPLAY_HPP
|
|
||||||
#define DISPLAY_HPP
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
struct editor_t;
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
class abuf; // Forward declaration of ke::abuf
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display class for screen rendering and refresh operations.
|
|
||||||
*/
|
|
||||||
class Display {
|
|
||||||
public:
|
|
||||||
Display() = default;
|
|
||||||
~Display() = default;
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment
|
|
||||||
Display(const Display&) = delete;
|
|
||||||
Display& operator=(const Display&) = delete;
|
|
||||||
|
|
||||||
// Main display operations
|
|
||||||
static void refresh(editor_t* editor);
|
|
||||||
static void clear(ke::abuf* ab);
|
|
||||||
|
|
||||||
// Drawing operations
|
|
||||||
static void draw_rows(editor_t* editor, ke::abuf* ab);
|
|
||||||
static void draw_status_bar(editor_t* editor, ke::abuf* ab);
|
|
||||||
static void draw_message_line(editor_t* editor, ke::abuf* ab);
|
|
||||||
|
|
||||||
// Scrolling
|
|
||||||
static void scroll(editor_t* editor);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static char status_mode_char(int mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // DISPLAY_HPP
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# Constants Refactoring Documentation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This document describes the refactoring of #defines and common constants into a single centralized header file for Kyle's Editor (ke).
|
|
||||||
|
|
||||||
## Changes Made
|
|
||||||
|
|
||||||
### 1. Created `ke_constants.h`
|
|
||||||
A new header file was created to centralize all common constants and preprocessor definitions used throughout the project.
|
|
||||||
|
|
||||||
**Location**: `/Users/kyle/src/ke2/ke_constants.h`
|
|
||||||
|
|
||||||
**Contents**:
|
|
||||||
- **Version Information**: `KE_VERSION` - Editor version string
|
|
||||||
- **Terminal Sequences**: `ESCSEQ` - ANSI escape sequence prefix
|
|
||||||
- **Keyboard Macros**: `CTRL_KEY(key)` - Control key combination macro
|
|
||||||
- **Display Constants**:
|
|
||||||
- `TAB_STOP` (8) - Tab stop width
|
|
||||||
- `MSG_TIMEO` (3) - Message timeout in seconds
|
|
||||||
- **Memory Management**: `INITIAL_CAPACITY` (64) - Initial buffer capacity
|
|
||||||
- **Keyboard Modes**:
|
|
||||||
- `MODE_NORMAL` (0) - Normal editing mode
|
|
||||||
- `MODE_KCOMMAND` (1) - Command mode (^k commands)
|
|
||||||
- `MODE_ESCAPE` (2) - Escape key handling mode
|
|
||||||
- **Kill Ring Operations**:
|
|
||||||
- `KILLRING_NO_OP` (0) - No operation
|
|
||||||
- `KILLRING_APPEND` (1) - Append deleted characters
|
|
||||||
- `KILLRING_PREPEND` (2) - Prepend deleted characters
|
|
||||||
- `KILLING_SET` (3) - Set killring to deleted character
|
|
||||||
- `KILLRING_FLUSH` (4) - Clear the killring
|
|
||||||
- **Legacy Initializers**: `ABUF_INIT` - C struct initializer for append buffer
|
|
||||||
|
|
||||||
### 2. Updated `main.c`
|
|
||||||
- Added `#include "ke_constants.h"` at the top with other includes
|
|
||||||
- Removed all duplicate #define statements that were moved to the constants header
|
|
||||||
- Eliminated the duplicate `TAB_STOP` definition (was defined twice on lines 36 and 50)
|
|
||||||
- Cleaned up approximately 30+ lines of redundant definitions
|
|
||||||
|
|
||||||
### 3. Updated `erow.cpp`
|
|
||||||
- Added `#include "ke_constants.h"` with extern "C" linkage for C++ compatibility
|
|
||||||
- Removed local `constexpr int TAB_STOP = 8` definition
|
|
||||||
- Now uses the centralized `TAB_STOP` constant from `ke_constants.h`
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
1. **Single Source of Truth**: All constants are now defined in one location, making them easier to maintain and modify
|
|
||||||
2. **Eliminated Duplication**: Removed duplicate definitions (e.g., TAB_STOP was defined twice in main.c)
|
|
||||||
3. **Better Organization**: Constants are grouped logically by category with clear comments
|
|
||||||
4. **Cross-Language Compatibility**: Header works with both C (main.c) and C++ (erow.cpp) code
|
|
||||||
5. **Easier Maintenance**: Future changes to constants only need to be made in one place
|
|
||||||
6. **Improved Readability**: Cleaner source files with less clutter from #define statements
|
|
||||||
|
|
||||||
## Build System Compatibility
|
|
||||||
|
|
||||||
Both build systems have been tested and verified to work correctly:
|
|
||||||
|
|
||||||
### Makefile Build
|
|
||||||
```bash
|
|
||||||
make clean
|
|
||||||
make
|
|
||||||
```
|
|
||||||
Successfully compiles all files (main.c, abuf.cpp, erow.cpp) and links the executable.
|
|
||||||
|
|
||||||
### CMake Build
|
|
||||||
```bash
|
|
||||||
cd cmake-build-debug
|
|
||||||
cmake --build . --clean-first
|
|
||||||
```
|
|
||||||
Successfully builds the project with all components.
|
|
||||||
|
|
||||||
## Header Guard
|
|
||||||
The header uses standard include guards:
|
|
||||||
```c
|
|
||||||
#ifndef KE_CONSTANTS_H
|
|
||||||
#define KE_CONSTANTS_H
|
|
||||||
...
|
|
||||||
#endif /* KE_CONSTANTS_H */
|
|
||||||
```
|
|
||||||
|
|
||||||
## C/C++ Interoperability
|
|
||||||
When including in C++ files, use extern "C" linkage:
|
|
||||||
```cpp
|
|
||||||
extern "C" {
|
|
||||||
#include "ke_constants.h"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This ensures proper linkage and prevents name mangling issues.
|
|
||||||
|
|
||||||
## Future Considerations
|
|
||||||
|
|
||||||
1. **Additional Constants**: As the project grows, any new constants should be added to `ke_constants.h` rather than being scattered across source files
|
|
||||||
2. **Namespacing**: For C++ constants, consider creating a dedicated C++ header with namespaced constants
|
|
||||||
3. **Type Safety**: For C++ code, consider using `constexpr` or `inline constexpr` variables in a C++ header for better type safety
|
|
||||||
4. **Documentation**: Keep this document updated as new constants are added or modified
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
All compilation tests passed:
|
|
||||||
- ✅ Makefile build: Clean compilation with no errors or warnings
|
|
||||||
- ✅ CMake build: Clean compilation with no errors or warnings
|
|
||||||
- ✅ All source files (C and C++) successfully include and use the constants
|
|
||||||
- ✅ No duplicate definition errors
|
|
||||||
- ✅ Proper linkage between C and C++ components
|
|
||||||
|
|
||||||
## Related Files
|
|
||||||
|
|
||||||
- `ke_constants.h` - The new constants header (created)
|
|
||||||
- `main.c` - Updated to use new header (modified)
|
|
||||||
- `erow.cpp` - Updated to use new header (modified)
|
|
||||||
- `Makefile` - No changes required (working)
|
|
||||||
- `CMakeLists.txt` - No changes required (working)
|
|
||||||
|
|
||||||
## Date
|
|
||||||
November 24, 2025
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
# C++17 Conversion Documentation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This document describes the initial conversion of the ke2 project from C to C++17 standards, starting with the `abuf` and `erow` structures as requested.
|
|
||||||
|
|
||||||
## Converted Components
|
|
||||||
|
|
||||||
### 1. abuf (Append Buffer)
|
|
||||||
**Original C Structure (main.c:69-73):**
|
|
||||||
```c
|
|
||||||
struct abuf {
|
|
||||||
char *b;
|
|
||||||
int len;
|
|
||||||
int cap;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**C++17 Implementation (abuf.hpp, abuf.cpp):**
|
|
||||||
- Converted to a modern C++ class using RAII principles
|
|
||||||
- Uses `std::string` as the internal buffer for automatic memory management
|
|
||||||
- Implements move semantics (deleted copy operations)
|
|
||||||
- Uses C++17 features:
|
|
||||||
- `std::string_view` for efficient string parameters
|
|
||||||
- `[[nodiscard]]` attributes for better safety
|
|
||||||
- `noexcept` specifications where appropriate
|
|
||||||
- No manual memory management required - destructor automatically handled by std::string
|
|
||||||
|
|
||||||
**Key Improvements:**
|
|
||||||
- Automatic memory cleanup (RAII)
|
|
||||||
- Exception-safe
|
|
||||||
- No memory leaks possible
|
|
||||||
- More efficient with move semantics
|
|
||||||
- Type-safe with modern C++ idioms
|
|
||||||
|
|
||||||
### 2. erow (Editor Row)
|
|
||||||
**Original C Structure (main.c:79-87):**
|
|
||||||
```c
|
|
||||||
struct erow {
|
|
||||||
char *line;
|
|
||||||
char *render;
|
|
||||||
int size;
|
|
||||||
int rsize;
|
|
||||||
int cap;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**C++17 Implementation (erow.hpp, erow.cpp):**
|
|
||||||
- Converted to a C++ class with proper encapsulation
|
|
||||||
- Uses `std::string` for both `line` and `render` buffers
|
|
||||||
- Implements move semantics (deleted copy operations)
|
|
||||||
- All manual memory management replaced with RAII
|
|
||||||
- Retains all original functionality:
|
|
||||||
- UTF-8 character handling with `mbrtowc` and `wcwidth`
|
|
||||||
- Tab expansion (TAB_STOP = 8)
|
|
||||||
- Control character rendering as `\xx`
|
|
||||||
- Cursor-to-render and render-to-cursor position conversions
|
|
||||||
|
|
||||||
**Key Improvements:**
|
|
||||||
- Automatic memory cleanup for both line and render buffers
|
|
||||||
- No malloc/free/realloc needed
|
|
||||||
- Exception-safe operations
|
|
||||||
- Better encapsulation with private members
|
|
||||||
- Modern C++ method naming conventions
|
|
||||||
|
|
||||||
## Build System Updates
|
|
||||||
|
|
||||||
### Makefile
|
|
||||||
- Added C++ compiler support (g++)
|
|
||||||
- Added CXXFLAGS with `-std=c++17`
|
|
||||||
- Modified build rules to compile C and C++ separately
|
|
||||||
- Updated to link with C++ compiler
|
|
||||||
- Enhanced clean target to remove object files
|
|
||||||
|
|
||||||
### CMakeLists.txt
|
|
||||||
- Changed project language from `C` to `C CXX`
|
|
||||||
- Added `CMAKE_CXX_STANDARD 17` and `CMAKE_CXX_STANDARD_REQUIRED ON`
|
|
||||||
- Added C++ compiler flags matching C flags
|
|
||||||
- Updated executable target to include `abuf.cpp` and `erow.cpp`
|
|
||||||
- Maintained backward compatibility with existing C code (main.c)
|
|
||||||
|
|
||||||
## Compilation Testing
|
|
||||||
Both build systems have been tested and verified:
|
|
||||||
- ✅ Makefile: Successfully compiles with `make`
|
|
||||||
- ✅ CMake: Successfully configures and builds
|
|
||||||
- ✅ Object files generated correctly for all C++ sources
|
|
||||||
- ✅ No compilation warnings or errors
|
|
||||||
|
|
||||||
## C++17 Features Used
|
|
||||||
1. **std::string** - Modern string class with automatic memory management
|
|
||||||
2. **std::string_view** - Efficient non-owning string references (C++17)
|
|
||||||
3. **[[nodiscard]]** - Compiler warnings for ignored return values (C++17)
|
|
||||||
4. **noexcept** - Exception specifications for optimization
|
|
||||||
5. **default/delete** - Explicit control over special member functions
|
|
||||||
6. **Move semantics** - Efficient resource transfer
|
|
||||||
7. **constexpr** - Compile-time constants (TAB_STOP)
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
The project now has a foundation for further C++17 conversion:
|
|
||||||
- Additional structures can be converted following the same patterns
|
|
||||||
- Consider converting more C code to C++ classes
|
|
||||||
- Potential for using more STL containers (std::vector for dynamic arrays)
|
|
||||||
- Opportunity to use smart pointers for other memory management
|
|
||||||
- Can leverage more C++17 features (std::optional, structured bindings, etc.)
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
- The new C++ code coexists with existing C code (main.c)
|
|
||||||
- Both can be compiled together and linked successfully
|
|
||||||
- The conversion maintains the original functionality
|
|
||||||
- No changes required to existing C code at this stage
|
|
||||||
214
erow.cc
214
erow.cc
@@ -1,214 +0,0 @@
|
|||||||
#include "erow.h"
|
|
||||||
#include <cstring>
|
|
||||||
#include <cwchar>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include "ke_constants.h"
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
erow::erow(std::size_t initial_capacity) {
|
|
||||||
line_.reserve(initial_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
erow::erow(const char* text, std::size_t len) {
|
|
||||||
line_.assign(text, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set line content
|
|
||||||
void erow::set_line(const char* data, std::size_t len) {
|
|
||||||
line_.assign(data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append string to the line
|
|
||||||
void erow::append_string(const char* s, std::size_t len) {
|
|
||||||
line_.append(s, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert character at position
|
|
||||||
void erow::insert_char(std::size_t at, char c) {
|
|
||||||
if (at > line_.size()) {
|
|
||||||
at = line_.size();
|
|
||||||
}
|
|
||||||
line_.insert(at, 1, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete character at position
|
|
||||||
void erow::delete_char(std::size_t at) {
|
|
||||||
if (at < line_.size()) {
|
|
||||||
line_.erase(at, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for nibble to hex conversion
|
|
||||||
char erow::nibble_to_hex(char c) {
|
|
||||||
c &= 0xf;
|
|
||||||
if (c < 10) {
|
|
||||||
return static_cast<char>('0' + c);
|
|
||||||
}
|
|
||||||
return static_cast<char>('A' + (c - 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the render string based on the line content
|
|
||||||
void erow::update() {
|
|
||||||
int tabs = 0;
|
|
||||||
int ctrl = 0;
|
|
||||||
|
|
||||||
// Count tabs and control characters
|
|
||||||
for (std::size_t j = 0; j < line_.size(); ++j) {
|
|
||||||
if (line_[j] == '\t') {
|
|
||||||
++tabs;
|
|
||||||
} else if (static_cast<unsigned char>(line_[j]) < 0x20) {
|
|
||||||
++ctrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate render buffer
|
|
||||||
render_.clear();
|
|
||||||
render_.reserve(line_.size() + (tabs * (TAB_STOP - 1)) + (ctrl * 3) + 1);
|
|
||||||
|
|
||||||
// Build the render string
|
|
||||||
for (std::size_t j = 0; j < line_.size(); ++j) {
|
|
||||||
if (line_[j] == '\t') {
|
|
||||||
// Expand tabs to spaces
|
|
||||||
do {
|
|
||||||
render_.push_back(' ');
|
|
||||||
} while ((render_.size() % TAB_STOP) != 0);
|
|
||||||
} else if (static_cast<unsigned char>(line_[j]) < 0x20) {
|
|
||||||
// Render control characters as \xx
|
|
||||||
render_.push_back('\\');
|
|
||||||
render_.push_back(nibble_to_hex(line_[j] >> 4));
|
|
||||||
render_.push_back(nibble_to_hex(line_[j] & 0x0f));
|
|
||||||
} else {
|
|
||||||
// Leave UTF-8 multibyte bytes untouched
|
|
||||||
render_.push_back(line_[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert cursor position to render position
|
|
||||||
int erow::render_to_cursor(int cx) const {
|
|
||||||
int rx = 0;
|
|
||||||
std::size_t j = 0;
|
|
||||||
|
|
||||||
wchar_t wc;
|
|
||||||
std::mbstate_t st{};
|
|
||||||
|
|
||||||
while (j < static_cast<std::size_t>(cx) && j < line_.size()) {
|
|
||||||
unsigned char b = static_cast<unsigned char>(line_[j]);
|
|
||||||
|
|
||||||
if (b == '\t') {
|
|
||||||
rx += (TAB_STOP - 1) - (rx % TAB_STOP);
|
|
||||||
++rx;
|
|
||||||
++j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b < 0x20) {
|
|
||||||
// Render as \xx -> width 3
|
|
||||||
rx += 3;
|
|
||||||
++j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t rem = line_.size() - j;
|
|
||||||
std::size_t n = std::mbrtowc(&wc, &line_[j], rem, &st);
|
|
||||||
|
|
||||||
if (n == static_cast<std::size_t>(-2)) {
|
|
||||||
// Incomplete sequence at end; treat one byte
|
|
||||||
rx += 1;
|
|
||||||
j += 1;
|
|
||||||
std::memset(&st, 0, sizeof(st));
|
|
||||||
} else if (n == static_cast<std::size_t>(-1)) {
|
|
||||||
// Invalid byte; consume one and reset state
|
|
||||||
rx += 1;
|
|
||||||
j += 1;
|
|
||||||
std::memset(&st, 0, sizeof(st));
|
|
||||||
} else if (n == 0) {
|
|
||||||
// Null character
|
|
||||||
rx += 0;
|
|
||||||
j += 1;
|
|
||||||
} else {
|
|
||||||
int w = wcwidth(wc);
|
|
||||||
if (w < 0) {
|
|
||||||
w = 1; // Non-printable -> treat as width 1
|
|
||||||
}
|
|
||||||
rx += w;
|
|
||||||
j += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert render position to cursor position
|
|
||||||
int erow::cursor_to_render(int rx) const {
|
|
||||||
int cur_rx = 0;
|
|
||||||
std::size_t j = 0;
|
|
||||||
|
|
||||||
wchar_t wc;
|
|
||||||
std::mbstate_t st{};
|
|
||||||
|
|
||||||
while (j < line_.size()) {
|
|
||||||
unsigned char b = static_cast<unsigned char>(line_[j]);
|
|
||||||
|
|
||||||
if (b == '\t') {
|
|
||||||
int tab_width = (TAB_STOP - 1) - (cur_rx % TAB_STOP) + 1;
|
|
||||||
if (cur_rx + tab_width > rx) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cur_rx += tab_width;
|
|
||||||
++j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b < 0x20) {
|
|
||||||
// Control char width is 3
|
|
||||||
if (cur_rx + 3 > rx) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cur_rx += 3;
|
|
||||||
++j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t rem = line_.size() - j;
|
|
||||||
std::size_t n = std::mbrtowc(&wc, &line_[j], rem, &st);
|
|
||||||
std::size_t adv = 1;
|
|
||||||
int w = 1;
|
|
||||||
|
|
||||||
if (n == static_cast<std::size_t>(-2)) {
|
|
||||||
// Incomplete sequence
|
|
||||||
adv = 1;
|
|
||||||
w = 1;
|
|
||||||
std::memset(&st, 0, sizeof(st));
|
|
||||||
} else if (n == static_cast<std::size_t>(-1)) {
|
|
||||||
// Invalid byte
|
|
||||||
adv = 1;
|
|
||||||
w = 1;
|
|
||||||
std::memset(&st, 0, sizeof(st));
|
|
||||||
} else if (n == 0) {
|
|
||||||
// Null character
|
|
||||||
adv = 1;
|
|
||||||
w = 0;
|
|
||||||
} else {
|
|
||||||
adv = n;
|
|
||||||
w = wcwidth(wc);
|
|
||||||
if (w < 0) {
|
|
||||||
w = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cur_rx + w > rx) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_rx += w;
|
|
||||||
j += adv;
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<int>(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
76
erow.h
76
erow.h
@@ -1,76 +0,0 @@
|
|||||||
#ifndef EROW_HPP
|
|
||||||
#define EROW_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Editor row class representing a line of text with rendering information.
|
|
||||||
* C++17 implementation with RAII memory management.
|
|
||||||
*/
|
|
||||||
class erow {
|
|
||||||
public:
|
|
||||||
// Constructors
|
|
||||||
erow() noexcept = default;
|
|
||||||
explicit erow(std::size_t initial_capacity);
|
|
||||||
erow(const char* text, std::size_t len);
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment (use move semantics)
|
|
||||||
erow(const erow&) = delete;
|
|
||||||
erow& operator=(const erow&) = delete;
|
|
||||||
|
|
||||||
// Move constructor and assignment
|
|
||||||
erow(erow&&) noexcept = default;
|
|
||||||
erow& operator=(erow&&) noexcept = default;
|
|
||||||
|
|
||||||
// Destructor (automatic cleanup via std::string)
|
|
||||||
~erow() = default;
|
|
||||||
|
|
||||||
// Core operations
|
|
||||||
void update();
|
|
||||||
void insert_char(std::size_t at, char c);
|
|
||||||
void delete_char(std::size_t at);
|
|
||||||
void append_string(const char* s, std::size_t len);
|
|
||||||
|
|
||||||
// Cursor/render position conversions
|
|
||||||
[[nodiscard]] int render_to_cursor(int cx) const;
|
|
||||||
[[nodiscard]] int cursor_to_render(int rx) const;
|
|
||||||
|
|
||||||
// Accessors for line data
|
|
||||||
[[nodiscard]] const char* line_data() const noexcept { return line_.data(); }
|
|
||||||
[[nodiscard]] char* line_data() noexcept { return line_.data(); }
|
|
||||||
[[nodiscard]] std::size_t line_size() const noexcept { return line_.size(); }
|
|
||||||
[[nodiscard]] std::size_t line_capacity() const noexcept { return line_.capacity(); }
|
|
||||||
|
|
||||||
// Accessors for render data
|
|
||||||
[[nodiscard]] const char* render_data() const noexcept { return render_.data(); }
|
|
||||||
[[nodiscard]] std::size_t render_size() const noexcept { return render_.size(); }
|
|
||||||
|
|
||||||
// Direct access to strings (for compatibility)
|
|
||||||
[[nodiscard]] std::string& line() noexcept { return line_; }
|
|
||||||
[[nodiscard]] const std::string& line() const noexcept { return line_; }
|
|
||||||
[[nodiscard]] const std::string& render() const noexcept { return render_; }
|
|
||||||
|
|
||||||
// Resize operations
|
|
||||||
void resize_line(std::size_t new_size) { line_.resize(new_size); }
|
|
||||||
void reserve_line(std::size_t new_cap) { line_.reserve(new_cap); }
|
|
||||||
|
|
||||||
// Set line content
|
|
||||||
void set_line(const char* data, std::size_t len);
|
|
||||||
void set_line(std::string&& data) noexcept { line_ = std::move(data); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string line_; // The actual line content
|
|
||||||
std::string render_; // The rendered version (with tabs expanded, etc.)
|
|
||||||
|
|
||||||
// Helper for nibble to hex conversion
|
|
||||||
static char nibble_to_hex(char c);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // EROW_HPP
|
|
||||||
180
file_io.cc
180
file_io.cc
@@ -1,180 +0,0 @@
|
|||||||
#include "file_io.h"
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cassert>
|
|
||||||
#include <ctime>
|
|
||||||
|
|
||||||
// Need access to main.cc structures and functions
|
|
||||||
extern void erow_insert(int at, const char *s, int len);
|
|
||||||
extern void reset_editor();
|
|
||||||
extern void editor_set_status(const char *fmt, ...);
|
|
||||||
extern void die(const char *s);
|
|
||||||
extern char *editor_prompt(const char *, void (*cb)(char *, int16_t));
|
|
||||||
|
|
||||||
// erow definition
|
|
||||||
struct erow {
|
|
||||||
char *line;
|
|
||||||
char *render;
|
|
||||||
int size;
|
|
||||||
int rsize;
|
|
||||||
int cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
// editor_t definition
|
|
||||||
struct editor_t {
|
|
||||||
struct termios entry_term;
|
|
||||||
int rows, cols;
|
|
||||||
int curx, cury;
|
|
||||||
int rx;
|
|
||||||
int mode;
|
|
||||||
int nrows;
|
|
||||||
int rowoffs, coloffs;
|
|
||||||
struct erow *row;
|
|
||||||
struct erow *killring;
|
|
||||||
int kill;
|
|
||||||
int no_kill;
|
|
||||||
char *filename;
|
|
||||||
int dirty;
|
|
||||||
int dirtyex;
|
|
||||||
char msg[80];
|
|
||||||
int mark_set;
|
|
||||||
int mark_curx, mark_cury;
|
|
||||||
time_t msgtm;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
void FileIO::open_file(editor_t* editor, const char* filename) {
|
|
||||||
char* line = nullptr;
|
|
||||||
size_t linecap = 0;
|
|
||||||
ssize_t linelen;
|
|
||||||
FILE* fp = nullptr;
|
|
||||||
|
|
||||||
reset_editor();
|
|
||||||
|
|
||||||
editor->filename = strdup(filename);
|
|
||||||
assert(editor->filename != nullptr);
|
|
||||||
|
|
||||||
editor->dirty = 0;
|
|
||||||
if ((fp = fopen(filename, "r")) == nullptr) {
|
|
||||||
if (errno == ENOENT) {
|
|
||||||
editor_set_status("[new file]");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
die("fopen");
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((linelen = getline(&line, &linecap, fp)) != -1) {
|
|
||||||
if (linelen != -1) {
|
|
||||||
while ((linelen > 0) && ((line[linelen - 1] == '\r') ||
|
|
||||||
(line[linelen - 1] == '\n'))) {
|
|
||||||
linelen--;
|
|
||||||
}
|
|
||||||
|
|
||||||
erow_insert(editor->nrows, line, linelen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(line);
|
|
||||||
line = nullptr;
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* FileIO::rows_to_buffer(editor_t* editor, int* buflen) {
|
|
||||||
int len = 0;
|
|
||||||
int j;
|
|
||||||
char* buf = nullptr;
|
|
||||||
char* p = nullptr;
|
|
||||||
|
|
||||||
for (j = 0; j < editor->nrows; j++) {
|
|
||||||
/* extra byte for newline */
|
|
||||||
len += editor->row[j].size + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
*buflen = len;
|
|
||||||
buf = static_cast<char*>(malloc(len));
|
|
||||||
assert(buf != nullptr);
|
|
||||||
p = buf;
|
|
||||||
|
|
||||||
for (j = 0; j < editor->nrows; j++) {
|
|
||||||
memcpy(p, editor->row[j].line, editor->row[j].size);
|
|
||||||
p += editor->row[j].size;
|
|
||||||
*p++ = '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
int FileIO::save_file(editor_t* editor) {
|
|
||||||
int fd = -1;
|
|
||||||
int len;
|
|
||||||
int status = 1; /* will be used as exit code */
|
|
||||||
char* buf;
|
|
||||||
|
|
||||||
if (!editor->dirty) {
|
|
||||||
editor_set_status("No changes to save.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->filename == nullptr) {
|
|
||||||
editor->filename = editor_prompt("Filename: %s", nullptr);
|
|
||||||
if (editor->filename == nullptr) {
|
|
||||||
editor_set_status("Save aborted.");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = rows_to_buffer(editor, &len);
|
|
||||||
if ((fd = open(editor->filename, O_RDWR | O_CREAT, 0644)) == -1) {
|
|
||||||
goto save_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 == ftruncate(fd, len)) {
|
|
||||||
goto save_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
status = 0;
|
|
||||||
goto save_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ssize_t) len != write(fd, buf, len)) {
|
|
||||||
goto save_exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = 0;
|
|
||||||
|
|
||||||
save_exit:
|
|
||||||
if (fd)
|
|
||||||
close(fd);
|
|
||||||
if (buf) {
|
|
||||||
free(buf);
|
|
||||||
buf = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != 0) {
|
|
||||||
buf = strerror(errno);
|
|
||||||
editor_set_status("Error writing %s: %s",
|
|
||||||
editor->filename,
|
|
||||||
buf);
|
|
||||||
} else {
|
|
||||||
editor_set_status("Wrote %d bytes to %s.",
|
|
||||||
len,
|
|
||||||
editor->filename);
|
|
||||||
editor->dirty = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
29
file_io.h
29
file_io.h
@@ -1,29 +0,0 @@
|
|||||||
#ifndef FILE_IO_HPP
|
|
||||||
#define FILE_IO_HPP
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
struct editor_t;
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File I/O class for reading and writing files.
|
|
||||||
*/
|
|
||||||
class FileIO {
|
|
||||||
public:
|
|
||||||
FileIO() = default;
|
|
||||||
~FileIO() = default;
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment
|
|
||||||
FileIO(const FileIO&) = delete;
|
|
||||||
FileIO& operator=(const FileIO&) = delete;
|
|
||||||
|
|
||||||
// File operations
|
|
||||||
static void open_file(editor_t* editor, const char* filename);
|
|
||||||
static int save_file(editor_t* editor);
|
|
||||||
static char* rows_to_buffer(editor_t* editor, int* buflen);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // FILE_IO_HPP
|
|
||||||
122
input_handler.cc
122
input_handler.cc
@@ -1,122 +0,0 @@
|
|||||||
#include "input_handler.h"
|
|
||||||
#include "ke_constants.h"
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
// Key codes for special keys
|
|
||||||
enum KeyPress {
|
|
||||||
TAB_KEY = 9,
|
|
||||||
ESC_KEY = 27,
|
|
||||||
BACKSPACE = 127,
|
|
||||||
ARROW_LEFT = 1000,
|
|
||||||
ARROW_RIGHT,
|
|
||||||
ARROW_UP,
|
|
||||||
ARROW_DOWN,
|
|
||||||
DEL_KEY,
|
|
||||||
HOME_KEY,
|
|
||||||
END_KEY,
|
|
||||||
PG_UP,
|
|
||||||
PG_DN,
|
|
||||||
};
|
|
||||||
|
|
||||||
int16_t InputHandler::get_keypress() {
|
|
||||||
char seq[3];
|
|
||||||
/* read raw byte so UTF-8 bytes (>=0x80) are not sign-extended */
|
|
||||||
unsigned char uc = 0;
|
|
||||||
int16_t c;
|
|
||||||
|
|
||||||
if (read(STDIN_FILENO, &uc, 1) == -1) {
|
|
||||||
perror("get_keypress:read");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
c = (int16_t) uc;
|
|
||||||
|
|
||||||
if (c == 0x1b) {
|
|
||||||
if (read(STDIN_FILENO, &seq[0], 1) != 1)
|
|
||||||
return c;
|
|
||||||
if (read(STDIN_FILENO, &seq[1], 1) != 1)
|
|
||||||
return c;
|
|
||||||
|
|
||||||
if (seq[0] == '[') {
|
|
||||||
if (seq[1] < 'A') {
|
|
||||||
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
|
||||||
return c;
|
|
||||||
if (seq[2] == '~') {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case '1':
|
|
||||||
return HOME_KEY;
|
|
||||||
case '3':
|
|
||||||
return DEL_KEY;
|
|
||||||
case '4':
|
|
||||||
return END_KEY;
|
|
||||||
case '5':
|
|
||||||
return PG_UP;
|
|
||||||
case '6':
|
|
||||||
return PG_DN;
|
|
||||||
case '7':
|
|
||||||
return HOME_KEY;
|
|
||||||
case '8':
|
|
||||||
return END_KEY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case 'A':
|
|
||||||
return ARROW_UP;
|
|
||||||
case 'B':
|
|
||||||
return ARROW_DOWN;
|
|
||||||
case 'C':
|
|
||||||
return ARROW_RIGHT;
|
|
||||||
case 'D':
|
|
||||||
return ARROW_LEFT;
|
|
||||||
case 'F':
|
|
||||||
return END_KEY;
|
|
||||||
case 'H':
|
|
||||||
return HOME_KEY;
|
|
||||||
|
|
||||||
default:
|
|
||||||
/* nada */ ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (seq[0] == 'O') {
|
|
||||||
switch (seq[1]) {
|
|
||||||
case 'F':
|
|
||||||
return END_KEY;
|
|
||||||
case 'H':
|
|
||||||
return HOME_KEY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0x1b;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InputHandler::is_arrow_key(int16_t c) {
|
|
||||||
switch (c) {
|
|
||||||
case ARROW_DOWN:
|
|
||||||
case ARROW_LEFT:
|
|
||||||
case ARROW_RIGHT:
|
|
||||||
case ARROW_UP:
|
|
||||||
case CTRL_KEY('p'):
|
|
||||||
case CTRL_KEY('n'):
|
|
||||||
case CTRL_KEY('f'):
|
|
||||||
case CTRL_KEY('b'):
|
|
||||||
case CTRL_KEY('a'):
|
|
||||||
case CTRL_KEY('e'):
|
|
||||||
case END_KEY:
|
|
||||||
case HOME_KEY:
|
|
||||||
case PG_DN:
|
|
||||||
case PG_UP:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#ifndef INPUT_HANDLER_HPP
|
|
||||||
#define INPUT_HANDLER_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input handler class for reading and processing keyboard input.
|
|
||||||
*/
|
|
||||||
class InputHandler {
|
|
||||||
public:
|
|
||||||
InputHandler() = default;
|
|
||||||
~InputHandler() = default;
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment
|
|
||||||
InputHandler(const InputHandler&) = delete;
|
|
||||||
InputHandler& operator=(const InputHandler&) = delete;
|
|
||||||
|
|
||||||
// Read a keypress from stdin
|
|
||||||
static int16_t get_keypress();
|
|
||||||
|
|
||||||
// Check if a key code is an arrow/navigation key
|
|
||||||
static bool is_arrow_key(int16_t c);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // INPUT_HANDLER_HPP
|
|
||||||
35
ke.1
35
ke.1
@@ -22,11 +22,18 @@ grandeur. Many commands work with and without control; for example,
|
|||||||
saving a file can be done with either C-k s or C-k C-s. Other commands work
|
saving a file can be done with either C-k s or C-k C-s. Other commands work
|
||||||
with ESC or CTRL.
|
with ESC or CTRL.
|
||||||
.Sh K-COMMANDS
|
.Sh K-COMMANDS
|
||||||
|
k-command mode can be exited with ESC or C-g.
|
||||||
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
||||||
.It C-k BACKSPACE
|
.It C-k BACKSPACE
|
||||||
Delete from the cursor to the beginning of the line.
|
Delete from the cursor to the beginning of the line.
|
||||||
.It C-k SPACE
|
.It C-k SPACE
|
||||||
Toggle the mark.
|
Toggle the mark.
|
||||||
|
.It C-k -
|
||||||
|
If the mark is set, unindent the region.
|
||||||
|
.It C-k =
|
||||||
|
If the mark is set, indent the region.
|
||||||
|
.It C-k c
|
||||||
|
Clear (flush) the kill ring.
|
||||||
.It C-k d
|
.It C-k d
|
||||||
Delete from the cursor to the end of the line.
|
Delete from the cursor to the end of the line.
|
||||||
.It C-k C-d
|
.It C-k C-d
|
||||||
@@ -36,32 +43,48 @@ Edit a new file. Also C-k C-e.
|
|||||||
.It C-k f
|
.It C-k f
|
||||||
Incremental find.
|
Incremental find.
|
||||||
.It C-k g
|
.It C-k g
|
||||||
Go to a specific line. Also C-k C-g.
|
Go to a specific line.
|
||||||
|
.It C-k j
|
||||||
|
Jump to the mark.
|
||||||
.It C-k l
|
.It C-k l
|
||||||
List the number of lines of code in a saved file.
|
List the number of lines of code in a saved file.
|
||||||
.It C-k m
|
.It C-k m
|
||||||
Run make(1).
|
Run make(1).
|
||||||
.It C-k q
|
.It C-k q
|
||||||
exit the editor. Also C-k C-q.
|
Exit the editor. If the file has unsaved changes,
|
||||||
|
a warning will be printed; a second C-k q will exit.
|
||||||
|
.It C-k C-q
|
||||||
|
Immediately exit the editor.
|
||||||
|
.It C-k C-r
|
||||||
|
Reload the current buffer from disk.
|
||||||
.It C-k s
|
.It C-k s
|
||||||
save the file, prompting for a filename if needed. Also C-k C-s.
|
Save the file, prompting for a filename if needed. Also C-k C-s.
|
||||||
|
.It C-k u
|
||||||
|
Undo changes.
|
||||||
|
.It C-k U
|
||||||
|
Redo changes.
|
||||||
.It C-k x
|
.It C-k x
|
||||||
save the file and exit. Also C-k C-x.
|
save the file and exit. Also C-k C-x.
|
||||||
.It C-k y
|
.It C-k y
|
||||||
Yank the killring.
|
Yank the kill ring.
|
||||||
.It C-k \[char92]
|
.It C-k \[char92]
|
||||||
Dump core.
|
Dump core.
|
||||||
.El
|
.El
|
||||||
.Sh OTHER KEYBINDINGS
|
.Sh OTHER KEYBINDINGS
|
||||||
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
.Bl -tag -width xxxxxxxxxxxx -offset indent
|
||||||
|
.It C-g
|
||||||
|
In general, C-g cancels an operation.
|
||||||
.It C-l
|
.It C-l
|
||||||
Refresh the display.
|
Refresh the display.
|
||||||
.It C-s
|
.It C-s
|
||||||
Incremental find.
|
Incremental find.
|
||||||
|
.It C-u
|
||||||
|
Universal argument. C-u followed by numbers will repeat an
|
||||||
|
operation n times.
|
||||||
.It C-w
|
.It C-w
|
||||||
Kill the region if the mark is set.
|
Kill the region if the mark is set.
|
||||||
.It C-y
|
.It C-y
|
||||||
Yank the killring.
|
Yank the kill ring.
|
||||||
.It ESC BACKSPACE
|
.It ESC BACKSPACE
|
||||||
Delete the previous word.
|
Delete the previous word.
|
||||||
.It ESC b
|
.It ESC b
|
||||||
@@ -71,7 +94,7 @@ Delete the next word.
|
|||||||
.It ESC f
|
.It ESC f
|
||||||
Move to the next word.
|
Move to the next word.
|
||||||
.It ESC w
|
.It ESC w
|
||||||
Save the region (if the mark is set) to the killring.
|
Save the region (if the mark is set) to the kill ring.
|
||||||
.It
|
.It
|
||||||
.El
|
.El
|
||||||
.Sh FIND
|
.Sh FIND
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* ke_constants.h
|
|
||||||
*
|
|
||||||
* Common constants and defines for Kyle's Editor (ke)
|
|
||||||
* Refactored from main.c and other source files for centralized maintenance.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KE_CONSTANTS_H
|
|
||||||
#define KE_CONSTANTS_H
|
|
||||||
|
|
||||||
/* Version information */
|
|
||||||
#ifndef KE_VERSION
|
|
||||||
#define KE_VERSION "ke dev build"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Terminal escape sequences */
|
|
||||||
#define ESCSEQ "\x1b["
|
|
||||||
|
|
||||||
/* Keyboard and control key macros */
|
|
||||||
#define CTRL_KEY(key) ((key)&0x1f)
|
|
||||||
|
|
||||||
/* Display and rendering constants */
|
|
||||||
#define TAB_STOP 8
|
|
||||||
#define MSG_TIMEO 3
|
|
||||||
|
|
||||||
/* Memory management constants */
|
|
||||||
#define INITIAL_CAPACITY 64
|
|
||||||
|
|
||||||
/* Keyboard input modes */
|
|
||||||
#define MODE_NORMAL 0
|
|
||||||
#define MODE_KCOMMAND 1
|
|
||||||
#define MODE_ESCAPE 2
|
|
||||||
|
|
||||||
/* Kill ring operations */
|
|
||||||
#define KILLRING_NO_OP 0 /* don't touch the killring */
|
|
||||||
#define KILLRING_APPEND 1 /* append deleted chars */
|
|
||||||
#define KILLRING_PREPEND 2 /* prepend deleted chars */
|
|
||||||
#define KILLING_SET 3 /* set killring to deleted char */
|
|
||||||
#define KILLRING_FLUSH 4 /* clear the killring */
|
|
||||||
|
|
||||||
/* Legacy C struct initializers (for compatibility with main.c) */
|
|
||||||
#define ABUF_INIT {NULL, 0, 0}
|
|
||||||
|
|
||||||
#endif /* KE_CONSTANTS_H */
|
|
||||||
262
killring.cc
262
killring.cc
@@ -1,262 +0,0 @@
|
|||||||
#include "killring.h"
|
|
||||||
#include <termios.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cassert>
|
|
||||||
#include <ctime>
|
|
||||||
|
|
||||||
// Need access to main.cc structures and functions
|
|
||||||
extern int erow_init(struct erow *row, int len);
|
|
||||||
extern void erow_update(struct erow *row);
|
|
||||||
extern void erow_free(struct erow *row);
|
|
||||||
extern void editor_set_status(const char *fmt, ...);
|
|
||||||
extern void insertch(int16_t c);
|
|
||||||
extern void newline();
|
|
||||||
extern void move_cursor(int16_t c);
|
|
||||||
extern int cursor_at_eol();
|
|
||||||
extern void swap_int(int *a, int *b);
|
|
||||||
extern void deletech(uint8_t op);
|
|
||||||
|
|
||||||
// erow definition
|
|
||||||
struct erow {
|
|
||||||
char *line;
|
|
||||||
char *render;
|
|
||||||
int size;
|
|
||||||
int rsize;
|
|
||||||
int cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
// editor_t definition
|
|
||||||
struct editor_t {
|
|
||||||
struct termios entry_term;
|
|
||||||
int rows, cols;
|
|
||||||
int curx, cury;
|
|
||||||
int rx;
|
|
||||||
int mode;
|
|
||||||
int nrows;
|
|
||||||
int rowoffs, coloffs;
|
|
||||||
struct erow *row;
|
|
||||||
struct erow *killring;
|
|
||||||
int kill;
|
|
||||||
int no_kill;
|
|
||||||
char *filename;
|
|
||||||
int dirty;
|
|
||||||
int dirtyex;
|
|
||||||
char msg[80];
|
|
||||||
int mark_set;
|
|
||||||
int mark_curx, mark_cury;
|
|
||||||
time_t msgtm;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
void Killring::flush(editor_t* editor) {
|
|
||||||
if (editor->killring != nullptr) {
|
|
||||||
erow_free(editor->killring);
|
|
||||||
free(editor->killring);
|
|
||||||
editor->killring = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::yank(editor_t* editor) {
|
|
||||||
if (editor->killring == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Insert killring contents at the cursor without clearing the ring.
|
|
||||||
* Interpret '\n' as an actual newline() rather than inserting a raw 0x0A
|
|
||||||
* byte, so yanked content preserves lines correctly.
|
|
||||||
*/
|
|
||||||
for (int i = 0; i < editor->killring->size; i++) {
|
|
||||||
unsigned char ch = (unsigned char) editor->killring->line[i];
|
|
||||||
if (ch == '\n') {
|
|
||||||
newline();
|
|
||||||
} else {
|
|
||||||
insertch(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::start_with_char(editor_t* editor, unsigned char ch) {
|
|
||||||
erow* row = nullptr;
|
|
||||||
|
|
||||||
if (editor->killring != nullptr) {
|
|
||||||
erow_free(editor->killring);
|
|
||||||
free(editor->killring);
|
|
||||||
editor->killring = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->killring = static_cast<erow*>(malloc(sizeof(erow)));
|
|
||||||
assert(editor->killring != nullptr);
|
|
||||||
assert(erow_init(editor->killring, 0) == 0);
|
|
||||||
|
|
||||||
/* append one char to empty killring without affecting editor.dirty */
|
|
||||||
row = editor->killring;
|
|
||||||
|
|
||||||
row->line = static_cast<char*>(realloc(row->line, row->size + 2));
|
|
||||||
assert(row->line != nullptr);
|
|
||||||
row->line[row->size] = ch;
|
|
||||||
row->size++;
|
|
||||||
row->line[row->size] = '\0';
|
|
||||||
erow_update(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::append_char(editor_t* editor, unsigned char ch) {
|
|
||||||
erow* row = nullptr;
|
|
||||||
|
|
||||||
if (editor->killring == nullptr) {
|
|
||||||
start_with_char(editor, ch);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
row = editor->killring;
|
|
||||||
row->line = static_cast<char*>(realloc(row->line, row->size + 2));
|
|
||||||
assert(row->line != nullptr);
|
|
||||||
row->line[row->size] = ch;
|
|
||||||
row->size++;
|
|
||||||
row->line[row->size] = '\0';
|
|
||||||
erow_update(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::prepend_char(editor_t* editor, unsigned char ch) {
|
|
||||||
if (editor->killring == nullptr) {
|
|
||||||
start_with_char(editor, ch);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
erow* row = editor->killring;
|
|
||||||
row->line = static_cast<char*>(realloc(row->line, row->size + 2));
|
|
||||||
assert(row->line != nullptr);
|
|
||||||
memmove(&row->line[1], &row->line[0], row->size + 1);
|
|
||||||
row->line[0] = ch;
|
|
||||||
row->size++;
|
|
||||||
erow_update(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::toggle_markset(editor_t* editor) {
|
|
||||||
if (editor->mark_set) {
|
|
||||||
editor->mark_set = 0;
|
|
||||||
editor_set_status("Mark cleared.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->mark_set = 1;
|
|
||||||
editor->mark_curx = editor->curx;
|
|
||||||
editor->mark_cury = editor->cury;
|
|
||||||
editor_set_status("Mark set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int Killring::cursor_after_mark(editor_t* editor) {
|
|
||||||
if (editor->mark_cury < editor->cury) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor->mark_cury > editor->cury) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return editor->curx >= editor->mark_curx;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Killring::count_chars_from_cursor_to_mark(editor_t* editor) {
|
|
||||||
int count = 0;
|
|
||||||
int curx = editor->curx;
|
|
||||||
int cury = editor->cury;
|
|
||||||
|
|
||||||
int markx = editor->mark_curx;
|
|
||||||
int marky = editor->mark_cury;
|
|
||||||
|
|
||||||
if (!cursor_after_mark(editor)) {
|
|
||||||
swap_int(&curx, &markx);
|
|
||||||
swap_int(&cury, &marky);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->curx = markx;
|
|
||||||
editor->cury = marky;
|
|
||||||
|
|
||||||
while (editor->cury != cury) {
|
|
||||||
while (!cursor_at_eol()) {
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (editor->curx != curx) {
|
|
||||||
count++;
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::kill_region(editor_t* editor) {
|
|
||||||
int curx = editor->curx;
|
|
||||||
int cury = editor->cury;
|
|
||||||
int markx = editor->mark_curx;
|
|
||||||
int marky = editor->mark_cury;
|
|
||||||
|
|
||||||
if (!editor->mark_set) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* kill the current killring */
|
|
||||||
flush(editor);
|
|
||||||
|
|
||||||
if (!cursor_after_mark(editor)) {
|
|
||||||
swap_int(&curx, &markx);
|
|
||||||
swap_int(&cury, &marky);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->curx = markx;
|
|
||||||
editor->cury = marky;
|
|
||||||
|
|
||||||
while (editor->cury != cury) {
|
|
||||||
while (!cursor_at_eol()) {
|
|
||||||
append_char(editor, editor->row[editor->cury].line[editor->curx]);
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
}
|
|
||||||
append_char(editor, '\n');
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
while (editor->curx != curx) {
|
|
||||||
append_char(editor, editor->row[editor->cury].line[editor->curx]);
|
|
||||||
move_cursor(1000 + 3); // ARROW_RIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
editor_set_status("Region killed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Killring::delete_region(editor_t* editor) {
|
|
||||||
int count = count_chars_from_cursor_to_mark(editor);
|
|
||||||
int killed = 0;
|
|
||||||
int curx = editor->curx;
|
|
||||||
int cury = editor->cury;
|
|
||||||
int markx = editor->mark_curx;
|
|
||||||
int marky = editor->mark_cury;
|
|
||||||
|
|
||||||
if (!editor->mark_set) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cursor_after_mark(editor)) {
|
|
||||||
swap_int(&curx, &markx);
|
|
||||||
swap_int(&cury, &marky);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->curx = markx;
|
|
||||||
editor->cury = marky;
|
|
||||||
|
|
||||||
while (killed < count) {
|
|
||||||
deletech(0); // KILLRING_NO_OP
|
|
||||||
killed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor->kill = 1;
|
|
||||||
editor_set_status("Region killed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
44
killring.h
44
killring.h
@@ -1,44 +0,0 @@
|
|||||||
#ifndef KILLRING_HPP
|
|
||||||
#define KILLRING_HPP
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
struct editor_t;
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Killring class for cut/paste (kill/yank) operations.
|
|
||||||
*/
|
|
||||||
class Killring {
|
|
||||||
public:
|
|
||||||
Killring() = default;
|
|
||||||
~Killring() = default;
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment
|
|
||||||
Killring(const Killring&) = delete;
|
|
||||||
Killring& operator=(const Killring&) = delete;
|
|
||||||
|
|
||||||
// Killring operations
|
|
||||||
static void flush(editor_t* editor);
|
|
||||||
static void yank(editor_t* editor);
|
|
||||||
static void start_with_char(editor_t* editor, unsigned char ch);
|
|
||||||
static void append_char(editor_t* editor, unsigned char ch);
|
|
||||||
static void prepend_char(editor_t* editor, unsigned char ch);
|
|
||||||
|
|
||||||
// Mark operations
|
|
||||||
static void toggle_markset(editor_t* editor);
|
|
||||||
static int cursor_after_mark(editor_t* editor);
|
|
||||||
|
|
||||||
// Region operations
|
|
||||||
static void kill_region(editor_t* editor);
|
|
||||||
static void delete_region(editor_t* editor);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int count_chars_from_cursor_to_mark(editor_t* editor);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // KILLRING_HPP
|
|
||||||
1671
main.cc → main.c
1671
main.cc → main.c
File diff suppressed because it is too large
Load Diff
162
scratch.c
Normal file
162
scratch.c
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* scratch.c - ideas in progress
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REFLOW_MARGIN 72
|
||||||
|
|
||||||
|
void
|
||||||
|
reflow_region(void)
|
||||||
|
{
|
||||||
|
int start_row, end_row, i, col, wlen, this_len;
|
||||||
|
struct erow *row;
|
||||||
|
struct abuf buf = ABUF_INIT;
|
||||||
|
struct abuf out = ABUF_INIT;
|
||||||
|
int in_paragraph = 0;
|
||||||
|
int indent_len = 0;
|
||||||
|
char indent[REFLOW_MARGIN + 1];
|
||||||
|
char word[REFLOW_MARGIN + 1];
|
||||||
|
char *e = NULL;
|
||||||
|
char *p = NULL;
|
||||||
|
char *s = NULL;
|
||||||
|
|
||||||
|
if (editor.mark_set) {
|
||||||
|
if (editor.mark_cury < editor.cury ||
|
||||||
|
(editor.mark_cury == editor.cury &&
|
||||||
|
editor.mark_curx < editor.curx)) {
|
||||||
|
start_row = editor.mark_cury;
|
||||||
|
end_row = editor.cury;
|
||||||
|
} else {
|
||||||
|
start_row = editor.cury;
|
||||||
|
end_row = editor.mark_cury;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
start_row = end_row = editor.cury;
|
||||||
|
while (start_row > 0 && editor.row[start_row - 1].size > 0) {
|
||||||
|
start_row--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (end_row < editor.nrows - 1 &&
|
||||||
|
editor.row[end_row + 1].size > 0) {
|
||||||
|
end_row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_row >= editor.nrows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_row >= editor.nrows) {
|
||||||
|
end_row = editor.nrows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = start_row; i <= end_row; i++) {
|
||||||
|
row = &editor.row[i];
|
||||||
|
|
||||||
|
if (row->size == 0) {
|
||||||
|
if (in_paragraph) {
|
||||||
|
ab_append(&buf, "\n", 1);
|
||||||
|
in_paragraph = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ab_append(&buf, "\n", 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_paragraph) {
|
||||||
|
indent_len = 0;
|
||||||
|
while (indent_len < row->size &&
|
||||||
|
(row->line[indent_len] == ' ' ||
|
||||||
|
row->line[indent_len] == '\t')) {
|
||||||
|
indent[indent_len] = row->line[indent_len], indent_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
indent[indent_len] = '\0';
|
||||||
|
in_paragraph = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ab_append(&buf, row->line + indent_len, row->size - indent_len);
|
||||||
|
ab_append(&buf, " ", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_paragraph) {
|
||||||
|
ab_append(&buf, "\n", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
p = buf.b;
|
||||||
|
col = 0;
|
||||||
|
|
||||||
|
while (p != NULL && *p != '\0') {
|
||||||
|
while (*p && isspace((unsigned char)*p) &&
|
||||||
|
*p != '\n') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlen = 0;
|
||||||
|
while (*p && !isspace((unsigned char)*p)) {
|
||||||
|
if (wlen < REFLOW_MARGIN)
|
||||||
|
word[wlen++] = *p;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
word[wlen] = '\0';
|
||||||
|
|
||||||
|
if (*p == '\n' && (p[1] == '\n' || p[1] == '\0')) {
|
||||||
|
ab_append(&out, "\n", 1); /* flush */
|
||||||
|
col = 0;
|
||||||
|
p++; /* consume the extra \n */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this_len = wlen;
|
||||||
|
if (col > 0) {
|
||||||
|
this_len++; /* space before word */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col == 0) {
|
||||||
|
ab_append(&out, indent, indent_len);
|
||||||
|
col = indent_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col + this_len > REFLOW_MARGIN && col > 0) {
|
||||||
|
ab_append(&out, "\n", 1);
|
||||||
|
ab_append(&out, indent, indent_len);
|
||||||
|
col = indent_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col > 0) {
|
||||||
|
ab_append(&out, " ", 1);
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ab_append(&out, word, wlen);
|
||||||
|
col += wlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col > 0) {
|
||||||
|
ab_append(&out, "\n", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the old switcharoo */
|
||||||
|
buf = out;
|
||||||
|
ab_free(&out);
|
||||||
|
|
||||||
|
|
||||||
|
for (i = end_row; i >= start_row; i--) {
|
||||||
|
delete_row(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
s = buf.b;
|
||||||
|
while ((e = strchr(s, '\n'))) {
|
||||||
|
erow_insert(start_row++, s, e - s);
|
||||||
|
s = e + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ab_free(&buf);
|
||||||
|
|
||||||
|
editor.dirty++;
|
||||||
|
editor_set_status("Region reflowed to %d columns", REFLOW_MARGIN);
|
||||||
|
}
|
||||||
111
terminal.cc
111
terminal.cc
@@ -1,111 +0,0 @@
|
|||||||
#include "terminal.h"
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
static Terminal* g_terminal_instance = nullptr;
|
|
||||||
|
|
||||||
static void cleanup_terminal_on_exit() {
|
|
||||||
if (g_terminal_instance) {
|
|
||||||
g_terminal_instance->disable_raw_mode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Terminal::Terminal() : raw_mode_enabled_(false) {
|
|
||||||
g_terminal_instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Terminal::~Terminal() {
|
|
||||||
if (raw_mode_enabled_) {
|
|
||||||
disable_raw_mode();
|
|
||||||
}
|
|
||||||
g_terminal_instance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Terminal::setup() {
|
|
||||||
if (tcgetattr(STDIN_FILENO, &entry_term_) == -1) {
|
|
||||||
perror("can't snapshot terminal settings");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
atexit(cleanup_terminal_on_exit);
|
|
||||||
enable_raw_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Terminal::enable_raw_mode() {
|
|
||||||
if (raw_mode_enabled_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct termios raw;
|
|
||||||
|
|
||||||
/* Read the current terminal parameters for standard input. */
|
|
||||||
if (tcgetattr(STDIN_FILENO, &raw) == -1) {
|
|
||||||
perror("tcgetattr while enabling raw mode");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Put the terminal into raw mode.
|
|
||||||
*/
|
|
||||||
cfmakeraw(&raw);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set timeout for read(2).
|
|
||||||
*
|
|
||||||
* VMIN: what is the minimum number of bytes required for read
|
|
||||||
* to return?
|
|
||||||
*
|
|
||||||
* VTIME: max time before read(2) returns in hundreds of milli-
|
|
||||||
* seconds.
|
|
||||||
*/
|
|
||||||
raw.c_cc[VMIN] = 0;
|
|
||||||
raw.c_cc[VTIME] = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now write the terminal parameters to the current terminal,
|
|
||||||
* after flushing any waiting input out.
|
|
||||||
*/
|
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
|
|
||||||
perror("tcsetattr while enabling raw mode");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_mode_enabled_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Terminal::disable_raw_mode() {
|
|
||||||
if (!raw_mode_enabled_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear_screen();
|
|
||||||
|
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &entry_term_) == -1) {
|
|
||||||
perror("couldn't disable terminal raw mode");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_mode_enabled_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Terminal::get_window_size(int* rows, int* cols) {
|
|
||||||
struct winsize ws;
|
|
||||||
|
|
||||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
*cols = ws.ws_col;
|
|
||||||
*rows = ws.ws_row;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Terminal::clear_screen() {
|
|
||||||
(void)write(STDOUT_FILENO, "\x1b[2J", 4);
|
|
||||||
(void)write(STDOUT_FILENO, "\x1b[H", 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
39
terminal.h
39
terminal.h
@@ -1,39 +0,0 @@
|
|||||||
#ifndef TERMINAL_HPP
|
|
||||||
#define TERMINAL_HPP
|
|
||||||
|
|
||||||
#include <termios.h>
|
|
||||||
|
|
||||||
namespace ke {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminal management class for handling raw mode and terminal operations.
|
|
||||||
*/
|
|
||||||
class Terminal {
|
|
||||||
public:
|
|
||||||
Terminal();
|
|
||||||
~Terminal();
|
|
||||||
|
|
||||||
// Deleted copy constructor and assignment
|
|
||||||
Terminal(const Terminal&) = delete;
|
|
||||||
Terminal& operator=(const Terminal&) = delete;
|
|
||||||
|
|
||||||
// Setup and teardown
|
|
||||||
void setup();
|
|
||||||
void enable_raw_mode();
|
|
||||||
void disable_raw_mode();
|
|
||||||
|
|
||||||
// Terminal operations
|
|
||||||
static int get_window_size(int* rows, int* cols);
|
|
||||||
static void clear_screen();
|
|
||||||
|
|
||||||
// Access to original terminal settings
|
|
||||||
const termios& entry_term() const { return entry_term_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
termios entry_term_;
|
|
||||||
bool raw_mode_enabled_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ke
|
|
||||||
|
|
||||||
#endif // TERMINAL_HPP
|
|
||||||
Reference in New Issue
Block a user