357 lines
5.7 KiB
C
357 lines
5.7 KiB
C
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "abuf.h"
|
|
#include "core.h"
|
|
#include "editing.h"
|
|
#include "editor.h"
|
|
#include "killring.h"
|
|
|
|
|
|
void
|
|
killring_flush(void)
|
|
{
|
|
if (editor.killring != NULL) {
|
|
ab_free(editor.killring);
|
|
free(editor.killring);
|
|
editor.killring = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
killring_yank(void)
|
|
{
|
|
if (editor.killring == NULL) {
|
|
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 < (int)editor.killring->size; i++) {
|
|
unsigned char ch = (unsigned char)editor.killring->b[i];
|
|
if (ch == '\n') {
|
|
newline();
|
|
} else {
|
|
insertch(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
killring_start_with_char(unsigned char ch)
|
|
{
|
|
abuf *row = NULL;
|
|
|
|
if (editor.killring != NULL) {
|
|
ab_free(editor.killring);
|
|
free(editor.killring);
|
|
editor.killring = NULL;
|
|
}
|
|
|
|
editor.killring = malloc(sizeof(abuf));
|
|
assert(editor.killring != NULL);
|
|
assert(erow_init(editor.killring, 0) == 0);
|
|
|
|
/* append one char to empty killring without affecting editor.dirty */
|
|
row = editor.killring;
|
|
|
|
row->b = realloc(row->b, row->size + 2);
|
|
assert(row->b != NULL);
|
|
row->b[row->size] = ch;
|
|
row->size++;
|
|
row->b[row->size] = '\0';
|
|
}
|
|
|
|
|
|
void
|
|
killring_append_char(unsigned char ch)
|
|
{
|
|
abuf *row = NULL;
|
|
|
|
if (editor.killring == NULL) {
|
|
killring_start_with_char(ch);
|
|
return;
|
|
}
|
|
|
|
row = editor.killring;
|
|
row->b = realloc(row->b, row->size + 2);
|
|
assert(row->b != NULL);
|
|
row->b[row->size] = ch;
|
|
row->size++;
|
|
row->b[row->size] = '\0';
|
|
}
|
|
|
|
|
|
void
|
|
killring_prepend_char(unsigned char ch)
|
|
{
|
|
abuf *row = NULL;
|
|
|
|
if (editor.killring == NULL) {
|
|
killring_start_with_char(ch);
|
|
return;
|
|
}
|
|
|
|
row = editor.killring;
|
|
row->b = realloc(row->b, row->size + 2);
|
|
assert(row->b != NULL);
|
|
memmove(&row->b[1], &row->b[0], row->size + 1);
|
|
row->b[0] = ch;
|
|
row->size++;
|
|
}
|
|
|
|
|
|
void
|
|
toggle_markset(void)
|
|
{
|
|
if (EMARK_SET) {
|
|
EMARK_SET = 0;
|
|
editor_set_status("Mark cleared.");
|
|
return;
|
|
}
|
|
|
|
EMARK_SET = 1;
|
|
EMARK_CURX = ECURX;
|
|
EMARK_CURY = ECURY;
|
|
editor_set_status("Mark set.");
|
|
}
|
|
|
|
|
|
int
|
|
cursor_after_mark(void)
|
|
{
|
|
if (EMARK_CURY < ECURY) {
|
|
return 1;
|
|
}
|
|
|
|
if (EMARK_CURY > ECURY) {
|
|
return 0;
|
|
}
|
|
|
|
return ECURX >= EMARK_CURX;
|
|
}
|
|
|
|
|
|
int
|
|
count_chars_from_cursor_to_mark(void)
|
|
{
|
|
size_t count = 0;
|
|
size_t curx = ECURX;
|
|
size_t cury = ECURY;
|
|
size_t markx = EMARK_CURX;
|
|
size_t marky = EMARK_CURY;
|
|
|
|
if (!cursor_after_mark()) {
|
|
swap_size_t(&curx, &markx);
|
|
swap_size_t(&curx, &marky);
|
|
}
|
|
|
|
ECURX = markx;
|
|
ECURY = marky;
|
|
|
|
while (ECURY != cury) {
|
|
while (!cursor_at_eol()) {
|
|
move_cursor(ARROW_RIGHT, 1);
|
|
count++;
|
|
}
|
|
|
|
move_cursor(ARROW_RIGHT, 1);
|
|
count++;
|
|
}
|
|
|
|
while (ECURX != curx) {
|
|
count++;
|
|
move_cursor(ARROW_RIGHT, 1);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
void
|
|
kill_region(void)
|
|
{
|
|
size_t curx = ECURX;
|
|
size_t cury = ECURY;
|
|
size_t markx = EMARK_CURX;
|
|
size_t marky = EMARK_CURY;
|
|
|
|
if (!EMARK_SET) {
|
|
return;
|
|
}
|
|
|
|
/* kill the current killring first */
|
|
killring_flush();
|
|
|
|
if (!cursor_after_mark()) {
|
|
swap_size_t(&curx, &markx);
|
|
swap_size_t(&cury, &marky);
|
|
}
|
|
|
|
ECURX = markx;
|
|
ECURY = marky;
|
|
|
|
while (ECURY != cury) {
|
|
while (!cursor_at_eol()) {
|
|
killring_append_char(EROW[ECURY].b[ECURX]);
|
|
move_cursor(ARROW_RIGHT, 0);
|
|
}
|
|
killring_append_char('\n');
|
|
move_cursor(ARROW_RIGHT, 0);
|
|
}
|
|
|
|
while (ECURX != curx) {
|
|
killring_append_char(EROW[ECURY].b[ECURX]);
|
|
move_cursor(ARROW_RIGHT, 0);
|
|
}
|
|
|
|
editor_set_status("Region killed.");
|
|
/* clearing the mark needs to be done outside this function; *
|
|
* when deleting the region, the mark needs to be set too. */
|
|
}
|
|
|
|
|
|
void
|
|
indent_region(void)
|
|
{
|
|
size_t start_row = 0;
|
|
size_t end_row = 0;
|
|
size_t i = 0;
|
|
|
|
if (!EMARK_SET) {
|
|
return;
|
|
}
|
|
|
|
if (EMARK_CURY < ECURY) {
|
|
start_row = EMARK_CURY;
|
|
end_row = ECURY;
|
|
} else if (EMARK_CURY > ECURY) {
|
|
start_row = ECURY;
|
|
end_row = EMARK_CURY;
|
|
} else {
|
|
start_row = end_row = ECURY;
|
|
}
|
|
|
|
/* Ensure bounds are valid */
|
|
if (end_row >= ENROWS) {
|
|
end_row = ENROWS - 1;
|
|
}
|
|
|
|
if (start_row >= ENROWS) {
|
|
return;
|
|
}
|
|
|
|
for (i = start_row; i <= end_row; i++) {
|
|
row_insert_ch(&EROW[i], 0, '\t');
|
|
}
|
|
|
|
ECURX = 0;
|
|
EDIRTY++;
|
|
}
|
|
|
|
|
|
void
|
|
unindent_region(void)
|
|
{
|
|
size_t start_row = 0;
|
|
size_t end_row = 0;
|
|
size_t i = 0;
|
|
size_t del = 0;
|
|
abuf *row = NULL;
|
|
|
|
if (!EMARK_SET) {
|
|
editor_set_status("Mark not set.");
|
|
return;
|
|
}
|
|
|
|
if (EMARK_CURY < ECURY ||
|
|
(EMARK_CURY == ECURY && EMARK_CURX < ECURX)) {
|
|
start_row = EMARK_CURY;
|
|
end_row = ECURY;
|
|
} else {
|
|
start_row = ECURY;
|
|
end_row = EMARK_CURY;
|
|
}
|
|
|
|
if (start_row >= ENROWS) {
|
|
return;
|
|
}
|
|
|
|
if (end_row >= ENROWS) {
|
|
end_row = ENROWS - 1;
|
|
}
|
|
|
|
for (i = start_row; i <= end_row; i++) {
|
|
row = &EROW[i];
|
|
|
|
if (row->size == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (row->b[0] == '\t') {
|
|
row_delete_ch(row, 0);
|
|
} else if (row->b[0] == ' ') {
|
|
del = 0;
|
|
|
|
while (del < TAB_STOP && del < row->size &&
|
|
row->b[del] == ' ') {
|
|
del++;
|
|
}
|
|
|
|
if (del > 0) {
|
|
/* +1 for NUL */
|
|
memmove(row->b, row->b + del,
|
|
row->size - del + 1);
|
|
row->size -= del;
|
|
}
|
|
}
|
|
}
|
|
|
|
ECURX = 0;
|
|
ECURY = start_row;
|
|
|
|
EDIRTY++;
|
|
editor_set_status("Region unindented");
|
|
}
|
|
|
|
|
|
void
|
|
delete_region(void)
|
|
{
|
|
size_t count = count_chars_from_cursor_to_mark();
|
|
size_t killed = 0;
|
|
size_t curx = ECURX;
|
|
size_t cury = ECURY;
|
|
size_t markx = EMARK_CURX;
|
|
size_t marky = EMARK_CURY;
|
|
|
|
if (!EMARK_SET) {
|
|
return;
|
|
}
|
|
|
|
if (!cursor_after_mark()) {
|
|
swap_size_t(&curx, &markx);
|
|
swap_size_t(&cury, &marky);
|
|
}
|
|
|
|
jump_to_position(markx, marky);
|
|
|
|
while (killed < count) {
|
|
move_cursor(ARROW_RIGHT, 0);
|
|
deletech(KILLRING_NO_OP);
|
|
killed++;
|
|
}
|
|
|
|
while (ECURX != markx && ECURY != marky) {
|
|
deletech(KILLRING_NO_OP);
|
|
}
|
|
|
|
editor.kill = 1;
|
|
editor_set_status("Region killed.");
|
|
}
|