Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions legmacs/buffer.lg
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,54 @@
(assoc state :scroll-row (max 0 scroll-row) :scroll-col (max 0 scroll-col))))

;; --- whole-buffer text access (for eval, search-the-whole-thing, etc.) -----
;;
;; Paren-match / electric-pair / auto-indent run after every keystroke and
;; used to string/join the whole buffer, then count/subs that string from
;; the start (both O(n) in let-go). Cursor motion doesn't replace :lines,
;; so we cache a char vector and per-line start offsets against that
;; vector's identity -- a move is then nth, not a rebuild.

(def ^:private flat-cache (atom nil))

(defn- build-flat [lines]
(let [n (count lines)]
(loop [i 0, off 0, starts [], lens [], chars []]
(if (>= i n)
{:lines lines :starts starts :lens lens :chars chars}
(let [line (nth lines i)
chs (vec (seq line))
len (count chs)
chars (reduce conj chars chs)
chars (if (< i (dec n)) (conj chars \newline) chars)]
(recur (inc i)
(+ off len (if (< i (dec n)) 1 0))
(conj starts off)
(conj lens len)
chars))))))

(defn- flat-index
"Cached {:starts :lens :chars} for `state`'s current :lines."
[state]
(let [lines (:lines state)
cached @flat-cache]
(if (and cached (identical? lines (:lines cached)))
cached
(let [built (build-flat lines)]
(reset! flat-cache built)
built))))

(defn buffer-chars
"The buffer as a char vector (newlines between lines, no trailing
newline) -- the shape lisp-syntax/prog-syntax scan. Cached against
:lines identity so a cursor move doesn't rebuild it."
[state]
(:chars (flat-index state)))

(defn buffer-text
"The whole buffer joined with newlines -- no trailing newline, unlike
join-lines (which is for writing files)."
[state]
(string/join "\n" (:lines state)))
(apply str (buffer-chars state)))

