diff options
Diffstat (limited to 'src/buffer')
-rw-r--r-- | src/buffer/container.nim | 1024 | ||||
-rw-r--r-- | src/buffer/select.nim | 306 |
2 files changed, 0 insertions, 1330 deletions
diff --git a/src/buffer/container.nim b/src/buffer/container.nim deleted file mode 100644 index 53be817a..00000000 --- a/src/buffer/container.nim +++ /dev/null @@ -1,1024 +0,0 @@ -import deques -import options -import streams -import unicode - -when defined(posix): - import posix - -import buffer/buffer -import buffer/cell -import buffer/select -import config/config -import io/promise -import io/request -import io/window -import ips/forkserver -import ips/serialize -import js/javascript -import js/regex -import types/buffersource -import types/color -import types/cookie -import types/url -import utils/mimeguess -import utils/twtstr - -import chakasu/charset - -type - CursorPosition* = object - cursorx*: int - cursory*: int - xend*: int - fromx*: int - fromy*: int - setx: int - setxrefresh: bool - - ContainerEventType* = enum - NO_EVENT, FAIL, SUCCESS, NEEDS_AUTH, REDIRECT, ANCHOR, NO_ANCHOR, UPDATE, - READ_LINE, READ_AREA, OPEN, INVALID_COMMAND, STATUS, ALERT, LOADED, TITLE, - CHECK_MAILCAP, QUIT - - ContainerEvent* = object - case t*: ContainerEventType - of READ_LINE: - prompt*: string - value*: string - password*: bool - of READ_AREA: - tvalue*: string - of OPEN, REDIRECT: - request*: Request - of ANCHOR, NO_ANCHOR: - anchor*: string - of ALERT: - msg*: string - of UPDATE: - force*: bool - else: discard - - Highlight* = ref object - x*, y*: int - endy*, endx*: int - rect*: bool - clear*: bool - - Container* = ref object - parent* {.jsget.}: Container - children* {.jsget.}: seq[Container] - config*: BufferConfig - iface*: BufferInterface - width* {.jsget.}: int - height* {.jsget.}: int - title*: string # used in status msg - hovertext: array[HoverType, string] - lastpeek: HoverType - source*: BufferSource - pos: CursorPosition - bpos: seq[CursorPosition] - highlights: seq[Highlight] - process* {.jsget.}: Pid - loadinfo*: string - lines: SimpleFlexibleGrid - lineshift: int - numLines*: int - replace*: Container - code*: int - retry*: seq[URL] - hlon*: bool # highlight on? - sourcepair*: Container # pointer to buffer with a source view (may be nil) - redraw*: bool - needslines*: bool - canceled: bool - events*: Deque[ContainerEvent] - startpos: Option[CursorPosition] - hasstart: bool - redirectdepth*: int - select*: Select - -jsDestructor(Container) - -proc newBuffer*(forkserver: ForkServer, mainproc: Pid, config: BufferConfig, - source: BufferSource, title = "", redirectdepth = 0): Container = - let attrs = getWindowAttributes(stdout) - let ostream = forkserver.ostream - let istream = forkserver.istream - ostream.swrite(FORK_BUFFER) - ostream.swrite(source) - ostream.swrite(config) - ostream.swrite(attrs) - ostream.swrite(mainproc) - ostream.flush() - var process: Pid - istream.sread(process) - return Container( - source: source, - width: attrs.width, - height: attrs.height - 1, - title: title, - config: config, - redirectdepth: redirectdepth, - process: process, - pos: CursorPosition( - setx: -1 - ) - ) - -func charset*(container: Container): Charset = - return container.source.charset - -func contentType*(container: Container): Option[string] {.jsfget.} = - return container.source.contenttype - -func location*(container: Container): URL {.jsfget.} = - return container.source.location - -func lineLoaded(container: Container, y: int): bool = - return y - container.lineshift in 0..container.lines.high - -func getLine(container: Container, y: int): SimpleFlexibleLine = - if container.lineLoaded(y): - return container.lines[y - container.lineshift] - -iterator ilines*(container: Container, slice: Slice[int]): SimpleFlexibleLine {.inline.} = - for y in slice: - yield container.getLine(y) - -func cursorx*(container: Container): int {.inline.} = container.pos.cursorx -func cursory*(container: Container): int {.inline.} = container.pos.cursory -func fromx*(container: Container): int {.inline.} = container.pos.fromx -func fromy*(container: Container): int {.inline.} = container.pos.fromy -func xend(container: Container): int {.inline.} = container.pos.xend -func lastVisibleLine(container: Container): int = min(container.fromy + container.height, container.numLines) - 1 - -func currentLine(container: Container): string = - return container.getLine(container.cursory).str - -func cursorBytes(container: Container, y: int, cc = container.cursorx): int = - let line = container.getLine(y).str - var w = 0 - var i = 0 - while i < line.len and w < cc: - var r: Rune - fastRuneAt(line, i, r) - w += r.twidth(w) - return i - -func currentCursorBytes(container: Container, cc = container.cursorx): int = - return container.cursorBytes(container.cursory, cc) - -# Returns the X position of the first cell occupied by the character the cursor -# currently points to. -func cursorFirstX(container: Container): int = - if container.numLines == 0: return 0 - let line = container.currentLine - var w = 0 - var i = 0 - var r: Rune - let cc = container.cursorx - while i < line.len: - fastRuneAt(line, i, r) - let tw = r.twidth(w) - if w + tw > cc: - return w - w += tw - -# Returns the X position of the last cell occupied by the character the cursor -# currently points to. -func cursorLastX(container: Container): int = - if container.numLines == 0: return 0 - let line = container.currentLine - var w = 0 - var i = 0 - var r: Rune - let cc = container.cursorx - while i < line.len and w <= cc: - fastRuneAt(line, i, r) - w += r.twidth(w) - return max(w - 1, 0) - -# Last cell for tab, first cell for everything else (e.g. double width.) -# This is needed because moving the cursor to the 2nd cell of a double -# width character clears it on some terminals. -func cursorDispX(container: Container): int = - if container.numLines == 0: return 0 - let line = container.currentLine - if line.len == 0: return 0 - var w = 0 - var pw = 0 - var i = 0 - var r: Rune - let cc = container.cursorx - while i < line.len and w <= cc: - fastRuneAt(line, i, r) - pw = w - w += r.twidth(w) - if r == Rune('\t'): - return max(w - 1, 0) - else: - return pw - -func acursorx*(container: Container): int = - max(0, container.cursorDispX() - container.fromx) - -func acursory*(container: Container): int = - container.cursory - container.fromy - -func maxScreenWidth(container: Container): int = - for line in container.ilines(container.fromy..container.lastVisibleLine): - result = max(line.str.width(), result) - -func getTitle*(container: Container): string {.jsfunc.} = - if container.title != "": - return container.title - return container.source.location.serialize(excludepassword = true) - -func currentLineWidth(container: Container): int = - if container.numLines == 0: return 0 - return container.currentLine.width() - -func maxfromy(container: Container): int = max(container.numLines - container.height, 0) - -func maxfromx(container: Container): int = max(container.maxScreenWidth() - container.width, 0) - -func atPercentOf*(container: Container): int = - if container.numLines == 0: return 100 - return (100 * (container.cursory + 1)) div container.numLines - -func lineWindow(container: Container): Slice[int] = - if container.numLines == 0: # not loaded - return 0..container.height * 5 - let n = (container.height * 5) div 2 - var x = container.fromy - n + container.height div 2 - var y = container.fromy + n + container.height div 2 - if y >= container.numLines: - x -= y - container.numLines - y = container.numLines - if x < 0: - y += -x - x = 0 - return x .. y - -func contains*(hl: Highlight, x, y: int): bool = - if hl.rect: - let rx = hl.x .. hl.endx - let ry = hl.y .. hl.endy - return x in rx and y in ry - else: - return (y > hl.y or y == hl.y and x >= hl.x) and - (y < hl.endy or y == hl.endy and x <= hl.endx) - -func contains*(hl: Highlight, y: int): bool = - return y in hl.y .. hl.endy - -func colorArea*(hl: Highlight, y: int, limitx: Slice[int]): Slice[int] = - if hl.rect: - if y in hl.y .. hl.endy: - return max(hl.x, limitx.a) .. min(hl.endx, limitx.b) - else: - if y in hl.y + 1 .. hl.endy - 1: - return limitx - if y == hl.y and y == hl.endy: - return max(hl.x, limitx.a) .. min(hl.endx, limitx.b) - if y == hl.y: - return max(hl.x, limitx.a) .. limitx.b - if y == hl.endy: - return limitx.a .. min(hl.endx, limitx.b) - -func findHighlights*(container: Container, y: int): seq[Highlight] = - for hl in container.highlights: - if y in hl: - result.add(hl) - -func getHoverText*(container: Container): string = - for t in HoverType: - if container.hovertext[t] != "": - return container.hovertext[t] - -func isHoverURL*(container: Container, url: URL): bool = - let hoverurl = parseURL(container.hovertext[HOVER_LINK]) - return hoverurl.isSome and url.host == hoverurl.get.host - -proc triggerEvent(container: Container, event: ContainerEvent) = - container.events.addLast(event) - -proc triggerEvent(container: Container, t: ContainerEventType) = - container.triggerEvent(ContainerEvent(t: t)) - -proc updateCursor(container: Container) - -proc setNumLines(container: Container, lines: int, finish = false) = - if container.numLines != lines: - container.numLines = lines - if container.startpos.isSome and finish: - container.pos = container.startpos.get - container.startpos = none(CursorPosition) - container.updateCursor() - container.triggerEvent(STATUS) - -proc requestLines*(container: Container, w = container.lineWindow): auto {.discardable.} = - return container.iface.getLines(w).then(proc(res: tuple[numLines: int, lines: seq[SimpleFlexibleLine]]) = - container.lines.setLen(w.len) - container.lineshift = w.a - for y in 0 ..< min(res.lines.len, w.len): - container.lines[y] = res.lines[y] - container.lines[y].str.mnormalize() - container.updateCursor() - if res.numLines != container.numLines: - container.setNumLines(res.numLines, true) - let cw = container.fromy ..< container.fromy + container.height - if w.a in cw or w.b in cw or cw.a in w or cw.b in w: - container.triggerEvent(UPDATE)) - -proc redraw(container: Container) {.jsfunc.} = - container.triggerEvent(ContainerEvent(t: UPDATE, force: true)) - -proc sendCursorPosition*(container: Container) = - container.iface.updateHover(container.cursorx, container.cursory) - .then(proc(res: UpdateHoverResult) = - if res.link.isSome: - container.hovertext[HOVER_LINK] = res.link.get - if res.title.isSome: - container.hovertext[HOVER_TITLE] = res.title.get - if res.link.isSome or res.title.isSome: - container.triggerEvent(STATUS) - if res.repaint: - container.needslines = true) - -proc setFromY(container: Container, y: int) {.jsfunc.} = - if container.pos.fromy != y: - container.pos.fromy = max(min(y, container.maxfromy), 0) - container.needslines = true - container.triggerEvent(UPDATE) - -proc setFromX(container: Container, x: int, refresh = true) {.jsfunc.} = - if container.pos.fromx != x: - container.pos.fromx = max(min(x, container.maxfromx), 0) - if container.pos.fromx > container.cursorx: - container.pos.cursorx = min(container.pos.fromx, container.currentLineWidth()) - if refresh: - container.sendCursorPosition() - container.triggerEvent(UPDATE) - -proc setFromXY(container: Container, x, y: int) {.jsfunc.} = - container.setFromY(y) - container.setFromX(x) - -proc setCursorX(container: Container, x: int, refresh = true, save = true) {.jsfunc.} = - if not container.lineLoaded(container.cursory): - container.pos.setx = x - container.pos.setxrefresh = refresh - return - container.pos.setx = -1 - let cw = container.currentLineWidth() - let x2 = x - let x = max(min(x, cw - 1), 0) - if not refresh or container.fromx <= x and x < container.fromx + container.width: - container.pos.cursorx = x - elif refresh and container.fromx > x: - if x2 < container.cursorx: - container.setFromX(x, false) - container.pos.cursorx = container.fromx - elif x > container.cursorx: - container.setFromX(max(x - container.width + 1, container.fromx), false) - container.pos.cursorx = x - elif x < container.cursorx: - container.setFromX(x, false) - container.pos.cursorx = x - if refresh: - container.sendCursorPosition() - if save: - container.pos.xend = container.cursorx - -proc restoreCursorX(container: Container) {.jsfunc.} = - let x = clamp(container.currentLineWidth() - 1, 0, container.xend) - container.setCursorX(x, false, false) - -proc setCursorY(container: Container, y: int, refresh = true) {.jsfunc.} = - let y = max(min(y, container.numLines - 1), 0) - if container.cursory == y: return - if y - container.fromy >= 0 and y - container.height < container.fromy: - container.pos.cursory = y - else: - if y > container.cursory: - container.setFromY(y - container.height + 1) - else: - container.setFromY(y) - container.pos.cursory = y - container.restoreCursorX() - if refresh: - container.sendCursorPosition() - -proc centerLine(container: Container) {.jsfunc.} = - container.setFromY(container.cursory - container.height div 2) - -proc centerColumn(container: Container) {.jsfunc.} = - container.setFromX(container.cursorx - container.width div 2) - -proc setCursorXY(container: Container, x, y: int, refresh = true) {.jsfunc.} = - let fy = container.fromy - container.setCursorY(y, refresh) - container.setCursorX(x, refresh) - if fy != container.fromy: - container.centerLine() - -proc cursorDown(container: Container, n = 1) {.jsfunc.} = - if container.select.open: - container.select.cursorDown() - else: - container.setCursorY(container.cursory + n) - -proc cursorUp(container: Container, n = 1) {.jsfunc.} = - if container.select.open: - container.select.cursorUp() - else: - container.setCursorY(container.cursory - n) - -proc cursorLeft(container: Container, n = 1) {.jsfunc.} = - if container.select.open: - container.select.cursorLeft() - else: - container.setCursorX(container.cursorFirstX() - n) - -proc cursorRight(container: Container, n = 1) {.jsfunc.} = - if container.select.open: - container.select.cursorRight() - else: - container.setCursorX(container.cursorLastX() + n) - -proc cursorLineBegin(container: Container) {.jsfunc.} = - container.setCursorX(0) - -proc cursorLineTextStart(container: Container) {.jsfunc.} = - if container.numLines == 0: return - var x = 0 - for r in container.currentLine.runes: - if not r.isWhitespace(): - break - x += r.twidth(x) - container.setCursorX(x) - -proc cursorLineEnd(container: Container) {.jsfunc.} = - container.setCursorX(container.currentLineWidth() - 1) - -proc cursorNextWord(container: Container) {.jsfunc.} = - if container.numLines == 0: return - var r: Rune - var b = container.currentCursorBytes() - var x = container.cursorx - while b < container.currentLine.len: - let pb = b - fastRuneAt(container.currentLine, b, r) - if r.breaksWord(): - b = pb - break - x += r.twidth(x) - - while b < container.currentLine.len: - let pb = b - fastRuneAt(container.currentLine, b, r) - if not r.breaksWord(): - b = pb - break - x += r.twidth(x) - - if b < container.currentLine.len: - container.setCursorX(x) - else: - if container.cursory < container.numLines - 1: - container.cursorDown() - container.cursorLineBegin() - else: - container.cursorLineEnd() - -proc cursorPrevWord(container: Container) {.jsfunc.} = - if container.numLines == 0: return - var b = container.currentCursorBytes() - var x = container.cursorx - if container.currentLine.len > 0: - b = min(b, container.currentLine.len - 1) - while b >= 0: - let (r, o) = lastRune(container.currentLine, b) - if r.breaksWord(): - break - b -= o - x -= r.twidth(x) - - while b >= 0: - let (r, o) = lastRune(container.currentLine, b) - if not r.breaksWord(): - break - b -= o - x -= r.twidth(x) - else: - b = -1 - - if b >= 0: - container.setCursorX(x) - else: - if container.cursory > 0: - container.cursorUp() - container.cursorLineEnd() - else: - container.cursorLineBegin() - -proc pageDown(container: Container) {.jsfunc.} = - container.setFromY(container.fromy + container.height) - container.setCursorY(container.cursory + container.height) - container.restoreCursorX() - -proc pageUp(container: Container) {.jsfunc.} = - container.setFromY(container.fromy - container.height) - container.setCursorY(container.cursory - container.height) - container.restoreCursorX() - -proc pageLeft(container: Container) {.jsfunc.} = - container.setFromX(container.fromx - container.width) - -proc pageRight(container: Container) {.jsfunc.} = - container.setFromX(container.fromx + container.width) - -proc halfPageUp(container: Container) {.jsfunc.} = - container.setFromY(container.fromy - container.height div 2 + 1) - container.setCursorY(container.cursory - container.height div 2 + 1) - container.restoreCursorX() - -proc halfPageDown(container: Container) {.jsfunc.} = - container.setFromY(container.fromy + container.height div 2 - 1) - container.setCursorY(container.cursory + container.height div 2 - 1) - container.restoreCursorX() - -proc cursorFirstLine(container: Container) {.jsfunc.} = - if container.select.open: - container.select.cursorFirstLine() - else: - container.setCursorY(0) - -proc cursorLastLine*(container: Container) {.jsfunc.} = - if container.select.open: - container.select.cursorLastLine() - else: - container.setCursorY(container.numLines - 1) - -proc cursorTop(container: Container) {.jsfunc.} = - container.setCursorY(container.fromy) - -proc cursorMiddle(container: Container) {.jsfunc.} = - container.setCursorY(container.fromy + (container.height - 2) div 2) - -proc cursorBottom(container: Container) {.jsfunc.} = - container.setCursorY(container.fromy + container.height - 1) - -proc cursorLeftEdge(container: Container) {.jsfunc.} = - container.setCursorX(container.fromx) - -proc cursorMiddleColumn(container: Container) {.jsfunc.} = - container.setCursorX(container.fromx + (container.width - 2) div 2) - -proc cursorRightEdge(container: Container) {.jsfunc.} = - container.setCursorX(container.fromx + container.width - 1) - -proc scrollDown(container: Container) {.jsfunc.} = - if container.fromy + container.height < container.numLines: - container.setFromY(container.fromy + 1) - if container.fromy > container.cursory: - container.cursorDown() - else: - container.cursorDown() - -proc scrollUp(container: Container) {.jsfunc.} = - if container.fromy > 0: - container.setFromY(container.fromy - 1) - if container.fromy + container.height <= container.cursory: - container.cursorUp() - else: - container.cursorUp() - -proc scrollRight(container: Container) {.jsfunc.} = - if container.fromx + container.width < container.maxScreenWidth(): - container.setFromX(container.fromx + 1) - -proc scrollLeft(container: Container) {.jsfunc.} = - if container.fromx > 0: - container.setFromX(container.fromx - 1) - -proc alert(container: Container, msg: string) = - container.triggerEvent(ContainerEvent(t: ALERT, msg: msg)) - -proc lineInfo(container: Container) {.jsfunc.} = - container.alert("line " & $(container.cursory + 1) & "/" & - $container.numLines & " (" & $container.atPercentOf() & "%) col " & - $(container.cursorx + 1) & "/" & $container.currentLineWidth & - " (byte " & $container.currentCursorBytes & ")") - -proc updateCursor(container: Container) = - if container.pos.setx > -1: - container.setCursorX(container.pos.setx, container.pos.setxrefresh) - if container.fromy > container.maxfromy: - container.setFromY(container.maxfromy) - if container.cursory >= container.numLines: - container.setCursorY(container.lastVisibleLine) - container.alert("Last line is #" & $container.numLines) - -proc gotoLine*[T: string|int](container: Container, s: T) = - when s is string: - if s == "": - redraw(container) - elif s[0] == '^': - container.cursorFirstLine() - elif s[0] == '$': - container.cursorLastLine() - else: - let i = parseUInt32(s) - if i.isSome and i.get > 0: - container.setCursorY(int(i.get - 1)) - else: - container.alert("First line is #1") # :) - else: - container.setCursorY(s - 1) - -proc pushCursorPos*(container: Container) = - if container.select.open: - container.select.pushCursorPos() - else: - container.bpos.add(container.pos) - -proc popCursorPos*(container: Container, nojump = false) = - if container.select.open: - container.select.popCursorPos(nojump) - else: - container.pos = container.bpos.pop() - if not nojump: - container.updateCursor() - container.sendCursorPosition() - container.needslines = true - -proc copyCursorPos*(container, c2: Container) = - container.startpos = some(c2.pos) - container.hasstart = true - -proc cursorNextLink*(container: Container) {.jsfunc.} = - container.iface - .findNextLink(container.cursorx, container.cursory) - .then(proc(res: tuple[x, y: int]) = - if res.x > -1 and res.y != -1: - container.setCursorXY(res.x, res.y)) - -proc cursorPrevLink*(container: Container) {.jsfunc.} = - container.iface - .findPrevLink(container.cursorx, container.cursory) - .then(proc(res: tuple[x, y: int]) = - if res.x > -1 and res.y != -1: - container.setCursorXY(res.x, res.y)) - -proc clearSearchHighlights*(container: Container) = - for i in countdown(container.highlights.high, 0): - if container.highlights[i].clear: - container.highlights.del(i) - -proc onMatch(container: Container, res: BufferMatch, refresh: bool) = - if res.success: - container.setCursorXY(res.x, res.y, refresh) - if container.hlon: - container.clearSearchHighlights() - let ex = res.x + res.str.twidth(res.x) - 1 - let hl = Highlight(x: res.x, y: res.y, endx: ex, endy: res.y, clear: true) - container.highlights.add(hl) - container.triggerEvent(UPDATE) - container.hlon = false - elif container.hlon: - container.clearSearchHighlights() - container.triggerEvent(UPDATE) - container.needslines = true - container.hlon = false - -proc cursorNextMatch*(container: Container, regex: Regex, wrap, refresh: bool): - EmptyPromise {.discardable.} = - if container.select.open: - container.select.cursorNextMatch(regex, wrap) - return newResolvedPromise() - else: - return container.iface - .findNextMatch(regex, container.cursorx, container.cursory, wrap) - .then(proc(res: BufferMatch) = - container.onMatch(res, refresh)) - -proc cursorPrevMatch*(container: Container, regex: Regex, wrap, refresh: bool): - EmptyPromise {.discardable.} = - if container.select.open: - container.select.cursorPrevMatch(regex, wrap) - return newResolvedPromise() - else: - return container.iface - .findPrevMatch(regex, container.cursorx, container.cursory, wrap) - .then(proc(res: BufferMatch) = - container.onMatch(res, refresh)) - -proc setLoadInfo(container: Container, msg: string) = - container.loadinfo = msg - container.triggerEvent(STATUS) - -#TODO TODO TODO this should be called with a timeout. -proc onload*(container: Container, res: LoadResult) = - if container.canceled: - container.setLoadInfo("") - #TODO we wouldn't need the then part if we had incremental rendering of - # HTML. - container.iface.cancel().then(proc(lines: int) = - container.setNumLines(lines) - container.needslines = true) - else: - if res.bytes == -1 or res.atend: - container.setLoadInfo("") - elif not res.atend: - container.setLoadInfo(convert_size(res.bytes) & " loaded") - if res.lines > container.numLines: - container.setNumLines(res.lines) - container.triggerEvent(STATUS) - container.needslines = true - if not res.atend: - discard container.iface.load().then(proc(res: LoadResult) = - container.onload(res)) - else: - container.iface.getTitle().then(proc(title: string): auto = - if title != "": - container.title = title - container.triggerEvent(TITLE) - return container.iface.render() - ).then(proc(lines: int): auto = - container.setNumLines(lines, true) - container.needslines = true - container.triggerEvent(LOADED) - if not container.hasstart and container.source.location.anchor != "": - return container.iface.gotoAnchor() - ).then(proc(res: tuple[x, y: int]) = - if res.x != -1 and res.y != -1: - container.setCursorXY(res.x, res.y)) - -proc load(container: Container) = - container.setLoadInfo("Connecting to " & container.location.host & "...") - container.iface.connect().then(proc(res: ConnectResult) = - let info = container.loadinfo - if not res.invalid: - container.code = res.code - if res.code == 0: - container.triggerEvent(SUCCESS) - # accept cookies - if res.cookies.len > 0 and container.config.cookiejar != nil: - container.config.cookiejar.add(res.cookies) - if res.referrerpolicy.isSome and container.config.referer_from: - container.config.referrerpolicy = res.referrerpolicy.get - container.setLoadInfo("Connected to " & $container.source.location & ". Downloading...") - if res.needsAuth: - container.triggerEvent(NEEDS_AUTH) - if res.redirect != nil: - container.triggerEvent(ContainerEvent(t: REDIRECT, request: res.redirect)) - container.source.charset = res.charset - if res.contentType == "application/octet-stream": - let contentType = guessContentType(container.location.pathname, - "application/octet-stream", container.config.mimeTypes) - if contentType != "application/octet-stream": - container.iface.setContentType(contentType) - container.source.contenttype = some(contentType) - elif res.contentType != "": - container.source.contenttype = some(res.contentType) - container.triggerEvent(CHECK_MAILCAP) - else: - container.setLoadInfo("") - container.triggerEvent(FAIL) - else: - container.setLoadInfo(info) - ) - -proc startload*(container: Container) = - container.iface.load() - .then(proc(res: tuple[atend: bool, lines, bytes: int]) = - container.onload(res)) - -proc connect2*(container: Container): EmptyPromise = - return container.iface.connect2() - -proc redirectToFd*(container: Container, fdin: FileHandle, wait: bool): - EmptyPromise = - return container.iface.redirectToFd(fdin, wait) - -proc readFromFd*(container: Container, fdout: FileHandle, ishtml: bool): - EmptyPromise = - return container.iface.readFromFd(fdout, ishtml) - -proc quit*(container: Container) = - container.triggerEvent(QUIT) - -proc cancel*(container: Container) {.jsfunc.} = - if container.select.open: - container.select.cancel() - else: - container.canceled = true - container.alert("Canceled loading") - -proc findAnchor*(container: Container, anchor: string) = - container.iface.findAnchor(anchor).then(proc(found: bool) = - if found: - container.triggerEvent(ContainerEvent(t: ANCHOR, anchor: anchor)) - else: - container.triggerEvent(NO_ANCHOR)) - -proc readCanceled*(container: Container) = - container.iface.readCanceled().then(proc(repaint: bool) = - if repaint: - container.needslines = true) - -proc readSuccess*(container: Container, s: string) = - container.iface.readSuccess(s).then(proc(res: ReadSuccessResult) = - if res.repaint: - container.needslines = true - if res.open.isSome: - container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get))) - -proc reshape(container: Container): EmptyPromise {.discardable, jsfunc.} = - return container.iface.render().then(proc(lines: int): auto = - container.setNumLines(lines) - return container.requestLines()) - -proc pipeBuffer*(container, pipeTo: Container) = - container.iface.getSource().then(proc() = - pipeTo.load() #TODO do not load if pipeTo is killed first? - ) - -proc onclick(container: Container, res: ClickResult) - -proc displaySelect(container: Container, selectResult: SelectResult) = - let submitSelect = proc(selected: seq[int]) = - container.iface.select(selected).then(proc(res: ClickResult) = - container.onclick(res)) - container.select.initSelect(selectResult, container.acursorx, - container.acursory, container.height, submitSelect) - container.triggerEvent(UPDATE) - -proc onclick(container: Container, res: ClickResult) = - if res.repaint: - container.needslines = true - if res.open.isSome: - container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get)) - if res.select.isSome: - container.displaySelect(res.select.get) - if res.readline.isSome: - let rl = res.readline.get - let event = if rl.area: - ContainerEvent( - t: READ_AREA, - tvalue: rl.value - ) - else: - ContainerEvent( - t: READ_LINE, - prompt: rl.prompt, - value: rl.value, - password: rl.hide - ) - container.triggerEvent(event) - -proc click(container: Container) {.jsfunc.} = - if container.select.open: - container.select.click() - else: - container.iface.click(container.cursorx, container.cursory) - .then(proc(res: ClickResult) = container.onclick(res)) - -proc windowChange*(container: Container, attrs: WindowAttributes) = - if attrs.width != container.width or attrs.height - 1 != container.height: - container.width = attrs.width - container.height = attrs.height - 1 - container.iface.windowChange(attrs).then(proc(): auto = - container.needslines = true - return container.iface.render() - ).then(proc(lines: int) = - if lines != container.numLines: - container.setNumLines(lines, true) - container.needslines = true) - -proc peek(container: Container) {.jsfunc.} = - container.alert($container.source.location) - -proc clearHover*(container: Container) = - container.lastpeek = low(HoverType) - -proc peekCursor(container: Container) {.jsfunc.} = - var p = container.lastpeek - while true: - if container.hovertext[p] != "": - container.alert($p & ": " & container.hovertext[p]) - break - if p < high(HoverType): - inc p - else: - p = low(HoverType) - if p == container.lastpeek: break - if container.lastpeek < high(HoverType): - inc container.lastpeek - else: - container.lastpeek = low(HoverType) - -proc handleCommand(container: Container) = - var packetid, len: int - container.iface.stream.sread(len) - container.iface.stream.sread(packetid) - container.iface.resolve(packetid, len - slen(packetid)) - -proc setStream*(container: Container, stream: Stream) = - container.iface = newBufferInterface(stream) - if container.source.t == LOAD_PIPE: - container.iface.passFd(container.source.fd).then(proc() = - discard close(container.source.fd)) - stream.flush() - container.load() - -proc onreadline(container: Container, w: Slice[int], handle: (proc(line: SimpleFlexibleLine)), res: GetLinesResult) = - for line in res.lines: - handle(line) - if res.numLines > w.b + 1: - var w = w - w.a += 24 - w.b += 24 - container.iface.getLines(w).then(proc(res: GetLinesResult) = - container.onreadline(w, handle, res)) - else: - container.setNumLines(res.numLines, true) - -# Synchronously read all lines in the buffer. -proc readLines*(container: Container, handle: (proc(line: SimpleFlexibleLine))) = - if container.code == 0: - # load succeded - let w = 0 .. 23 - container.iface.getLines(w).then(proc(res: GetLinesResult) = - container.onreadline(w, handle, res)) - while container.iface.hasPromises: - # fulfill all promises - container.handleCommand() - -proc drawLines*(container: Container, display: var FixedGrid, - hlcolor: CellColor) = - var r: Rune - var by = 0 - let endy = min(container.fromy + display.height, container.numLines) - for line in container.ilines(container.fromy ..< endy): - var w = 0 # width of the row so far - var i = 0 # byte in line.str - # Skip cells till fromx. - while w < container.fromx and i < line.str.len: - fastRuneAt(line.str, i, r) - w += r.twidth(w) - let dls = by * display.width # starting position of row in display - # Fill in the gap in case we skipped more cells than fromx mandates (i.e. - # we encountered a double-width character.) - var k = 0 - if w > container.fromx: - while k < w - container.fromx: - display[dls + k].str &= ' ' - inc k - var cf = line.findFormat(w) - var nf = line.findNextFormat(w) - let startw = w # save this for later - # Now fill in the visible part of the row. - while i < line.str.len: - let pw = w - fastRuneAt(line.str, i, r) - let rw = r.twidth(w) - w += rw - if w > container.fromx + display.width: - break # die on exceeding the width limit - if nf.pos != -1 and nf.pos <= pw: - cf = nf - nf = line.findNextFormat(pw) - if cf.pos != -1: - display[dls + k].format = cf.format - if r == Rune('\t'): - # Needs to be replaced with spaces, otherwise bgcolor isn't displayed. - let tk = k + rw - while k < tk: - display[dls + k].str &= ' ' - inc k - else: - display[dls + k].str &= r - k += rw - # Finally, override cell formatting for highlighted cells. - let hls = container.findHighlights(container.fromy + by) - let aw = container.width - (startw - container.fromx) # actual width - for hl in hls: - let area = hl.colorArea(container.fromy + by, startw .. startw + aw) - for i in area: - var hlformat = display[dls + i - startw].format - hlformat.bgcolor = hlcolor - display[dls + i - startw].format = hlformat - inc by - -proc handleEvent*(container: Container) = - container.handleCommand() - if container.needslines: - container.requestLines() - container.needslines = false - -proc addContainerModule*(ctx: JSContext) = - ctx.registerType(Container, name = "Buffer") diff --git a/src/buffer/select.nim b/src/buffer/select.nim deleted file mode 100644 index f7afa4d9..00000000 --- a/src/buffer/select.nim +++ /dev/null @@ -1,306 +0,0 @@ -import unicode - -import buffer/buffer -import buffer/cell -import js/regex -import utils/twtstr - -type - SubmitSelect* = proc(selected: seq[int]) - CloseSelect* = proc() - - Select* = object - open*: bool - options: seq[string] - multiple: bool - # old selection - oselected*: seq[int] - # new selection - selected*: seq[int] - # cursor distance from y - cursor: int - # widest option - maxw: int - # maximum height on screen (yes the naming is dumb) - maxh: int - # first index to display - si: int - # location on screen - x: int - y: int - redraw*: bool - submitFun: SubmitSelect - bpos: seq[int] - -proc windowChange*(select: var Select, height: int) = - select.maxh = height - 2 - if select.y + select.options.len >= select.maxh: - select.y = height - select.options.len - if select.y < 0: - select.si = -select.y - select.y = 0 - if select.selected.len > 0: - let i = select.selected[0] - if select.si > i: - select.si = i - elif select.si + select.maxh < i: - select.si = max(i - select.maxh, 0) - select.redraw = true - -proc initSelect*(select: var Select, selectResult: SelectResult, - x, y, height: int, submitFun: SubmitSelect) = - select.open = true - select.multiple = selectResult.multiple - select.options = selectResult.options - select.oselected = selectResult.selected - select.selected = selectResult.selected - select.submitFun = submitFun - for opt in select.options.mitems: - opt.mnormalize() - select.maxw = max(select.maxw, opt.width()) - select.x = x - select.y = y - select.windowChange(height) - -# index of option currently under cursor -func hover(select: Select): int = - return select.cursor + select.si - -func dispheight(select: Select): int = - return select.maxh - select.y - -proc `hover=`(select: var Select, i: int) = - let i = clamp(i, 0, select.options.high) - if i >= select.si + select.dispheight: - select.si = i - select.dispheight + 1 - select.cursor = select.dispheight - 1 - elif i < select.si: - select.si = i - select.cursor = 0 - else: - select.cursor = i - select.si - -proc cursorDown*(select: var Select) = - if select.hover < select.options.high and - select.cursor + select.y < select.maxh - 1: - inc select.cursor - select.redraw = true - elif select.si < select.options.len - select.maxh: - inc select.si - select.redraw = true - -proc cursorUp*(select: var Select) = - if select.cursor > 0: - dec select.cursor - select.redraw = true - elif select.si > 0: - dec select.si - select.redraw = true - elif select.multiple and select.cursor > -1: - select.cursor = -1 - -proc close(select: var Select) = - select = Select() - -proc cancel*(select: var Select) = - select.submitFun(select.oselected) - select.close() - -proc submit(select: var Select) = - select.submitFun(select.selected) - select.close() - -proc click*(select: var Select) = - if not select.multiple: - select.selected = @[select.hover] - select.submit() - elif select.cursor == -1: - select.submit() - else: - var k = select.selected.len - let i = select.hover - for j in 0 ..< select.selected.len: - if select.selected[j] >= i: - k = j - break - if k < select.selected.len and select.selected[k] == i: - select.selected.delete(k) - else: - select.selected.insert(i, k) - select.redraw = true - -proc cursorLeft*(select: var Select) = - select.submit() - -proc cursorRight*(select: var Select) = - select.click() - -proc getCursorX*(select: var Select): int = - if select.cursor == -1: - return select.x - return select.x + 1 - -proc getCursorY*(select: var Select): int = - return select.y + 1 + select.cursor - -proc cursorFirstLine*(select: var Select) = - if select.cursor != 0 or select.si != 0: - select.cursor = 0 - select.si = 0 - select.redraw = true - -proc cursorLastLine*(select: var Select) = - if select.hover < select.options.len: - select.cursor = select.dispheight - 1 - select.si = max(select.options.len - select.maxh, 0) - select.redraw = true - -proc cursorNextMatch*(select: var Select, regex: Regex, wrap: bool) = - var j = -1 - for i in select.hover + 1 ..< select.options.len: - if regex.exec(select.options[i]).success: - j = i - break - if j != -1: - select.hover = j - select.redraw = true - elif wrap: - for i in 0 ..< select.hover: - if regex.exec(select.options[i]).success: - j = i - break - if j != -1: - select.hover = j - select.redraw = true - -proc cursorPrevMatch*(select: var Select, regex: Regex, wrap: bool) = - var j = -1 - for i in countdown(select.hover - 1, 0): - if regex.exec(select.options[i]).success: - j = i - break - if j != -1: - select.hover = j - select.redraw = true - elif wrap: - for i in countdown(select.options.high, select.hover): - if regex.exec(select.options[i]).success: - j = i - break - if j != -1: - select.hover = j - select.redraw = true - -proc pushCursorPos*(select: var Select) = - select.bpos.add(select.hover) - -proc popCursorPos*(select: var Select, nojump = false) = - select.hover = select.bpos.pop() - if not nojump: - select.redraw = true - -const HorizontalBar = $Rune(0x2500) -const VerticalBar = $Rune(0x2502) -const CornerTopLeft = $Rune(0x250C) -const CornerTopRight = $Rune(0x2510) -const CornerBottomLeft = $Rune(0x2514) -const CornerBottomRight = $Rune(0x2518) - -proc drawBorders(display: var FixedGrid, sx, ex, sy, ey: int, - upmore, downmore: bool) = - for y in sy .. ey: - var x = 0 - while x < sx: - if display[y * display.width + x].str == "": - display[y * display.width + x].str = " " - inc x - else: - #x = display[y * display.width + x].str.twidth(x) - inc x - # Draw corners. - let tl = if upmore: VerticalBar else: CornerTopLeft - let tr = if upmore: VerticalBar else: CornerTopRight - let bl = if downmore: VerticalBar else: CornerBottomLeft - let br = if downmore: VerticalBar else: CornerBottomRight - const fmt = newFormat() - display[sy * display.width + sx].str = tl - display[sy * display.width + ex].str = tr - display[ey * display.width + sx].str = bl - display[ey * display.width + ex].str = br - display[sy * display.width + sx].format = fmt - display[sy * display.width + ex].format = fmt - display[ey * display.width + sx].format = fmt - display[ey * display.width + ex].format = fmt - # Draw top, bottom borders. - let ups = if upmore: " " else: HorizontalBar - let downs = if downmore: " " else: HorizontalBar - for x in sx + 1 .. ex - 1: - display[sy * display.width + x].str = ups - display[ey * display.width + x].str = downs - display[sy * display.width + x].format = fmt - display[ey * display.width + x].format = fmt - if upmore: - display[sy * display.width + sx + (ex - sx) div 2].str = ":" - if downmore: - display[ey * display.width + sx + (ex - sx) div 2].str = ":" - # Draw left, right borders. - for y in sy + 1 .. ey - 1: - display[y * display.width + sx].str = VerticalBar - display[y * display.width + ex].str = VerticalBar - display[y * display.width + sx].format = fmt - display[y * display.width + ex].format = fmt - -proc drawSelect*(select: Select, display: var FixedGrid) = - if display.width < 2 or display.height < 2: - return # border does not fit... - # Max width, height with one row/column on the sides. - let mw = display.width - 2 - let mh = display.height - 2 - var sy = select.y - let si = select.si - var ey = min(sy + select.options.len, mh) + 1 - var sx = select.x - if sx + select.maxw >= mw: - sx = display.width - select.maxw - if sx < 0: - # This means the widest option is wider than the available screen. - # w3m simply cuts off the part that doesn't fit, and we do that too, - # but I feel like this may not be the best solution. - sx = 0 - var ex = min(sx + select.maxw, mw) + 1 - let upmore = select.si > 0 - let downmore = select.si + mh < select.options.len - drawBorders(display, sx, ex, sy, ey, upmore, downmore) - if select.multiple and not upmore: - display[sy * display.width + sx].str = "X" - # move inside border - inc sy - inc sx - var r: Rune - var k = 0 - var format = newFormat() - while k < select.selected.len and select.selected[k] < si: - inc k - for y in sy ..< ey: - let i = y - sy + si - var j = 0 - var x = sx - let dls = y * display.width - if k < select.selected.len and select.selected[k] == i: - format.reverse = true - inc k - else: - format.reverse = false - while j < select.options[i].len: - fastRuneAt(select.options[i], j, r) - let rw = r.twidth(x) - let ox = x - x += rw - if x > ex: - break - display[dls + ox].str = $r - display[dls + ox].format = format - while x < ex: - display[dls + x].str = " " - display[dls + x].format = format - inc x |