diff options
-rw-r--r-- | bonus/w3m.toml | 77 | ||||
-rw-r--r-- | doc/config.md | 22 | ||||
-rw-r--r-- | res/config.toml | 6 | ||||
-rw-r--r-- | src/bindings/quickjs.nim | 4 | ||||
-rw-r--r-- | src/buffer/container.nim | 125 | ||||
-rw-r--r-- | src/config/config.nim | 18 | ||||
-rw-r--r-- | src/display/client.nim | 15 | ||||
-rw-r--r-- | src/display/pager.nim | 110 | ||||
-rw-r--r-- | src/js/javascript.nim | 36 |
9 files changed, 264 insertions, 149 deletions
diff --git a/bonus/w3m.toml b/bonus/w3m.toml new file mode 100644 index 00000000..9acc887f --- /dev/null +++ b/bonus/w3m.toml @@ -0,0 +1,77 @@ +# w3m-like keybindings for Chawan. +# Copy-paste this into your config file, or just include it (place it in +# ~/.config/chawan/w3m.toml, then at the beginning of +# ~/.config/chawan/chawan.toml, include = "w3m.toml".) + +[page] +# Page/cursor movement +' ' = 'pager.pageDown()' +C-v = 'pager.pageDown()' +b = 'pager.pageUp()' +M-v = 'pager.pageUp()' +l = 'pager.cursorRight()' +h = 'pager.cursorLeft()' +j = 'pager.cursorDown()' +k = 'pager.cursorUp()' +C-f = 'pager.cursorRight()' +C-b = 'pager.cursorLeft()' +C-n = 'pager.cursorDown()' +C-p = 'pager.cursorUp()' +J = 'pager.scrollUp()' +K = 'pager.scrollDown()' +'^' = 'pager.cursorLineBegin()' +C-a = 'pager.cursorLineBegin()' +'$' = 'pager.cursorLineEnd()' +C-e = 'pager.cursorLineEnd()' +w = 'pager.cursorNextWord()' +W = 'pager.cursorPrevWord()' +'<' = 'pager.pageLeft()' +'>' = 'pager.pageRight()' +'.' = 'pager.scrollLeft()' +',' = 'pager.scrollRight()' +g = 'pager.cursorFirstLine()' +'M-<' = 'pager.cursorFirstLine()' +G = 'pager.cursorLastLine()' +'M->' = 'pager.cursorLastLine()' +M-g = 'pager.gotoLine()' +Z = 'pager.centerColumn()' +z = 'pager.centerLine()' +C-i = 'pager.cursorNextLink()' +C-u = 'pager.cursorPrevLink()' +M-C-i = 'pager.cursorPrevLink()' +'[' = 'pager.cursorFirstLine();pager.cursorLineBegin();pager.cursorNextLink()' +']' = 'pager.cursorLastLine();pager.cursorLineEnd();pager.cursorPrevLink()' +# Hyperlink selection +C-j = 'pager.click()' +C-m = 'pager.click()' +c = 'pager.peek()' +u = 'pager.peekCursor()' +#TODO download, etc +# File and URL-related actions +U = 'pager.load()' +V = 'pager.load()' #TODO file only +#TODO exec shell +# Buffer operations +B = 'pager.discardBuffer()' +v = 'pager.toggleSource()' +#TODO edit +C-l = 'pager.redraw()' +R = 'pager.reload()' +#TODO save, save source, view in editor +#TODO buffer selection mode +#TODO mark +# Search +'/' = 'pager.searchForward()' +C-s = 'pager.searchForward()' +'?' = 'pager.searchBackward()' +C-r = 'pager.searchBackward()' +n = 'pager.searchNext()' +N = 'pager.searchPrev()' +C-w = 'config.searchwrap = !config.searchwrap; pager.alert("Wrap search " + (config.searchwrap ? "on" : "off"))' +# Misc +#TODO shell out, help file, options, cookies +C-c = 'pager.cancel()' +q = 'pager.quit()' #TODO confirm +Q = 'pager.quit()' + +# w3m line editing is equivalent to Chawan's defaults. diff --git a/doc/config.md b/doc/config.md index 65ea2fb0..5aec7a5d 100644 --- a/doc/config.md +++ b/doc/config.md @@ -559,7 +559,7 @@ open the current buffer's contents as HTML.</td> <tr> <td>`pager.cursorMiddle()`</td> -<td>Move to the middle of the screen. (Equivalent to M in vi.)</td> +<td>Move to the line in the middle of the screen. (Equivalent to M in vi.)</td> </tr> <tr> @@ -573,6 +573,26 @@ open the current buffer's contents as HTML.</td> </tr> <tr> +<td>`pager.cursorLeftEdge()`</td> +<td>Move to the first column on the screen.</td> +</tr> + +<tr> +<td>`pager.cursorMiddleColumn()`</td> +<td>Move to the column in the middle of the screen.</td> +</tr> + +<tr> +<td>`pager.cursorRightEdge()`</td> +<td>Move to the last column on the screen.</td> +</tr> + +<tr> +<td>`pager.centerColumn()`</td> +<td>Center screen around the current column.</td> +</tr> + +<tr> <td>`pager.lineInfo()`</td> <td>Display information about the current line.</td> </tr> diff --git a/res/config.toml b/res/config.toml index dd15aebc..27589198 100644 --- a/res/config.toml +++ b/res/config.toml @@ -3,6 +3,9 @@ visual-home = "about:chawan" run-script = "" headless = false +[search] +wrap = true + [external] tmpdir = "/tmp/cha" editor = "vi %s +%d" @@ -46,7 +49,7 @@ H = 'pager.cursorTop()' M = 'pager.cursorMiddle()' L = 'pager.cursorBottom()' ';' = 'pager.cursorLeftEdge()' -'+' = 'pager.cursorVertMiddle()' +'+' = 'pager.cursorMiddleColumn()' '@' = 'pager.cursorRightEdge()' C-d = 'pager.halfPageDown()' C-u = 'pager.halfPageUp()' @@ -93,6 +96,7 @@ n = 'pager.searchNext()' N = 'pager.searchPrev()' c = 'pager.peek()' u = 'pager.peekCursor()' +C-w = 'config.searchwrap = !config.searchwrap; pager.alert("Wrap search " + (config.searchwrap ? "on" : "off"))' [line] C-m = 'line.submit()' diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim index cfef9693..a606ede7 100644 --- a/src/bindings/quickjs.nim +++ b/src/bindings/quickjs.nim @@ -85,7 +85,8 @@ else: type JSRuntime* = ptr object JSContext* = ptr object - JSCFunction* = proc (ctx: JSContext, this_val: JSValue, argc: int, argv: ptr JSValue): JSValue {.cdecl.} + JSCFunction* = proc (ctx: JSContext, this_val: JSValue, argc: cint, argv: ptr JSValue): JSValue {.cdecl.} + JSCFunctionData* = proc (ctx: JSContext, this_val: JSValue, argc: cint, argv: ptr JSValue, magic: cint, func_data: ptr JSValue): JSValue {.cdecl.} JSGetterFunction* = proc(ctx: JSContext, this_val: JSValue): JSValue {.cdecl.} JSSetterFunction* = proc(ctx: JSContext, this_val: JSValue, val: JSValue): JSValue {.cdecl.} JSGetterMagicFunction* = proc(ctx: JSContext, this_val: JSValue, magic: cint): JSValue {.cdecl.} @@ -339,6 +340,7 @@ proc JS_FreeAtom*(ctx: JSContext, atom: JSAtom) proc JS_FreeAtomRT*(rt: JSRuntime, atom: JSAtom) proc JS_NewCFunction2*(ctx: JSContext, cfunc: JSCFunction, name: cstring, length: cint, proto: JSCFunctionEnum, magic: cint): JSValue +proc JS_NewCFunctionData*(ctx: JSContext, cfunc: JSCFunctionData, length: cint, magic: cint, data_len: cint, data: ptr JSValue): JSValue proc JS_NewCFunction*(ctx: JSContext, cfunc: JSCFunction, name: cstring, length: cint): JSValue proc JS_NewString*(ctx: JSContext, str: cstring): JSValue diff --git a/src/buffer/container.nim b/src/buffer/container.nim index f85ed39e..e6a65550 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -2,6 +2,7 @@ import deques import options import streams import strformat +import strutils import unicode when defined(posix): @@ -68,11 +69,11 @@ type config*: BufferConfig iface*: BufferInterface attrs: WindowAttributes - width*: int - height*: int - contenttype*: Option[string] - title*: string - hovertext*: array[HoverType, string] + width* {.jsget.}: int + height* {.jsget.}: int + contenttype* {.jsget.}: Option[string] + title: string + hovertext: array[HoverType, string] lastpeek: HoverType source*: BufferSource pos: CursorPosition @@ -134,8 +135,8 @@ 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 xend(container: Container): int {.inline.} = container.pos.xend +func lastVisibleLine(container: Container): int = min(container.fromy + container.height, container.numLines) - 1 func acursorx*(container: Container): int = max(0, container.cursorx - container.fromx) @@ -143,7 +144,7 @@ func acursorx*(container: Container): int = func acursory*(container: Container): int = container.cursory - container.fromy -func currentLine*(container: Container): string = +func currentLine(container: Container): string = return container.getLine(container.cursory).str func cursorBytes(container: Container, y: int, cc = container.cursorx): int = @@ -159,7 +160,7 @@ func cursorBytes(container: Container, y: int, cc = container.cursorx): int = func currentCursorBytes(container: Container, cc = container.cursorx): int = return container.cursorBytes(container.cursory, cc) -func prevWidth*(container: Container): int = +func prevWidth(container: Container): int = if container.numLines == 0: return 0 let line = container.currentLine if line.len == 0: return 0 @@ -175,7 +176,7 @@ func prevWidth*(container: Container): int = w += r.width() return pr.width() -func currentWidth*(container: Container): int = +func currentWidth(container: Container): int = if container.numLines == 0: return 0 let line = container.currentLine if line.len == 0: return 0 @@ -193,7 +194,7 @@ 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 = +func getTitle*(container: Container): string {.jsfunc.} = if container.title != "": return container.title return container.source.location.serialize(excludepassword = true) @@ -210,9 +211,6 @@ func atPercentOf*(container: Container): int = if container.numLines == 0: return 100 return (100 * (container.cursory + 1)) div container.numLines -func lineInfo*(container: Container): string = - fmt"line {container.cursory + 1}/{container.numLines} ({container.atPercentOf}%) col {container.cursorx + 1}/{container.currentLineWidth} (byte {container.currentCursorBytes})" - func lineWindow(container: Container): Slice[int] = if container.numLines == 0: # not loaded return 0..container.height * 5 @@ -293,7 +291,7 @@ proc requestLines*(container: Container, w = container.lineWindow): auto {.disca 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.} = +proc redraw(container: Container) {.jsfunc.} = container.triggerEvent(ContainerEvent(t: UPDATE, force: true)) proc sendCursorPosition(container: Container) = @@ -307,13 +305,13 @@ proc sendCursorPosition(container: Container) = if res.repaint: container.needslines = true) -proc setFromY*(container: Container, y: int) {.jsfunc.} = +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.} = +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: @@ -322,11 +320,11 @@ proc setFromX*(container: Container, x: int, refresh = true) {.jsfunc.} = container.sendCursorPosition() container.triggerEvent(UPDATE) -proc setFromXY*(container: Container, x, y: int) {.jsfunc.} = +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.} = +proc setCursorX(container: Container, x: int, refresh = true, save = true) {.jsfunc.} = if not container.lineLoaded(container.cursory): container.pos.setx = x return @@ -354,7 +352,7 @@ proc setCursorX*(container: Container, x: int, refresh = true, save = true) {.js proc restoreCursorX(container: Container) {.jsfunc.} = container.setCursorX(max(min(container.currentLineWidth() - 1, container.xend), 0), false, false) -proc setCursorY*(container: Container, y: int) {.jsfunc.} = +proc setCursorY(container: Container, y: int) {.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: @@ -368,38 +366,41 @@ proc setCursorY*(container: Container, y: int) {.jsfunc.} = container.restoreCursorX() container.sendCursorPosition() -proc centerLine*(container: Container) {.jsfunc.} = +proc centerLine(container: Container) {.jsfunc.} = container.setFromY(container.cursory - container.height div 2) -proc setCursorXY*(container: Container, x, y: int) {.jsfunc.} = +proc centerColumn(container: Container) {.jsfunc.} = + container.setFromX(container.cursorx - container.width div 2) + +proc setCursorXY(container: Container, x, y: int) {.jsfunc.} = let fy = container.fromy container.setCursorY(y) container.setCursorX(x) if fy != container.fromy: container.centerLine() -proc cursorDown*(container: Container) {.jsfunc.} = +proc cursorDown(container: Container) {.jsfunc.} = container.setCursorY(container.cursory + 1) -proc cursorUp*(container: Container) {.jsfunc.} = +proc cursorUp(container: Container) {.jsfunc.} = container.setCursorY(container.cursory - 1) -proc cursorLeft*(container: Container) {.jsfunc.} = +proc cursorLeft(container: Container) {.jsfunc.} = var w = container.prevWidth() if w == 0: w = 1 container.setCursorX(container.cursorx - w) -proc cursorRight*(container: Container) {.jsfunc.} = +proc cursorRight(container: Container) {.jsfunc.} = container.setCursorX(container.cursorx + container.currentWidth()) -proc cursorLineBegin*(container: Container) {.jsfunc.} = +proc cursorLineBegin(container: Container) {.jsfunc.} = container.setCursorX(0) -proc cursorLineEnd*(container: Container) {.jsfunc.} = +proc cursorLineEnd(container: Container) {.jsfunc.} = container.setCursorX(container.currentLineWidth() - 1) -proc cursorNextWord*(container: Container) {.jsfunc.} = +proc cursorNextWord(container: Container) {.jsfunc.} = if container.numLines == 0: return var r: Rune var b = container.currentCursorBytes() @@ -429,7 +430,7 @@ proc cursorNextWord*(container: Container) {.jsfunc.} = else: container.cursorLineEnd() -proc cursorPrevWord*(container: Container) {.jsfunc.} = +proc cursorPrevWord(container: Container) {.jsfunc.} = if container.numLines == 0: return var b = container.currentCursorBytes() var x = container.cursorx @@ -460,57 +461,57 @@ proc cursorPrevWord*(container: Container) {.jsfunc.} = else: container.cursorLineBegin() -proc pageDown*(container: Container) {.jsfunc.} = +proc pageDown(container: Container) {.jsfunc.} = container.setFromY(container.fromy + container.height) container.setCursorY(container.cursory + container.height) container.restoreCursorX() -proc pageUp*(container: Container) {.jsfunc.} = +proc pageUp(container: Container) {.jsfunc.} = container.setFromY(container.fromy - container.height) container.setCursorY(container.cursory - container.height) container.restoreCursorX() -proc pageLeft*(container: Container) {.jsfunc.} = +proc pageLeft(container: Container) {.jsfunc.} = container.setFromX(container.fromx - container.width) -proc pageRight*(container: Container) {.jsfunc.} = +proc pageRight(container: Container) {.jsfunc.} = container.setFromX(container.fromx + container.width) -proc halfPageUp*(container: Container) {.jsfunc.} = +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.} = +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.} = +proc cursorFirstLine(container: Container) {.jsfunc.} = container.setCursorY(0) proc cursorLastLine*(container: Container) {.jsfunc.} = container.setCursorY(container.numLines - 1) -proc cursorTop*(container: Container) {.jsfunc.} = +proc cursorTop(container: Container) {.jsfunc.} = container.setCursorY(container.fromy) -proc cursorMiddle*(container: Container) {.jsfunc.} = +proc cursorMiddle(container: Container) {.jsfunc.} = container.setCursorY(container.fromy + (container.height - 2) div 2) -proc cursorBottom*(container: Container) {.jsfunc.} = +proc cursorBottom(container: Container) {.jsfunc.} = container.setCursorY(container.fromy + container.height - 1) -proc cursorLeftEdge*(container: Container) {.jsfunc.} = +proc cursorLeftEdge(container: Container) {.jsfunc.} = container.setCursorX(container.fromx) -proc cursorVertMiddle*(container: Container) {.jsfunc.} = +proc cursorMiddleColumn(container: Container) {.jsfunc.} = container.setCursorX(container.fromx + (container.width - 2) div 2) -proc cursorRightEdge*(container: Container) {.jsfunc.} = +proc cursorRightEdge(container: Container) {.jsfunc.} = container.setCursorX(container.fromx + container.width - 1) -proc scrollDown*(container: Container) {.jsfunc.} = +proc scrollDown(container: Container) {.jsfunc.} = if container.fromy + container.height < container.numLines: container.setFromY(container.fromy + 1) if container.fromy > container.cursory: @@ -518,7 +519,7 @@ proc scrollDown*(container: Container) {.jsfunc.} = else: container.cursorDown() -proc scrollUp*(container: Container) {.jsfunc.} = +proc scrollUp(container: Container) {.jsfunc.} = if container.fromy > 0: container.setFromY(container.fromy - 1) if container.fromy + container.height <= container.cursory: @@ -526,11 +527,11 @@ proc scrollUp*(container: Container) {.jsfunc.} = else: container.cursorUp() -proc scrollRight*(container: Container) {.jsfunc.} = +proc scrollRight(container: Container) {.jsfunc.} = if container.fromx + container.width < container.maxScreenWidth(): container.setFromX(container.fromx + 1) -proc scrollLeft*(container: Container) {.jsfunc.} = +proc scrollLeft(container: Container) {.jsfunc.} = if container.fromx > 0: container.setFromX(container.fromx - 1) if container.cursorx < container.fromx: @@ -539,6 +540,9 @@ proc scrollLeft*(container: Container) {.jsfunc.} = proc alert(container: Container, msg: string) = container.triggerEvent(ContainerEvent(t: ALERT, msg: msg)) +proc lineInfo(container: Container) {.jsfunc.} = + container.alert(fmt"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) @@ -548,6 +552,23 @@ proc updateCursor(container: Container) = 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: + try: + let i = parseInt(s) + container.setCursorY(i) + except ValueError: + container.alert("First line is #1") # :) + else: + container.setCursorY(s) + proc pushCursorPos*(container: Container) = container.bpos.add(container.pos) @@ -654,7 +675,7 @@ proc onload(container: Container, res: LoadResult) = if res.x != -1 and res.y != -1: container.setCursorXY(res.x, res.y)) -proc load*(container: Container) = +proc load(container: Container) = container.setLoadInfo("Connecting to " & $container.source.location & "...") container.iface.connect().then(proc(res: ConnectResult): auto = let info = container.loadinfo @@ -705,7 +726,7 @@ proc readSuccess*(container: Container, s: string) = if res.open.isSome: container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get))) -proc reshape*(container: Container, noreq = false) {.jsfunc.} = +proc reshape(container: Container, noreq = false) {.jsfunc.} = container.iface.render().then(proc(lines: int) = container.setNumLines(lines)) if not noreq: @@ -725,7 +746,7 @@ proc dupeBuffer*(dispatcher: Dispatcher, container: Container, config: Config, l container.pipeto = nil) return container.pipeto -proc click*(container: Container) {.jsfunc.} = +proc click(container: Container) {.jsfunc.} = container.iface.click(container.cursorx, container.cursory).then(proc(res: ClickResult) = if res.repaint: container.needslines = true @@ -761,13 +782,13 @@ proc windowChange*(container: Container, attrs: WindowAttributes) = container.setNumLines(lines, true) container.needslines = true) -proc peek*(container: Container) {.jsfunc.} = +proc peek(container: Container) {.jsfunc.} = container.alert($container.source.location) proc clearHover*(container: Container) = container.lastpeek = low(HoverType) -proc peekCursor*(container: Container) {.jsfunc.} = +proc peekCursor(container: Container) {.jsfunc.} = var p = container.lastpeek while true: if container.hovertext[p] != "": diff --git a/src/config/config.nim b/src/config/config.nim index 7ed1441a..d3cad0b1 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -53,12 +53,13 @@ type Config* = ref ConfigObj ConfigObj* = object + searchwrap* {.jsget, jsset.}: bool maxredirect*: int - prependhttps*: bool + prependhttps* {.jsget, jsset.}: bool termreload*: bool nmap*: ActionMap lemap*: ActionMap - stylesheet*: string + stylesheet* {.jsget, jsset.}: string startup*: string ambiguous_double*: bool hlcolor*: RGBAColor @@ -254,15 +255,20 @@ proc parseConfig(config: Config, dir: string, t: TomlValue) = of "include": if v.vt == VALUE_STRING: when nimvm: - config.loadConfig(v.s) + config.parseConfig(dir, staticRead(dir / v.s)) else: - config.loadConfig(v.s) + config.parseConfig(dir, newFileStream(dir / v.s)) elif t.vt == VALUE_ARRAY: for v in t.a: when nimvm: - config.parseConfig(parentDir(v.s), staticRead(v.s)) + config.parseConfig(dir, staticRead(dir / v.s)) else: - config.parseConfig(parentDir(v.s), newFileStream(v.s)) + config.parseConfig(dir, newFileStream(dir / v.s)) + of "search": + for k, v in v: + case k + of "wrap": + config.searchwrap = v.b of "start": for k, v in v: case k diff --git a/src/display/client.nim b/src/display/client.nim index 9feb5410..d1c33816 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -194,7 +194,7 @@ proc setTimeout[T: JSValue|string](client: Client, handler: T, timeout = 0): int else: let fun = JS_DupValue(client.jsctx, handler) client.timeouts[id] = ((proc() = - let ret = callFunction(client.jsctx, fun) + let ret = JS_Call(client.jsctx, fun, JS_UNDEFINED, 0, nil) if JS_IsException(ret): client.jsctx.writeException(client.console.err) JS_FreeValue(client.jsctx, ret) @@ -214,7 +214,7 @@ proc setInterval[T: JSValue|string](client: Client, handler: T, interval = 0): i else: let fun = JS_DupValue(client.jsctx, handler) client.intervals[id] = ((proc() = - let ret = callFunction(client.jsctx, handler) + let ret = JS_Call(client.jsctx, handler, JS_UNDEFINED, 0, nil) if JS_IsException(ret): client.jsctx.writeException(client.console.err) JS_FreeValue(client.jsctx, ret) @@ -405,9 +405,10 @@ proc launchClient*(client: Client, pages: seq[string], ctype: Option[string], du if not dump: if stdin.isatty(): tty = stdin - elif stdout.isatty(): - discard open(tty, "/dev/tty", fmRead) - if tty == nil: + if stdout.isatty(): + if tty == nil: + dump = not open(tty, "/dev/tty", fmRead) + else: dump = true client.ssock = initServerSocket(false) client.selector = newSelector[Container]() @@ -467,9 +468,7 @@ proc newClient*(config: Config, dispatcher: Dispatcher): Client = result.jsrt.setInterruptHandler(interruptHandler, cast[pointer](result)) let ctx = result.jsrt.newJSContext() result.jsctx = ctx - result.pager = newPager(config, result.attrs, dispatcher, - result.config.getSiteConfig(ctx), - result.config.getOmniRules(ctx)) + result.pager = newPager(config, result.attrs, dispatcher, ctx) var global = JS_GetGlobalObject(ctx) ctx.registerType(Client, asglobal = true) setOpaque(ctx, global, result) diff --git a/src/display/pager.nim b/src/display/pager.nim index 25bcee82..f90b057c 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -31,9 +31,10 @@ import utils/twtstr type LineMode* = enum NO_LINEMODE, LOCATION, USERNAME, PASSWORD, COMMAND, BUFFER, SEARCH_F, - SEARCH_B, ISEARCH_F, ISEARCH_B + SEARCH_B, ISEARCH_F, ISEARCH_B, GOTO_LINE Pager* = ref object + jsctx: JSContext numload*: int alerts: seq[string] commandMode*: bool @@ -88,52 +89,43 @@ proc setContainer*(pager: Pager, c: Container) {.jsfunc.} = pager.container = c pager.redraw = true -proc cursorDown(pager: Pager) {.jsfunc.} = pager.container.cursorDown() -proc cursorUp(pager: Pager) {.jsfunc.} = pager.container.cursorUp() -proc cursorLeft(pager: Pager) {.jsfunc.} = pager.container.cursorLeft() -proc cursorRight(pager: Pager) {.jsfunc.} = pager.container.cursorRight() -proc cursorLineBegin(pager: Pager) {.jsfunc.} = pager.container.cursorLineBegin() -proc cursorLineEnd(pager: Pager) {.jsfunc.} = pager.container.cursorLineEnd() -proc cursorNextWord(pager: Pager) {.jsfunc.} = pager.container.cursorNextWord() -proc cursorPrevWord(pager: Pager) {.jsfunc.} = pager.container.cursorPrevWord() -proc cursorNextLink(pager: Pager) {.jsfunc.} = pager.container.cursorNextLink() -proc cursorPrevLink(pager: Pager) {.jsfunc.} = pager.container.cursorPrevLink() -proc pageUp(pager: Pager) {.jsfunc.} = pager.container.pageUp() -proc pageDown(pager: Pager) {.jsfunc.} = pager.container.pageDown() -proc pageRight(pager: Pager) {.jsfunc.} = pager.container.pageRight() -proc pageLeft(pager: Pager) {.jsfunc.} = pager.container.pageLeft() -proc halfPageDown(pager: Pager) {.jsfunc.} = pager.container.halfPageDown() -proc halfPageUp(pager: Pager) {.jsfunc.} = pager.container.halfPageUp() -proc cursorFirstLine(pager: Pager) {.jsfunc.} = pager.container.cursorFirstLine() -proc cursorLastLine(pager: Pager) {.jsfunc.} = pager.container.cursorLastLine() -proc cursorTop(pager: Pager) {.jsfunc.} = pager.container.cursorTop() -proc cursorMiddle(pager: Pager) {.jsfunc.} = pager.container.cursorMiddle() -proc cursorBottom(pager: Pager) {.jsfunc.} = pager.container.cursorBottom() -proc cursorLeftEdge(pager: Pager) {.jsfunc.} = pager.container.cursorLeftEdge() -proc cursorVertMiddle(pager: Pager) {.jsfunc.} = pager.container.cursorVertMiddle() -proc cursorRightEdge(pager: Pager) {.jsfunc.} = pager.container.cursorRightEdge() -proc centerLine(pager: Pager) {.jsfunc.} = pager.container.centerLine() -proc scrollDown(pager: Pager) {.jsfunc.} = pager.container.scrollDown() -proc scrollUp(pager: Pager) {.jsfunc.} = pager.container.scrollUp() -proc scrollLeft(pager: Pager) {.jsfunc.} = pager.container.scrollLeft() -proc scrollRight(pager: Pager) {.jsfunc.} = pager.container.scrollRight() -proc reshape(pager: Pager) {.jsfunc.} = pager.container.reshape() -proc peek(pager: Pager) {.jsfunc.} = pager.container.peek() -proc peekCursor(pager: Pager) {.jsfunc.} = pager.container.peekCursor() +proc hasprop(pager: Pager, s: string): bool {.jshasprop.} = + if pager.container != nil: + let cval = toJS(pager.jsctx, pager.container) + let val = JS_GetPropertyStr(pager.jsctx, cval, s) + if val != JS_UNDEFINED: + result = true + JS_FreeValue(pager.jsctx, val) + +proc reflect(ctx: JSContext, this_val: JSValue, argc: cint, argv: ptr JSValue, + magic: cint, func_data: ptr JSValue): JSValue {.cdecl.} = + let fun = cast[ptr JSValue](cast[int](func_data) + sizeof(JSValue))[] + return JS_Call(ctx, fun, func_data[], argc, argv) + +proc getter(pager: Pager, s: string): Option[JSValue] {.jsgetprop.} = + if pager.container != nil: + let cval = toJS(pager.jsctx, pager.container) + let val = JS_GetPropertyStr(pager.jsctx, cval, s) + if val != JS_UNDEFINED: + if JS_IsFunction(pager.jsctx, val): + var func_data = @[cval, val] + let fun = JS_NewCFunctionData(pager.jsctx, reflect, 1, 0, 2, addr func_data[0]) + return some(fun) + return some(val) proc searchNext(pager: Pager) {.jsfunc.} = if pager.regex.issome: if not pager.reverseSearch: - pager.container.cursorNextMatch(pager.regex.get, true) + pager.container.cursorNextMatch(pager.regex.get, pager.config.searchwrap) else: - pager.container.cursorPrevMatch(pager.regex.get, true) + pager.container.cursorPrevMatch(pager.regex.get, pager.config.searchwrap) proc searchPrev(pager: Pager) {.jsfunc.} = if pager.regex.issome: if not pager.reverseSearch: - pager.container.cursorPrevMatch(pager.regex.get, true) + pager.container.cursorPrevMatch(pager.regex.get, pager.config.searchwrap) else: - pager.container.cursorNextMatch(pager.regex.get, true) + pager.container.cursorNextMatch(pager.regex.get, pager.config.searchwrap) proc getLineHist(pager: Pager, mode: LineMode): LineHistory = if pager.linehist[mode] == nil: @@ -161,22 +153,24 @@ proc isearchBackward(pager: Pager) {.jsfunc.} = pager.container.pushCursorPos() pager.setLineEdit("?", ISEARCH_B) -proc newPager*(config: Config, attrs: WindowAttributes, dispatcher: Dispatcher, siteconf: seq[SiteConfig], omnirules: seq[OmniRule]): Pager = +proc gotoLine[T: string|int](pager: Pager, s: T = "") {.jsfunc.} = + when s is string: + if s == "": + pager.setLineEdit("Goto line: ", GOTO_LINE) + return + pager.container.gotoLine(s) + +proc newPager*(config: Config, attrs: WindowAttributes, dispatcher: Dispatcher, ctx: JSContext): Pager = let pager = Pager( dispatcher: dispatcher, config: config, display: newFixedGrid(attrs.width, attrs.height - 1), statusgrid: newFixedGrid(attrs.width), term: newTerminal(stdout, config, attrs), + jsctx: ctx, + siteconf: config.getSiteConfig(ctx), + omnirules: config.getOmniRules(ctx) ) - for sc in siteconf: - # not sure why but normal copies don't seem to work here... probably - # something weird going on with lambdas - pager.siteconf.add(sc) - pager.siteconf[^1].subst = sc.subst - for rule in omnirules: - pager.omnirules.add(rule) - pager.omnirules[^1].subst = rule.subst return pager proc launchPager*(pager: Pager, tty: File) = @@ -411,9 +405,6 @@ proc nextSiblingBuffer(pager: Pager): bool {.jsfunc.} = proc alert*(pager: Pager, msg: string) {.jsfunc.} = pager.alerts.add(msg) -proc lineInfo(pager: Pager) {.jsfunc.} = - pager.alert(pager.container.lineInfo()) - proc deleteContainer(pager: Pager, container: Container) = container.cancel() if container.sourcepair != nil: @@ -466,7 +457,7 @@ proc discardTree(pager: Pager, container = none(Container)) {.jsfunc.} = else: pager.alert("Buffer has no children!") -proc toggleSource*(pager: Pager) {.jsfunc.} = +proc toggleSource(pager: Pager) {.jsfunc.} = if pager.container.sourcepair != nil: pager.setContainer(pager.container.sourcepair) else: @@ -519,7 +510,7 @@ proc applySiteconf(pager: Pager, request: Request): BufferConfig = return pager.config.getBufferConfig(request.url, cookiejar, headers, refererfrom, scripting) # Load request in a new buffer. -proc gotoURL*(pager: Pager, request: Request, prevurl = none(URL), +proc gotoURL(pager: Pager, request: Request, prevurl = none(URL), ctype = none(string), replace: Container = nil, redirectdepth = 0, referrer: Container = nil) = if referrer != nil and referrer.config.refererfrom: @@ -628,9 +619,9 @@ proc updateReadLineISearch(pager: Pager, linemode: LineMode) = pager.container.popCursorPos(true) if pager.iregex.isSome: if linemode == ISEARCH_F: - pager.container.cursorNextMatch(pager.iregex.get, true) + pager.container.cursorNextMatch(pager.iregex.get, pager.config.searchwrap) else: - pager.container.cursorPrevMatch(pager.iregex.get, true) + pager.container.cursorPrevMatch(pager.iregex.get, pager.config.searchwrap) pager.container.hlon = true pager.container.pushCursorPos() of FINISH: @@ -674,7 +665,9 @@ proc updateReadLine*(pager: Pager) = let x = s if x != "": pager.regex = compileSearchRegex(x) pager.reverseSearch = true - pager.searchPrev() + pager.searchNext() + of GOTO_LINE: + pager.container.gotoLine(s) else: discard of CANCEL: case pager.linemode @@ -703,14 +696,7 @@ proc load(pager: Pager, s = "") {.jsfunc.} = proc reload(pager: Pager) {.jsfunc.} = pager.gotoURL(newRequest(pager.container.source.location), none(URL), pager.container.contenttype, pager.container) -# Cancel loading current page (if exists). -proc cancel(pager: Pager) {.jsfunc.} = - pager.container.cancel() - -proc click(pager: Pager) {.jsfunc.} = - pager.container.click() - -proc authorize*(pager: Pager) = +proc authorize(pager: Pager) = pager.setLineEdit("Username: ", USERNAME) proc handleEvent0(pager: Pager, container: Container, event: ContainerEvent): bool = diff --git a/src/js/javascript.nim b/src/js/javascript.nim index 876f7127..d7ccc115 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -1,10 +1,10 @@ # JavaScript binding generator. Horrifying, I know. But it works! -# Note 1: Function overloading is currently not implemented. Though there is a +# Warning: Function overloading is currently not implemented. Though there is a # block dielabel: # ... # around each bound function call, so it shouldn't be too difficult to get it # working. (This would involve generating JS functions in registerType.) -# Note 2: Now, for the pragmas: +# Now for the pragmas: # {.jsctr.} for constructors. These need no `this' value, and are bound as # regular constructors in JS. They must return a ref object, which will have # a JS counterpart too. (Other functions can return ref objects too, which @@ -19,6 +19,8 @@ # keyword, unfortunately that didn't work out.) #{.jsgetprop.} for property getters. Called when GetOwnProperty would return # nothing. The key should probably be either a string or an integer. +#{.jshasprop.} for overriding has_property. Must return a boolean. + import macros import options import sets @@ -48,7 +50,9 @@ export export JSRuntime, JSContext, JSValue -export JS_GetGlobalObject, JS_FreeValue, JS_IsException +export + JS_GetGlobalObject, JS_FreeValue, JS_IsException, JS_GetPropertyStr, + JS_IsFunction, JS_NewCFunctionData, JS_Call type JSContextOpaque* = ref object @@ -246,7 +250,7 @@ proc setProperty*(ctx: JSContext, val: JSValue, name: string, prop: JSValue) = proc setProperty*(ctx: JSContext, val: JSValue, name: string, fun: JSCFunction, argc: int = 0) = ctx.setProperty(val, name, ctx.newJSCFunction(name, fun, argc)) -func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, parent: JSClassID, asglobal: bool, nointerface: bool): JSClassID {.discardable.} = +func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string, ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, parent: JSClassID, asglobal: bool, nointerface: bool): JSClassID {.discardable.} = let rt = JS_GetRuntime(ctx) discard JS_NewClassID(addr result) var ctxOpaque = ctx.getOpaque() @@ -254,7 +258,7 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, ctor: JSCFunction, funcs if JS_NewClass(rt, result, cdef) != 0: raise newException(Defect, "Failed to allocate JS class: " & $cdef.class_name) ctxOpaque.typemap[nimt] = result - ctxOpaque.creg[$cdef.class_name] = result + ctxOpaque.creg[tname] = result var proto: JSValue if parent != 0: let parentProto = JS_GetClassProto(ctx, parent) @@ -277,7 +281,7 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, ctor: JSCFunction, funcs if asglobal: let global = JS_GetGlobalObject(ctx) assert ctxOpaque.gclaz == "" - ctxOpaque.gclaz = $cdef.class_name + ctxOpaque.gclaz = tname if JS_SetPrototype(ctx, global, proto) != 1: raise newException(Defect, "Failed to set global prototype: " & $cdef.class_name) JS_FreeValue(ctx, global) @@ -289,11 +293,6 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, ctor: JSCFunction, funcs ctx.setProperty(global, $cdef.class_name, jctor) JS_FreeValue(ctx, global) -proc callFunction*(ctx: JSContext, fun: JSValue): JSValue = - let global = JS_GetGlobalObject(ctx) - result = JS_Call(ctx, fun, JS_UNDEFINED, 0, nil) - JS_FreeValue(ctx, global) - type FuncParam = tuple[name: string, t: NimNode, val: Option[NimNode], generic: Option[NimNode]] func getMinArgs(params: seq[FuncParam]): int = @@ -790,7 +789,7 @@ type magic: uint16 BoundFunctionType = enum - FUNCTION = "js_" + FUNCTION = "js_func" CONSTRUCTOR = "js_ctor" GETTER = "js_get" SETTER = "js_set" @@ -877,7 +876,7 @@ template getJSParams(): untyped = (quote do: JSValue), newIdentDefs(ident("ctx"), quote do: JSContext), newIdentDefs(ident("this"), quote do: JSValue), - newIdentDefs(ident("argc"), quote do: int), + newIdentDefs(ident("argc"), quote do: cint), newIdentDefs(ident("argv"), quote do: ptr JSValue) ] @@ -1439,7 +1438,7 @@ proc nim_finalize_for_js[T](obj: T) = # trigger the JS finalizer and free the JS value. JS_FreeValueRT(rt, val) -proc js_illegal_ctor*(ctx: JSContext, this: JSValue, argc: int, argv: ptr JSValue): JSValue {.cdecl.} = +proc js_illegal_ctor*(ctx: JSContext, this: JSValue, argc: cint, argv: ptr JSValue): JSValue {.cdecl.} = return JS_ThrowTypeError(ctx, "Illegal constructor") type JSObjectPragmas = object @@ -1482,7 +1481,8 @@ proc findPragmas(t: NimNode): JSObjectPragmas = macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal = false, nointerface = false, name: static string = ""): JSClassID = result = newStmtList() - let name = if name == "": t.strVal else: name + let tname = t.strVal # the nim type's name. + let name = if name == "": tname else: name # possibly a different name, e.g. Buffer for Container var sctr = ident("js_illegal_ctor") var sfin = ident("js_" & t.strVal & "ClassFin") # constructor @@ -1500,7 +1500,7 @@ macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal = fals let fn = $node result.add(quote do: proc `id`(ctx: JSContext, this: JSValue): JSValue {.cdecl.} = - if not (JS_IsUndefined(this) or ctx.isGlobal(`name`)) and not ctx.isInstanceOf(this, `name`): + if not (JS_IsUndefined(this) or ctx.isGlobal(`tname`)) and not ctx.isInstanceOf(this, `tname`): # undefined -> global. return JS_ThrowTypeError(ctx, "'%s' called on an object that is not an instance of %s", `fn`, `name`) let arg_0 = fromJS_or_return(`t`, ctx, this) @@ -1512,7 +1512,7 @@ macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal = fals let fn = $node result.add(quote do: proc `id`(ctx: JSContext, this: JSValue, val: JSValue): JSValue {.cdecl.} = - if not (JS_IsUndefined(this) or ctx.isGlobal(`name`)) and not ctx.isInstanceOf(this, `name`): + if not (JS_IsUndefined(this) or ctx.isGlobal(`tname`)) and not ctx.isInstanceOf(this, `tname`): # undefined -> global. return JS_ThrowTypeError(ctx, "'%s' called on an object that is not an instance of %s", `fn`, `name`) let arg_0 = fromJS_or_return(`t`, ctx, this) @@ -1610,7 +1610,7 @@ static JSClassDef """, `cdname`, """ = { # any associated JS object from all relevant runtimes. var x: `t` new(x, nim_finalize_for_js) - `ctx`.newJSClass(`classDef`, `sctr`, `tabList`, getTypePtr(x), `parent`, `asglobal`, `nointerface`) + `ctx`.newJSClass(`classDef`, `tname`, `sctr`, `tabList`, getTypePtr(x), `parent`, `asglobal`, `nointerface`) ) result.add(newBlockStmt(endstmts)) |