aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/design/002-dsl-direction.md4
-rw-r--r--examples/notes.aup5
-rw-r--r--src/compile.c12
-rw-r--r--tests/check.py3
4 files changed, 17 insertions, 7 deletions
diff --git a/docs/design/002-dsl-direction.md b/docs/design/002-dsl-direction.md
index 7d38d55..3692866 100644
--- a/docs/design/002-dsl-direction.md
+++ b/docs/design/002-dsl-direction.md
@@ -30,6 +30,7 @@ aup는 범용 언어가 아니라 **무엇이 무엇에 연결되는지를 선�
state title: text = "할 일"
state note: text = "" # 한 줄이든 1 MiB 문서든 같은 타입
state at: cursor in note # 항상 글자 경계에 있고 편집을 따라간다
+state from: mark in note # 같지만, 이 자리에 넣은 글보다 앞에 남는다
input typed = text # 호스트가 준 확정 입력(IME 조합 완료분)
input back = key Backspace
@@ -49,6 +50,9 @@ view current = note.line(at) # 커서가 있는 줄. 복사하지 않�
`cursor in 값` 타입을 둔다. 커서는 항상 글자 경계에 있고, 범위를 벗어나지 않으며, 같은 이벤트의
편집에 맞춰 런타임이 옮긴다. 이동은 단위로 쓴다: `at + 1 char`, `at - 1 line`, `note.start`, `note.end`.
범위는 `a .. b`, 부분은 `note[a .. b]`, 줄은 `note.line(at)`이다. 모두 복사 없는 보기다.
+- **커서와 마크.** 둘 다 편집을 따라간다. 차이는 그 자리에 글을 넣을 때뿐이다. 커서(캐럿)는 넣은 글 뒤로 가고,
+ 마크(기억해 둔 위치, 선택의 시작점, 책갈피)는 글 앞에 남는다. 지울 때는 둘 다 지운 범위의 처음으로 모인다.
+ 타입은 같아서 커서에 쓰는 모든 것(이동, 비교, 범위, `insert` 위치)에 마크도 쓴다.
- **글자 단위.** 목표는 사용자가 한 글자로 보는 단위(grapheme cluster)다. 첫 구현의 `char`는 Unicode
scalar이고, 분절 표(UAX #29)는 크기 비용이 있어 선택 모듈로 붙인다. `word`도 그때 추가한다.
- **편집.** `insert`, `remove`, `replace(a .. b, s)`는 Pure draft의 splice가 된다. 이벤트가 성공하면 커밋된다.
diff --git a/examples/notes.aup b/examples/notes.aup
index fdc91b8..214b617 100644
--- a/examples/notes.aup
+++ b/examples/notes.aup
@@ -1,10 +1,11 @@
# A small text editor. The runtime owns the text: typing inserts at the
# cursor, the cursor and the mark always sit between characters and follow
-# edits, and a failed event would leave everything as it was.
+# edits (text typed at the mark goes after it: the mark stays), and a failed
+# event would leave everything as it was.
state title: text = "메모"
state note: text = ""
state at: cursor in note
-state mark: cursor in note
+state mark: mark in note
input typed = text
input left = key ArrowLeft
diff --git a/src/compile.c b/src/compile.c
index c2e5ff2..234991f 100644
--- a/src/compile.c
+++ b/src/compile.c
@@ -119,6 +119,7 @@ typedef struct {
/* Declarations in source order. */
char state_names[STATES][NAME], state_target[STATES][NAME];
int state_types[STATES], state_line[STATES], state_column[STATES];
+ bool state_stays[STATES]; /* a mark: a cursor that stays before text inserted at it */
double initial[STATES];
Expr *state_text[STATES];
unsigned states;
@@ -987,15 +988,17 @@ static void state_declaration(Compiler *c) {
c->state_column[index] = name.column;
if (is(c, ":")) {
take(c);
- Token type = expect_name(c, "text, cursor, list or index");
+ Token type = expect_name(c, "text, cursor, mark, list or index");
if (!strcmp(type.text, "text")) {
c->state_types[index] = STATE_TEXT;
if (is(c, "=")) {
take(c);
c->state_text[index] = plain_string(c, "a text state");
}
- } else if (!strcmp(type.text, "cursor")) {
+ } else if (!strcmp(type.text, "cursor") || !strcmp(type.text, "mark")) {
+ /* Both follow edits; text inserted at a cursor goes before it, at a mark after it. */
c->state_types[index] = STATE_CURSOR;
+ c->state_stays[index] = type.text[0] == 'm';
expect_word(c, "in");
Token target = expect_name(c, "a text state");
memcpy(c->state_target[index], target.text, NAME);
@@ -1005,7 +1008,8 @@ static void state_declaration(Compiler *c) {
Token target = expect_name(c, type.text[0] == 'l' ? "a record" : "a list state");
memcpy(c->state_target[index], target.text, NAME);
} else fail_at(c, type.line, type.column,
- "state types are numbers, text, cursor in a text, list of a record, index of a list, and bytes by key");
+ "state types are numbers, text, cursor or mark in a text, list of a record, index of a list, "
+ "and bytes by key");
++c->states;
return;
}
@@ -1861,7 +1865,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
p = put32(p, state_name[i]);
p = put32(p, (uint32_t)c->state_types[i]);
if (c->state_types[i] == STATE_NUM) p = put_double(p, c->initial[i]);
- else { p = put32(p, state_ref[i]); p = put32(p, 0); }
+ else { p = put32(p, state_ref[i]); p = put32(p, c->state_stays[i] ? 1 : 0); } /* 1: the cursor stays */
}
for (unsigned i = 0; i < c->collections; ++i) { p = put32(p, collection_name[i]); p = put32(p, 1); p = put32(p, 1); }
unsigned first = 0;
diff --git a/tests/check.py b/tests/check.py
index c4484c4..7f94235 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -445,7 +445,8 @@ class NotesModel:
def insert(self, text):
p = self.at
self.note = self.note[:p] + text + self.note[p:]
- self.at, self.mark = [c + len(text) if c >= p else c for c in (self.at, self.mark)]
+ self.at = self.at + len(text) if self.at >= p else self.at
+ self.mark = self.mark + len(text) if self.mark > p else self.mark # a mark stays at the insertion point
def remove(self, a, b):
self.note = self.note[:a] + self.note[b:]