diff options
author | bptato <nincsnevem662@gmail.com> | 2024-03-20 16:01:58 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-03-20 16:01:58 +0100 |
commit | e9c202f29224f6e83abeed161263298237618145 (patch) | |
tree | 1849d6118a28f77bd6f5be416f1fa37adfeea460 /src/local | |
parent | 4b7149a68e70737ea05514bda26cd64dc8d5909a (diff) | |
download | chawan-e9c202f29224f6e83abeed161263298237618145.tar.gz |
pager: add "save link", "save source"; change & document some keybindings
* `s{Enter}' now saves link, and `sS' saves source. * Changed ;, +, @ to g0, g$, gc so that it's somewhat consistent with vim (and won't conflict with ; for "repeat jump to char") * Changed (, ) to -, + so that it doesn't conflict with vi's "previous/next sentence" (once we have it...) * Add previously missing keybindings to about:chawan
Diffstat (limited to 'src/local')
-rw-r--r-- | src/local/client.nim | 4 | ||||
-rw-r--r-- | src/local/container.nim | 66 | ||||
-rw-r--r-- | src/local/pager.nim | 104 |
3 files changed, 111 insertions, 63 deletions
diff --git a/src/local/client.nim b/src/local/client.nim index 7c5fabb9..c4358c4d 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -648,7 +648,7 @@ proc addConsole(pager: Pager; interactive: bool; clearFun, showFun, hideFun: raise newException(Defect, "Failed to open console pipe.") let url = newURL("stream:console").get let container = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pipefd[0], - url, ConsoleTitle, canReinterpret = false, userRequested = false) + url, ConsoleTitle, {}) let err = newPosixStream(pipefd[1]) err.writeLine("Type (M-c) console.hide() to return to buffer mode.") let console = newConsole(err, clearFun, showFun, hideFun) @@ -664,7 +664,7 @@ proc clearConsole(client: Client) = let url = newURL("stream:console").get let pager = client.pager let replacement = pager.readPipe0("text/plain", CHARSET_UNKNOWN, pipefd[0], - url, ConsoleTitle, canreinterpret = false, userRequested = false) + url, ConsoleTitle, {}) replacement.replace = client.consoleWrapper.container pager.replace(client.consoleWrapper.container, replacement) client.consoleWrapper.container = replacement diff --git a/src/local/container.nim b/src/local/container.nim index d706b77c..41cc2efd 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -56,6 +56,8 @@ type tvalue*: string of cetOpen: request*: Request + url*: URL + save*: bool of cetAnchor, cetNoAnchor: anchor*: string of cetAlert: @@ -90,6 +92,9 @@ type LoadState = enum lsLoading, lsCanceled, lsLoaded + ContainerFlag* = enum + cfCloned, cfUserRequested, cfHasStart, cfCanReinterpret, cfSave, cfIsHTML + Container* = ref object # note: this is not the same as source.request.url (but should be synced # with buffer.url) @@ -134,31 +139,27 @@ type loadState: LoadState events*: Deque[ContainerEvent] startpos: Option[CursorPosition] - hasstart: bool redirectDepth*: int select*: Select - canReinterpret*: bool - cloned: bool currentSelection {.jsget.}: Highlight tmpJumpMark: PagePos jumpMark: PagePos marks: Table[string, PagePos] - ishtml*: bool filter*: BufferFilter bgcolor*: CellColor tailOnLoad*: bool cacheFile* {.jsget.}: string - userRequested*: bool mainConfig*: Config + flags*: set[ContainerFlag] jsDestructor(Highlight) jsDestructor(Container) proc newContainer*(config: BufferConfig; loaderConfig: LoaderClientConfig; url: URL; request: Request; attrs: WindowAttributes; title: string; - redirectDepth: int; canReinterpret: bool; contentType: Option[string]; + redirectDepth: int; flags: set[ContainerFlag]; contentType: Option[string]; charsetStack: seq[Charset]; cacheId: int; cacheFile: string; - userRequested: bool; mainConfig: Config): Container = + mainConfig: Config): Container = return Container( url: url, request: request, @@ -172,13 +173,12 @@ proc newContainer*(config: BufferConfig; loaderConfig: LoaderClientConfig; pos: CursorPosition( setx: -1 ), - canReinterpret: canReinterpret, loadinfo: "Connecting to " & request.url.host & "...", cacheId: cacheId, cacheFile: cacheFile, process: -1, - userRequested: userRequested, - mainConfig: mainConfig + mainConfig: mainConfig, + flags: flags ) func location(container: Container): URL {.jsfget.} = @@ -196,7 +196,7 @@ proc clone*(container: Container; newurl: URL): Promise[Container] = nc[] = container[] nc.url = url nc.process = pid - nc.cloned = true + nc.flags.incl(cfCloned) nc.retry = @[] nc.parent = nil nc.children = @[] @@ -1070,7 +1070,7 @@ proc popCursorPos*(container: Container, nojump = false) = proc copyCursorPos*(container, c2: Container) = container.startpos = some(c2.pos) - container.hasstart = true + container.flags.incl(cfHasStart) proc cursorNextLink*(container: Container, n = 1) {.jsfunc.} = if container.iface == nil: @@ -1375,7 +1375,7 @@ proc onload*(container: Container, res: int) = container.title = title container.triggerEvent(cetTitle) ) - if not container.hasstart and container.url.anchor != "": + if cfHasStart notin container.flags and container.url.anchor != "": container.requestLines().then(proc(): Promise[Opt[tuple[x, y: int]]] = return container.iface.gotoAnchor() ).then(proc(res: Opt[tuple[x, y: int]]) = @@ -1481,22 +1481,26 @@ proc reshape(container: Container): EmptyPromise {.jsfunc.} = return container.requestLines() ) -proc onclick(container: Container, res: ClickResult) +proc onclick(container: Container; res: ClickResult; save: bool) -proc displaySelect(container: Container, selectResult: SelectResult) = +proc displaySelect(container: Container; selectResult: SelectResult) = let submitSelect = proc(selected: seq[int]) = container.iface.select(selected).then(proc(res: ClickResult) = - container.onclick(res)) + container.onclick(res, save = false)) container.select.initSelect(selectResult, container.acursorx, container.acursory, container.height, submitSelect) container.triggerEvent(cetUpdate) -proc onclick(container: Container; res: ClickResult) = +proc onclick(container: Container; res: ClickResult; save: bool) = if res.repaint: container.needslines = true if res.open.isSome: - container.triggerEvent(ContainerEvent(t: cetOpen, request: res.open.get)) - if res.select.isSome: + container.triggerEvent(ContainerEvent( + t: cetOpen, + request: res.open.get, + save: save + )) + if res.select.isSome and not save: container.displaySelect(res.select.get) if res.readline.isSome: let rl = res.readline.get @@ -1521,9 +1525,25 @@ proc click*(container: Container) {.jsfunc.} = if container.iface == nil: return container.iface.click(container.cursorx, container.cursory) - .then(proc(res: ClickResult) = container.onclick(res)) + .then(proc(res: ClickResult) = container.onclick(res, save = false)) + +proc saveLink*(container: Container) {.jsfunc.} = + if container.iface == nil: + return + container.iface.click(container.cursorx, container.cursory) + .then(proc(res: ClickResult) = container.onclick(res, save = true)) -proc windowChange*(container: Container, attrs: WindowAttributes) = +proc saveSource*(container: Container) {.jsfunc.} = + if container.iface == nil: + return + container.triggerEvent(ContainerEvent( + t: cetOpen, + request: newRequest(newURL("cache:" & $container.cacheId).get), + save: true, + url: container.url + )) + +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 @@ -1567,7 +1587,7 @@ proc handleCommand(container: Container) = proc setStream*(container: Container; stream: SocketStream; registerFun: proc(fd: int); fd: FileHandle; outCacheId: int) = - assert not container.cloned + assert cfCloned notin container.flags container.iface = newBufferInterface(stream, registerFun) container.iface.passFd(fd, outCacheId) discard close(fd) @@ -1577,7 +1597,7 @@ proc setStream*(container: Container; stream: SocketStream; proc setCloneStream*(container: Container; stream: SocketStream; registerFun: proc(fd: int)) = - assert container.cloned + assert cfCloned in container.flags container.iface = cloneInterface(stream, registerFun) # Maybe we have to resume loading. Let's try. discard container.iface.load().then(proc(res: int) = diff --git a/src/local/pager.nim b/src/local/pager.nim index 3a9c3aa7..49af389b 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -502,9 +502,9 @@ proc onSetLoadInfo(pager: Pager; container: Container) = proc newContainer(pager: Pager; bufferConfig: BufferConfig; loaderConfig: LoaderClientConfig; request: Request; title = ""; - redirectDepth = 0; canReinterpret = true; contentType = none(string); - charsetStack: seq[Charset] = @[]; url = request.url; cacheId = -1; - cacheFile = ""; userRequested = true): Container = + redirectDepth = 0; flags = {cfCanReinterpret, cfUserRequested}; + contentType = none(string); charsetStack: seq[Charset] = @[]; + url = request.url; cacheId = -1; cacheFile = ""): Container = request.suspended = true if loaderConfig.cookieJar != nil: # loader stores cookie jars per client, but we have no client yet. @@ -528,12 +528,11 @@ proc newContainer(pager: Pager; bufferConfig: BufferConfig; pager.term.attrs, title, redirectDepth, - canReinterpret, + flags, contentType, charsetStack, cacheId, cacheFile, - userRequested, pager.config ) pager.connectingContainers.add(ConnectingContainerItem( @@ -796,12 +795,12 @@ template myExec(cmd: string) = exitnow(127) proc toggleSource(pager: Pager) {.jsfunc.} = - if not pager.container.canReinterpret: + if cfCanReinterpret notin pager.container.flags: return if pager.container.sourcepair != nil: pager.setContainer(pager.container.sourcepair) else: - let ishtml = not pager.container.ishtml + let ishtml = cfIsHTML notin pager.container.flags #TODO I wish I could set the contentType to whatever I wanted, not just HTML let contentType = if ishtml: "text/html" @@ -944,11 +943,13 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; ) # Load request in a new buffer. -proc gotoURL(pager: Pager, request: Request, prevurl = none(URL), - contentType = none(string), cs = CHARSET_UNKNOWN, replace: Container = nil, - redirectDepth = 0, referrer: Container = nil) = +proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); + contentType = none(string); cs = CHARSET_UNKNOWN; replace: Container = nil; + redirectDepth = 0; referrer: Container = nil; save = false; + url: URL = nil) = if referrer != nil and referrer.config.referer_from: request.referrer = referrer.url + let url = if url != nil: url else: request.url var loaderConfig: LoaderClientConfig var bufferConfig = pager.applySiteconf(request.url, cs, loaderConfig) if prevurl.isNone or not prevurl.get.equals(request.url, true) or @@ -961,12 +962,17 @@ proc gotoURL(pager: Pager, request: Request, prevurl = none(URL), # feedback on what is actually going to happen when typing a URL; TODO. if referrer != nil: loaderConfig.referrerPolicy = referrer.loaderConfig.referrerPolicy + var flags = {cfCanReinterpret, cfUserRequested} + if save: + flags.incl(cfSave) let container = pager.newContainer( bufferConfig, loaderConfig, request, redirectDepth = redirectDepth, - contentType = contentType + contentType = contentType, + flags = flags, + url = url ) if replace != nil: pager.replace(replace, container) @@ -1026,8 +1032,8 @@ proc loadURL*(pager: Pager, url: string, ctype = none(string), pager.container.retry = urls proc readPipe0*(pager: Pager; contentType: string; cs: Charset; - fd: FileHandle; url: URL, title: string; - canReinterpret, userRequested: bool): Container = + fd: FileHandle; url: URL; title: string; flags: set[ContainerFlag]): + Container = var url = url pager.loader.passFd(url.pathname, fd) safeClose(fd) @@ -1038,16 +1044,15 @@ proc readPipe0*(pager: Pager; contentType: string; cs: Charset; loaderConfig, newRequest(url), title = title, - canReinterpret = canReinterpret, - contentType = some(contentType), - userRequested = userRequested + flags = flags, + contentType = some(contentType) ) proc readPipe*(pager: Pager, contentType: string, cs: Charset, fd: FileHandle, title: string) = let url = newURL("stream:-").get let container = pager.readPipe0(contentType, cs, fd, url, title, - canReinterpret = true, userRequested = true) + {cfCanReinterpret, cfUserRequested}) inc pager.numload pager.addContainer(container) @@ -1246,7 +1251,10 @@ proc externFilterSource(pager: Pager; cmd: string; c: Container = nil; let fallback = pager.container.contentType.get("text/plain") let contentType = contentType.get(fallback) let container = pager.newContainerFrom(fromc, contentType) - container.ishtml = contentType == "text/html" + if contentType == "text/html": + container.flags.incl(cfIsHTML) + else: + container.flags.excl(cfIsHTML) pager.addContainer(container) container.filter = BufferFilter(cmd: cmd) @@ -1453,7 +1461,11 @@ proc filterBuffer(pager: Pager; stream: SocketStream; cmd: string; proc checkMailcap(pager: Pager; container: Container; stream: SocketStream; istreamOutputId: int; contentType: string): CheckMailcapResult = if container.filter != nil: - return pager.filterBuffer(stream, container.filter.cmd, container.ishtml) + return pager.filterBuffer( + stream, + container.filter.cmd, + cfIsHTML in container.flags + ) # contentType must exist, because we set it in applyResponse let shortContentType = container.contentType.get if shortContentType == "text/html": @@ -1562,6 +1574,23 @@ proc redirect(pager: Pager; container: Container; response: Response; pager.alert("Error: maximum redirection depth reached") pager.deleteContainer(container) +proc askDownloadPath(pager: Pager; container: Container; response: Response) = + var buf = pager.config.external.download_dir + let pathname = container.url.pathname + if pathname[^1] == '/': + buf &= "index.html" + else: + buf &= container.url.pathname.afterLast('/') + pager.setLineEdit(lmDownload, buf) + pager.lineData = LineDataDownload( + outputId: response.outputId, + stream: response.body + ) + pager.deleteContainer(container) + pager.redraw = true + pager.refreshStatusMsg() + dec pager.numload + proc connected(pager: Pager; container: Container; response: Response) = let istream = response.body container.applyResponse(response, pager.config.external.mime_types) @@ -1573,8 +1602,12 @@ proc connected(pager: Pager; container: Container; response: Response) = # This forces client to ask for confirmation before quitting. # (It checks a flag on container, because console buffers must not affect this # variable.) - if container.userRequested: + if cfUserRequested in container.flags: pager.hasload = true + if cfSave in container.flags: + # download queried by user + pager.askDownloadPath(container, response) + return let realContentType = if "Content-Type" in response.headers: response.headers["Content-Type"] else: @@ -1582,35 +1615,28 @@ proc connected(pager: Pager; container: Container; response: Response) = container.contentType.get & ";charset=" & $container.charset let mailcapRes = pager.checkMailcap(container, istream, response.outputId, realContentType) - if not mailcapRes.found and container.contentType.get.until('/') != "text": - pager.setLineEdit(lmDownload, - pager.config.external.download_dir & - container.url.pathname.afterLast('/')) - pager.lineData = LineDataDownload( - outputId: response.outputId, - stream: istream - ) - pager.deleteContainer(container) - pager.redraw = true - pager.refreshStatusMsg() - dec pager.numload + if not mailcapRes.found and realContentType.startsWithIgnoreCase("text/"): + pager.askDownloadPath(container, response) return if mailcapRes.connect: - container.ishtml = mailcapRes.ishtml + if mailcapRes.ishtml: + container.flags.incl(cfIsHTML) + else: + container.flags.excl(cfIsHTML) # buffer now actually exists; create a process for it container.process = pager.forkserver.forkBuffer( container.config, container.url, container.request, pager.attrs, - container.ishtml, + mailcapRes.ishtml, container.charsetStack ) if mailcapRes.fdout != istream.fd: # istream has been redirected into a filter istream.close() pager.procmap.add(ProcMapItem( - container: container, + container: container, fdout: FileHandle(mailcapRes.fdout), fdin: FileHandle(istream.fd), ostreamOutputId: mailcapRes.ostreamOutputId, @@ -1713,15 +1739,17 @@ proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): pager.redraw = true of cetOpen: let url = event.request.url - if pager.container == nil or not pager.container.isHoverURL(url): + if not event.save and (pager.container != container or + not container.isHoverURL(url)): pager.ask("Open pop-up? " & $url).then(proc(x: bool) = if x: pager.gotoURL(event.request, some(container.url), - referrer = pager.container) + referrer = pager.container, save = event.save) ) else: + let url = if event.url != nil: event.url else: event.request.url pager.gotoURL(event.request, some(container.url), - referrer = pager.container) + referrer = pager.container, save = event.save, url = url) of cetStatus: if pager.container == container: pager.showAlerts() |