diff options
author | bptato <nincsnevem662@gmail.com> | 2024-11-12 18:52:26 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-11-12 18:52:26 +0100 |
commit | a4133432e47d9e45159f0ed1d4e7f6a2c02dc5b6 (patch) | |
tree | 679ee33faf91877685a89e3bfe8743bf2b013eea /src | |
parent | 66db97a494d4605c66ba0da049accf0375f647b2 (diff) | |
download | chawan-a4133432e47d9e45159f0ed1d4e7f6a2c02dc5b6.tar.gz |
buffer: fix broken gotoAnchor behavior
23beebe6 introduced a regression that broke gotoAnchor. This fixes that, plus a couple other long-standing gotoAnchor bugs: * If no anchor is found, do not dupe the buffer. Desktop browsers still add a history entry, while w3m prints an error. I've copied the latter because it makes more sense as a user, but this will have to be refined for the navigation API at some point. * If the anchor *is* found, then always jump to it, even if it's not visible. This was a limitation of relying on the line array, so now we rely on the box tree instead. (Sooner or later, the former must go anyway.) Also, fix `U' reload not restoring the position (hopefully this time for good).
Diffstat (limited to 'src')
-rw-r--r-- | src/css/box.nim | 5 | ||||
-rw-r--r-- | src/css/render.nim | 11 | ||||
-rw-r--r-- | src/local/container.nim | 68 | ||||
-rw-r--r-- | src/local/pager.nim | 20 | ||||
-rw-r--r-- | src/server/buffer.nim | 109 |
5 files changed, 123 insertions, 90 deletions
diff --git a/src/css/box.nim b/src/css/box.nim index a40cd4af..3d35b2ab 100644 --- a/src/css/box.nim +++ b/src/css/box.nim @@ -58,6 +58,7 @@ type InlineFragment* = ref object state*: InlineFragmentState + render*: BoxRenderState computed*: CSSComputedValues node*: StyledNode splitType*: set[SplitType] @@ -79,8 +80,12 @@ type RelativeRect* = array[DimensionType, Span] + BoxRenderState* = object + offset*: Offset + BlockBox* = ref object state*: BoxLayoutState + render*: BoxRenderState computed*: CSSComputedValues node*: StyledNode inline*: InlineFragment diff --git a/src/css/render.nim b/src/css/render.nim index 6db0a9de..9688ff30 100644 --- a/src/css/render.nim +++ b/src/css/render.nim @@ -2,7 +2,6 @@ import std/algorithm import css/box import css/cssvalues -import css/layout import css/lunit import css/stylednode import types/bitmap @@ -345,8 +344,10 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState; if bgcolor0.a > 0: grid.paintInlineFragment(state, fragment, offset, bgcolor0.rgb.cellColor()) + let startOffset = offset + fragment.state.startOffset + fragment.render.offset = startOffset if position notin PositionStaticLike and stSplitStart in fragment.splitType: - state.absolutePos.add(offset + fragment.state.startOffset) + state.absolutePos.add(startOffset) if fragment.t == iftParent: for child in fragment.children: grid.renderInlineFragment(state, child, offset, bgcolor0) @@ -396,6 +397,7 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; if not box.computed{"top"}.auto or not box.computed{"bottom"}.auto: offset.y = state.absolutePos[^1].y offset += box.state.offset + box.render.offset = offset if position notin PositionStaticLike: state.absolutePos.add(offset) if box.computed{"visibility"} == VisibilityVisible: @@ -438,13 +440,12 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; discard state.absolutePos.pop() proc renderDocument*(grid: var FlexibleGrid; bgcolor: var CellColor; - styledRoot: StyledNode; attrsp: ptr WindowAttributes; + rootBox: BlockBox; attrsp: ptr WindowAttributes; images: var seq[PosBitmap]) = grid.setLen(0) - if styledRoot == nil: + if rootBox == nil: # no HTML element when we run cascade; just clear all lines. return - let rootBox = styledRoot.layout(attrsp) var state = RenderState( absolutePos: @[offset(0, 0)], attrsp: attrsp, diff --git a/src/local/container.nim b/src/local/container.nim index a5cdd630..862ae0ed 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -46,9 +46,8 @@ type setxsave: bool ContainerEventType* = enum - cetAnchor, cetNoAnchor, cetReadLine, cetReadArea, cetReadFile, cetOpen, - cetSetLoadInfo, cetStatus, cetAlert, cetLoaded, cetTitle, cetCancel, - cetMetaRefresh + cetReadLine, cetReadArea, cetReadFile, cetOpen, cetSetLoadInfo, cetStatus, + cetAlert, cetLoaded, cetTitle, cetCancel, cetMetaRefresh ContainerEvent* = object case t*: ContainerEventType @@ -62,8 +61,6 @@ type request*: Request save*: bool url*: URL - of cetAnchor, cetNoAnchor: - anchor*: string of cetAlert: msg*: string of cetMetaRefresh: @@ -151,7 +148,7 @@ type # if set, this *overrides* any content type received from the network. (this # is because it stores the content type from the -T flag.) contentType* {.jsget.}: Option[string] - pos: CursorPosition + pos*: CursorPosition bpos: seq[CursorPosition] highlights: seq[Highlight] process* {.jsget.}: int @@ -489,13 +486,16 @@ proc triggerEvent(container: Container; event: ContainerEvent) = proc triggerEvent(container: Container; t: ContainerEventType) = container.triggerEvent(ContainerEvent(t: t)) -proc setNumLines(container: Container; lines: int; finish = false) = +proc gotoStart(container: Container) = + container.pos = container.startpos.get + container.startpos = none(CursorPosition) + container.needslines = true + +proc setNumLines(container: Container; lines: int) = if container.numLines != lines: container.numLines = lines - if container.startpos.isSome and finish: - container.pos = container.startpos.get - container.startpos = none(CursorPosition) - container.needslines = true + if container.startpos.isSome and lines >= container.startpos.get.cursory: + container.gotoStart() container.updateCursor() proc queueDraw*(container: Container) = @@ -514,7 +514,7 @@ proc requestLines(container: Container): EmptyPromise {.discardable.} = if isBgNew: container.bgcolor = res.bgcolor if res.numLines != container.numLines: - container.setNumLines(res.numLines, true) + container.setNumLines(res.numLines) if container.loadState != lsLoading: container.triggerEvent(cetStatus) if res.numLines > 0: @@ -706,7 +706,7 @@ proc setCursorYCenter(container: Container; y: int; refresh = true) if fy != container.fromy: container.centerLine() -proc setCursorXYCenter(container: Container; x, y: int; refresh = true) +proc setCursorXYCenter*(container: Container; x, y: int; refresh = true) {.jsfunc.} = let fy = container.fromy let fx = container.fromx @@ -1102,10 +1102,13 @@ proc popCursorPos*(container: Container; nojump = false) = container.sendCursorPosition() container.needslines = true -proc copyCursorPos*(container, c2: Container) = - container.startpos = some(c2.pos) +proc setStartingPos*(container: Container; pos: CursorPosition) = + container.startpos = some(pos) container.flags.incl(cfHasStart) +proc setStartingPos*(container: Container; x, y: int) = + container.setStartingPos(CursorPosition(setx: -1, cursorx: x, cursory: y)) + proc cursorNextLink*(container: Container; n = 1) {.jsfunc.} = if container.iface == nil: return @@ -1427,18 +1430,21 @@ proc onload(container: Container; res: int) = container.setLoadInfo("") container.triggerEvent(cetStatus) container.triggerEvent(cetLoaded) - if cfHasStart notin container.flags and (container.url.hash != "" or - container.config.autofocus): - container.requestLines().then(proc(): Promise[GotoAnchorResult] = - return container.iface.gotoAnchor() - ).then(proc(res: GotoAnchorResult) = - if res.found: - container.setCursorXYCenter(res.x, res.y) - if res.focus != nil: - container.onReadLine(res.focus) - ) + if cfHasStart in container.flags: + if container.startpos.isSome: + container.gotoStart() else: - container.needslines = true + let anchor = container.url.hash.substr(1) + if anchor != "" or container.config.autofocus: + container.requestLines().then(proc(): Promise[GotoAnchorResult] = + return container.iface.gotoAnchor(anchor, container.config.autofocus) + ).then(proc(res: GotoAnchorResult) = + if res.found: + container.setCursorXYCenter(res.x, res.y) + if res.focus != nil: + container.onReadLine(res.focus) + ) + container.needslines = true if container.config.metaRefresh != mrNever: container.iface.checkRefresh().then(proc(res: CheckRefreshResult) = if res.n >= 0: @@ -1520,14 +1526,6 @@ proc cancel*(container: Container) {.jsfunc.} = else: container.triggerEvent(cetCancel) -proc findAnchor*(container: Container; anchor: string) = - container.iface.findAnchor(anchor).then(proc(found: bool) = - if found: - container.triggerEvent(ContainerEvent(t: cetAnchor, anchor: anchor)) - else: - container.triggerEvent(ContainerEvent(t: cetNoAnchor, anchor: anchor)) - ) - proc readCanceled*(container: Container) = container.iface.readCanceled().then(proc(repaint: bool) = if repaint: @@ -1694,7 +1692,7 @@ proc onreadline(container: Container; w: Slice[int]; container.iface.getLines(w).then(proc(res: GetLinesResult) = container.onreadline(w, handle, res)) else: - container.setNumLines(res.numLines, true) + container.setNumLines(res.numLines) # Synchronously read all lines in the buffer. proc readLines*(container: Container; handle: proc(line: SimpleFlexibleLine)) = diff --git a/src/local/pager.nim b/src/local/pager.nim index 07244408..ee799705 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -865,7 +865,7 @@ proc dupeBuffer(pager: Pager; container: Container; url: URL) = if p == nil: pager.alert("Failed to duplicate buffer.") else: - p.then(proc(container: Container) = + p.then(proc(container: Container): Container = if container == nil: pager.alert("Failed to duplicate buffer.") else: @@ -1423,13 +1423,21 @@ proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); else: container.replaceBackup = replaceBackup replaceBackup.replaceRef = container - container.copyCursorPos(replace) + container.setStartingPos(replace.pos) else: pager.addContainer(container) inc pager.numload return container else: - pager.container.findAnchor(request.url.hash) + let container = pager.container + let url = request.url + let anchor = url.hash.substr(1) + container.iface.gotoAnchor(anchor, false).then(proc(res: GotoAnchorResult) = + if res.found: + pager.dupeBuffer(container, url) + else: + pager.alert("Anchor " & url.hash & " not found") + ) return nil proc omniRewrite(pager: Pager; s: string): string = @@ -2231,12 +2239,6 @@ proc handleEvent0(pager: Pager; container: Container; event: ContainerEvent): case event.t of cetLoaded: dec pager.numload - of cetAnchor: - let url2 = newURL(container.url) - url2.setHash(event.anchor) - pager.dupeBuffer(container, url2) - of cetNoAnchor: - pager.alert("Couldn't find anchor " & event.anchor) of cetReadLine: if container == pager.container: pager.setLineEdit(lmBuffer, event.value, hide = event.password, diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 0386b996..0aa8b545 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -11,9 +11,12 @@ import chagashi/decoder import chagashi/decodercore import chame/tags import config/config +import css/box import css/cascade import css/cssparser import css/cssvalues +import css/layout +import css/lunit import css/render import css/sheet import css/stylednode @@ -51,12 +54,11 @@ import utils/twtstr type BufferCommand* = enum - bcLoad, bcForceRender, bcWindowChange, bcFindAnchor, bcReadSuccess, - bcReadCanceled, bcClick, bcFindNextLink, bcFindPrevLink, bcFindNthLink, - bcFindRevNthLink, bcFindNextMatch, bcFindPrevMatch, bcGetLines, - bcUpdateHover, bcGotoAnchor, bcCancel, bcGetTitle, bcSelect, bcClone, - bcFindPrevParagraph, bcFindNextParagraph, bcMarkURL, bcToggleImages, - bcCheckRefresh + bcLoad, bcForceRender, bcWindowChange, bcReadSuccess, bcReadCanceled, + bcClick, bcFindNextLink, bcFindPrevLink, bcFindNthLink, bcFindRevNthLink, + bcFindNextMatch, bcFindPrevMatch, bcGetLines, bcUpdateHover, bcGotoAnchor, + bcCancel, bcGetTitle, bcSelect, bcClone, bcFindPrevParagraph, + bcFindNextParagraph, bcMarkURL, bcToggleImages, bcCheckRefresh BufferState = enum bsLoadingPage, bsLoadingResources, bsLoaded @@ -98,6 +100,7 @@ type pollData: PollData prevStyled: StyledNode prevnode: StyledNode + rootBox: BlockBox pstream: SocketStream # control stream quirkstyle: CSSStylesheet reportedBytesRead: int @@ -463,12 +466,12 @@ proc findPrevLink*(buffer: Buffer; cursorx, cursory, n: int): var ly = 0 #last y var lx = 0 #last x - template link_beginning() = - #go to beginning of link + template link_beginning(y: int) = + # go to beginning of link ly = y #last y lx = format.pos #last x - #on the current line + # on the current line let line = buffer.lines[y] while i >= 0: let format = line.formats[i] @@ -477,7 +480,7 @@ proc findPrevLink*(buffer: Buffer; cursorx, cursory, n: int): lx = format.pos dec i - #on previous lines + # on previous lines for iy in countdown(ly - 1, 0): let line = buffer.lines[iy] i = line.formats.len - 1 @@ -507,8 +510,7 @@ proc findPrevLink*(buffer: Buffer; cursorx, cursory, n: int): let format = line.formats[i] let fl = format.node.getClickable() if fl != nil and fl != link: - let y = cursory - link_beginning + link_beginning cursory found_pos lx, ly, fl dec i @@ -519,7 +521,7 @@ proc findPrevLink*(buffer: Buffer; cursorx, cursory, n: int): let format = line.formats[i] let fl = format.node.getClickable() if fl != nil and fl != link: - link_beginning + link_beginning y found_pos lx, ly, fl dec i return (-1, -1) @@ -704,12 +706,44 @@ type GotoAnchorResult* = object y*: int focus*: ReadLineResult -proc gotoAnchor*(buffer: Buffer): GotoAnchorResult {.proxy.} = +proc findAnchor(box: BlockBox; anchor: Element): Offset + +proc findAnchor(fragment: InlineFragment; anchor: Element): Offset = + if fragment.t == iftBox: + let off = fragment.box.findAnchor(anchor) + if off.y >= 0: + return off + elif fragment.t == iftParent: + for child in fragment.children: + let off = child.findAnchor(anchor) + if off.y >= 0: + return off + if fragment.node != nil and fragment.node.node == anchor: + return fragment.render.offset + return offset(-1, -1) + +proc findAnchor(box: BlockBox; anchor: Element): Offset = + if box.inline != nil: + let off = box.inline.findAnchor(anchor) + if off.y >= 0: + return off + for child in box.children: + let off = child.findAnchor(anchor) + if off.y >= 0: + return off + if box.node != nil and box.node.node == anchor: + return box.render.offset + return offset(-1, -1) + +proc gotoAnchor*(buffer: Buffer; anchor: string; autofocus: bool): + GotoAnchorResult {.proxy.} = if buffer.document == nil: return GotoAnchorResult(found: false) - var anchor = buffer.document.findAnchor(buffer.url.hash) + var anchor = buffer.document.findAnchor(anchor) var focus: ReadLineResult = nil - if buffer.config.autofocus: + # Do not use buffer.config.autofocus when we just want to check if the + # anchor can be found. + if autofocus: let autofocus = buffer.document.findAutoFocus() if autofocus != nil: if anchor == nil: @@ -718,18 +752,10 @@ proc gotoAnchor*(buffer: Buffer): GotoAnchorResult {.proxy.} = focus = res.readline.get(nil) if anchor == nil: return GotoAnchorResult(found: false) - for y in 0 ..< buffer.lines.len: - let line = buffer.lines[y] - for i in 0 ..< line.formats.len: - let format = line.formats[i] - if format.node != nil and format.node.node in anchor: - return GotoAnchorResult( - found: true, - x: format.pos, - y: y, - focus: focus - ) - return GotoAnchorResult(found: false) + let offset = buffer.rootBox.findAnchor(anchor) + let x = max(offset.x div buffer.attrs.ppc, 0).toInt + let y = max(offset.y div buffer.attrs.ppl, 0).toInt + return GotoAnchorResult(found: true, x: x, y: y, focus: focus) type CheckRefreshResult* = object # n is timeout in millis. -1 => not found @@ -788,8 +814,12 @@ proc reshape(buffer: Buffer) = buffer.prevStyled = nil let styledRoot = buffer.document.applyStylesheets(uastyle, buffer.userstyle, buffer.prevStyled) - buffer.lines.renderDocument(buffer.bgcolor, styledRoot, addr buffer.attrs, - buffer.images) + # applyStylesheets may return nil if there is no <html> element. + buffer.rootBox = nil + if styledRoot != nil: + buffer.rootBox = styledRoot.layout(addr buffer.attrs) + buffer.lines.renderDocument(buffer.bgcolor, buffer.rootBox, + addr buffer.attrs, buffer.images) buffer.prevStyled = styledRoot proc maybeReshape(buffer: Buffer) = @@ -1088,9 +1118,9 @@ proc hasTask(buffer: Buffer; cmd: BufferCommand): bool = proc resolveTask[T](buffer: Buffer; cmd: BufferCommand; res: T) = let packetid = buffer.tasks[cmd] assert packetid != 0 - buffer.pstream.withPacketWriter w: - w.swrite(packetid) - w.swrite(res) + buffer.pstream.withPacketWriter wt: + wt.swrite(packetid) + wt.swrite(res) buffer.tasks[cmd] = 0 proc onload(buffer: Buffer) = @@ -1610,9 +1640,6 @@ proc select*(buffer: Buffer; selected: seq[int]): ClickResult {.proxy.} = proc readCanceled*(buffer: Buffer): bool {.proxy.} = return buffer.restoreFocus() -proc findAnchor*(buffer: Buffer; anchor: string): bool {.proxy.} = - return buffer.document != nil and buffer.document.findAnchor(anchor) != nil - type GetLinesResult* = tuple numLines: int lines: seq[SimpleFlexibleLine] @@ -1741,14 +1768,14 @@ macro bufferDispatcher(funs: static ProxyMap; buffer: Buffer; var resolve = newStmtList() if rval == nil: resolve.add(quote do: - buffer.pstream.withPacketWriter w: - w.swrite(`packetid`) + buffer.pstream.withPacketWriter wt: + wt.swrite(`packetid`) ) else: resolve.add(quote do: - buffer.pstream.withPacketWriter w: - w.swrite(`packetid`) - w.swrite(`rval`) + buffer.pstream.withPacketWriter wt: + wt.swrite(`packetid`) + wt.swrite(`rval`) ) if v.istask: let en = v.ename |