(defn text-before-cursor
"Everything in the buffer up to (not including) the cursor, as one
Expand Down Expand Up @@ -521,28 +563,28 @@

;; --- offset <-> [row col] --------------------------------------------
;;
;; legmacs.lisp-syntax scans the buffer as one flat string (buffer-text --
;; string/join "\n" lines), since bracket-matching/sexp boundaries are
;; naturally a flat-text problem. These convert between that flat
;; character offset and the [row col] the rest of the editor speaks.
;; lisp-syntax/prog-syntax scan a flat char vector (buffer-chars). These
;; convert between that offset and the [row col] the rest of the editor
;; speaks, using the cached per-line lengths so we don't recount strings.

(defn offset-of
"The offset into (buffer-text state) of [row col]. Each line contributes
(count line) + 1 for its joining newline."
[state row col]
(let [lines (:lines state)]
(+ (reduce + 0 (map #(inc (count %)) (subvec lines 0 row))) col)))
(let [starts (:starts (flat-index state))]
(+ (nth starts row 0) col)))

(defn row-col-of
"The inverse of offset-of: the [row col] at flat offset `offset`."
[state offset]
(loop [row 0, remaining offset, lines (:lines state)]
(if (empty? lines)
[row remaining]
(let [len (count (first lines))]
(if (<= remaining len)
[row remaining]
(recur (inc row) (- remaining len 1) (rest lines)))))))
(let [lens (:lens (flat-index state))]
(loop [row 0, remaining offset]
(if (>= row (count lens))
[row remaining]
(let [len (nth lens row)]
(if (<= remaining len)
[row remaining]
(recur (inc row) (- remaining len 1))))))))

;; --- symbol at point (word-level expand-region) -----------------------

Expand Down
4 changes: 2 additions & 2 deletions legmacs/indent.lg
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@
"Bracket depth at the start of `row`, minus one level if the line pulls
itself back (see dedents-itself?)."
[state row code ws rules spec width]
(let [text (buf/buffer-text state)
depth (ps/depth-at text (buf/offset-of state row 0) spec)
(let [chars (buf/buffer-chars state)
depth (ps/depth-at chars (buf/offset-of state row 0) spec)
level (- depth (if (dedents-itself? code ws rules spec) 1 0))]
(* width (max 0 level))))

Expand Down
16 changes: 11 additions & 5 deletions legmacs/lisp_syntax.lg
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
(ns legmacs.lisp-syntax
(:require [string]))

(defn- as-chars
"Scan input as a char vector. Callers that already have one (buffer-chars)
skip the copy; a string is converted once."
[text]
(if (vector? text) text (vec (seq text))))

(def ^:private open->close {\( \) \[ \] \{ \}})
(def ^:private close->open {\) \( \] \[ \} \{})

Expand Down Expand Up @@ -58,9 +64,9 @@
:spans alone can't answer this because a string closed
exactly at upto and a string still open at upto both end up
with an identical trailing {:end upto} span"
([text] (scan text (count text)))
([text] (let [chars (as-chars text)] (scan chars (count chars))))
([text upto]
(let [chars (vec (seq text))
(let [chars (as-chars text)
len (min upto (count chars))]
(loop [i 0, stack (), pairs {}, spans [], str-start nil]
(cond
Expand Down Expand Up @@ -167,7 +173,7 @@
over everything above point, so it doesn't allocate what nobody is going
to read."
[text offset]
(let [chars (vec (seq text))
(let [chars (as-chars text)
n (count chars)
len (min offset n)]
;; every skip is capped at `len`, so a string or comment that hasn't
Expand Down Expand Up @@ -226,9 +232,9 @@
matching -- a closer's match is just the innermost bracket still open in
front of it -- and the half that runs the instant you type one."
[text offset]
(let [chars (vec (seq text))
(let [chars (as-chars text)
ch (when (< -1 offset (count chars)) (nth chars offset))
{:keys [stack code?]} (open-stack-at* text offset)
{:keys [stack code?]} (open-stack-at* chars offset)
[open-ch open-offset] (first stack)]
(when (and ch code? open-ch (= ch (get open->close open-ch)))
open-offset)))
Expand Down
34 changes: 17 additions & 17 deletions legmacs/modes/letgo.lg
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,18 @@
;; it stays current after every edit or cursor move while let-go-mode is
;; active -- see legmacs.modes/register-mode!.

(defn- char-at [text offset]
(when (< -1 offset (count text)) (first (subs text offset (inc offset)))))
(defn- char-at [chars offset]
(when (< -1 offset (count chars)) (nth chars offset)))

(defn update-paren-match
"Emacs show-paren-mode's rule: if the character *after* point is an
open bracket, or the character *before* point is a close bracket,
highlight it and its match. Otherwise clear :paren-match."
[state]
(let [text (buf/buffer-text state)
(let [chars (buf/buffer-chars state)
offset (buf/offset-of state (:cursor-row state) (:cursor-col state))
after (char-at text offset)
before (char-at text (dec offset))
after (char-at chars offset)
before (char-at chars (dec offset))
point (cond
(and after (ls/open-bracket? after)) offset
(and before (ls/close-bracket? before)) (dec offset)
Expand All @@ -180,9 +180,9 @@
;; which costs one lean pass up to point; only the open-bracket case
;; needs the full scan (and its bracket-pair map) to look forward
match (when point
(if (ls/close-bracket? (char-at text point))
(ls/match-for-closer text point)
(ls/matching (ls/scan text) point)))]
(if (ls/close-bracket? (char-at chars point))
(ls/match-for-closer chars point)
(ls/matching (ls/scan chars) point)))]
(assoc state :paren-match
(when match
[(buf/row-col-of state point) (buf/row-col-of state match)]))))
Expand Down Expand Up @@ -211,15 +211,15 @@

(def ^:private open->close-char {\( \) \[ \] \{ \}})

(defn- point-in-string-or-comment? [text offset]
(ls/point-in-string-or-comment-at? text offset))
(defn- point-in-string-or-comment? [chars offset]
(ls/point-in-string-or-comment-at? chars offset))

(defn- point-info [state]
(let [text (buf/buffer-text state)
(let [chars (buf/buffer-chars state)
offset (buf/offset-of state (:cursor-row state) (:cursor-col state))]
{:before (char-at text (dec offset))
:after (char-at text offset)
:in-str-or-comment? (point-in-string-or-comment? text offset)}))
{:before (char-at chars (dec offset))
:after (char-at chars offset)
:in-str-or-comment? (point-in-string-or-comment? chars offset)}))

(defn- insert-pair [state open-ch close-ch]
(if (:in-str-or-comment? (point-info state))
Expand Down Expand Up @@ -368,9 +368,9 @@
the first argument on the opener's own line (falling back to one past
the bracket when the opener ends its line)."
[state row]
(let [text (buf/buffer-text state)
(let [chars (buf/buffer-chars state)
offset (buf/offset-of state row 0)]
(if-let [[open-ch open-offset] (ls/innermost-open-before text offset)]
(if-let [[open-ch open-offset] (ls/innermost-open-before chars offset)]
(let [[open-row open-col] (buf/row-col-of state open-offset)
line (nth (:lines state) open-row)]
(if (contains? #{\[ \{} open-ch)
Expand Down Expand Up @@ -432,7 +432,7 @@
[(buf/offset-of state sr sc) (buf/offset-of state er ec)]))

(defn- next-region-to-select [state]
(let [scanned (ls/scan (buf/buffer-text state))
(let [scanned (ls/scan (buf/buffer-chars state))
current (region-offsets state)]
(if current
(let [[s e] current]
Expand Down
24 changes: 12 additions & 12 deletions legmacs/modes/structural.lg
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@

(defn- spec-for [state] (modes/mode-syntax-spec (:mode state)))

(defn- char-at [text offset]
(when (< -1 offset (count text)) (first (subs text offset (inc offset)))))
(defn- char-at [chars offset]
(when (< -1 offset (count chars)) (nth chars offset)))

;; --- paren matching --------------------------------------------------

Expand All @@ -41,10 +41,10 @@
(rather than erroring) when the mode has none."
[state]
(if-let [spec (spec-for state)]
(let [text (buf/buffer-text state)
(let [chars (buf/buffer-chars state)
offset (buf/offset-of state (:cursor-row state) (:cursor-col state))
after (char-at text offset)
before (char-at text (dec offset))
after (char-at chars offset)
before (char-at chars (dec offset))
point (cond
(and after (ps/open-bracket? spec after)) offset
(and before (ps/close-bracket? spec before)) (dec offset)
Expand All @@ -53,9 +53,9 @@
;; bracket -- only needs the innermost open bracket in front of
;; point, so it skips the full scan (see ps/match-for-closer)
match (when point
(if (ps/close-bracket? spec (char-at text point))
(ps/match-for-closer text point spec)
(ps/matching (ps/scan text spec) point)))]
(if (ps/close-bracket? spec (char-at chars point))
(ps/match-for-closer chars point spec)
(ps/matching (ps/scan chars spec) point)))]
(assoc state :paren-match
(when match
[(buf/row-col-of state point) (buf/row-col-of state match)])))
Expand All @@ -64,11 +64,11 @@
;; --- auto-pairing brackets ----------------------------------------------

(defn- point-info [state spec]
(let [text (buf/buffer-text state)
(let [chars (buf/buffer-chars state)
offset (buf/offset-of state (:cursor-row state) (:cursor-col state))]
{:before (char-at text (dec offset))
:after (char-at text offset)
:in-str-or-comment? (ps/point-in-string-or-comment-at? text offset spec)}))
{:before (char-at chars (dec offset))
:after (char-at chars offset)
:in-str-or-comment? (ps/point-in-string-or-comment-at? chars offset spec)}))

(defn- insert-pair [state open-ch close-ch]
(if-let [spec (spec-for state)]
Expand Down
16 changes: 11 additions & 5 deletions legmacs/prog_syntax.lg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
(ns legmacs.prog-syntax
(:require [string]))

(defn- as-chars
"Scan input as a char vector. Callers that already have one (buffer-chars)
skip the copy; a string is converted once."
[text]
(if (vector? text) text (vec (seq text))))

(def ^:private default-brackets {\( \) \[ \] \{ \}})

(defn- marker-at?
Expand Down Expand Up @@ -119,7 +125,7 @@
upto produce identically-shaped trailing spans, so a flag is the only way
to tell them apart. Line comments don't share this ambiguity (nothing
closes one early), so they don't need a matching flag."
([text spec] (scan text (count text) spec))
([text spec] (let [chars (as-chars text)] (scan chars (count chars) spec)))
([text upto spec]
(let [{:keys [line-comments block-comment string-delims multiline-strings brackets]} spec
line-comment-markers (markers-of (or line-comments []))
Expand All @@ -135,7 +141,7 @@
block-open-v (when block-open (vec (seq block-open)))
block-close-v (when block-close (vec (seq block-close)))
block-first (when block-open-v (nth block-open-v 0))
chars (vec (seq text))
chars (as-chars text)
len (min upto (count chars))]
(loop [i 0, stack (), pairs {}, spans []]
(if (>= i len)
Expand Down Expand Up @@ -220,7 +226,7 @@
block-first (when block-open-v (nth block-open-v 0))
comment-firsts (first-chars line-comment-markers)
ml-firsts (first-chars (mapv :open-v multiline-strings))
chars (vec (seq text))
chars (as-chars text)
n (count chars)
stop (min offset n)]
;; every skip is capped at `stop`, so a construct that hasn't closed by
Expand Down Expand Up @@ -277,9 +283,9 @@
scan of the buffer -- and it's the half that runs the instant you *type*
a closing bracket."
[text offset spec]
(let [chars (vec (seq text))
(let [chars (as-chars text)
ch (when (< -1 offset (count chars)) (nth chars offset))
{:keys [stack code?]} (open-stack-at text offset spec)
{:keys [stack code?]} (open-stack-at chars offset spec)
[open-ch open-offset] (first stack)]
(when (and ch code? open-ch
(= ch (get (or (:brackets spec) default-brackets) open-ch)))
Expand Down
Loading
Loading