123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- import strutils
- # Helpers
- proc shift*[T](s: var seq[T]): T {.inline, noSideEffect.} =
- ## Remove and return the first element of a seq
- result = s[0]
- s.delete(0)
- # Gap buffer
- type
- GapBuffer* = object of RootObj
- head*, tail*: seq[char]
- proc newGapBuffer*: GapBuffer =
- GapBuffer(
- head: @[],
- tail: @[]
- )
- proc toGapBuffer*(str: string, position = -1): GapBuffer =
- ## Creates a `GapBuffer` from a `string`
- var
- buf: seq[char] = @[]
- lastChar = str.len - 1
- for i in 0..lastChar: buf.add(str[i])
- var head, tail: seq[char] = @[]
- if position >= 0:
- head = buf[0..position]
- if position < lastChar:
- tail = buf[position+1..lastChar-1]
- GapBuffer(
- head: head,
- tail: tail
- )
- proc `$`*(buf: GapBuffer): string {.noSideEffect.} =
- ## The stringify operator for a GapBuffer argument. Returns `x`
- ## converted to a string.
- buf.head.join() & buf.tail.join()
- proc len*(buf: GapBuffer): int {.noSideEffect.} =
- ## Returns the length of a GapBuffer.
- buf.head.len + buf.tail.len
- proc pos*(buf: GapBuffer): int {.noSideEffect.} =
- ## Returns the position of the gap
- buf.head.len - 1
- proc move*(buf: var GapBuffer, amt: int) =
- ## Move the gap.
- if amt < 0:
- for _ in 1..(-amt):
- buf.tail.insert(buf.head.pop(), 0)
- else:
- for _ in 1..amt:
- buf.head.add(buf.tail.shift())
- proc moveTo*(buf: var GapBuffer, pos: int) =
- ## Move the gap to an absolute position
- var headLen = buf.head.len
- if pos < headLen:
- var newHead, newTail: seq[char] = @[]
- for i in 0..pos:
- newHead.add(buf.head[i])
- for i in pos+1..headLen-1:
- newTail.add(buf.head[i])
- newTail &= buf.tail
- buf.head = newHead
- buf.tail = newTail
- elif pos > headLen:
- var tailPos = pos - headLen
- for i in 0..tailPos:
- buf.head.add(buf.tail[i])
- var newTail: seq[char] = @[]
- for i in tailPos+1..buf.tail.len-1:
- newTail.add(buf.tail[i])
- buf.tail = newTail
- proc insert*(buf: var GapBuffer, ch: char) =
- ## Insert a character before the gap
- buf.head.add(ch)
- proc insert*(buf: var GapBuffer, str: string) =
- ## Insert a string before the gap
- for ch in str.items():
- buf.insert(ch)
- proc delete*(buf: var GapBuffer) =
- ## Remove the character before the gap
- buf.head.setLen(buf.head.len - 1)
- proc `[]`*(buf: GapBuffer, i: int): char {.noSideEffect.} =
- ## Get the character at the index `i`
- var headLen = buf.head.len
- if i < headLen:
- buf.head[i]
- else:
- buf.tail[i - headLen]
- proc `[]=`*(buf: var GapBuffer, i: int, c: char) =
- ## Set ther character at the index `i`
- var headLen = buf.head.len
- if i < headLen:
- buf.head[i] = c
- else:
- buf.tail[i - headLen] = c
- # Iterators
- iterator items*(buf: GapBuffer): char =
- for c in buf.head: yield c
- for c in buf.tail: yield c
- iterator splitLines*(buf: GapBuffer): seq[char] =
- ## Splits a GapBuffer into lines.
- template impl(s: seq[char]): untyped =
- for i in 0..s.len-1:
- case s[i]:
- of '\l':
- yield line
- line = @[]
- of '\c':
- if i+1 < buf.len and s[i+1] != '\l':
- # If it's not a line feed, treat this as the end of the line,
- # if it is, let the line feed case handle it on the next
- # character.
- yield line
- line = @[]
- else:
- line.add(s[i])
- var line: seq[char] = @[]
- impl(buf.head)
- impl(buf.tail)
- yield line
|