From 738cf976b920e41de72537d8313f8f6ab610e7f8 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 23 Oct 2015 21:16:37 +0200 Subject: [PATCH] lines can be skipped; minimap uses this feature --- NimEdit.nimble | 6 +++--- buffer.nim | 56 ++++++++++++++++++++++++++++++++------------------ buffertype.nim | 5 ++++- drawbuffer.nim | 52 ++++++++++++++++++++++++++++++++++++++++------ minimaps.nim | 37 +++++++++++++++++++++------------ nimedit.nim | 10 +++++++-- prims.nim | 7 +++++++ scrollbar.nim | 4 ++-- 8 files changed, 130 insertions(+), 47 deletions(-) diff --git a/NimEdit.nimble b/NimEdit.nimble index 0c24240..166ac04 100644 --- a/NimEdit.nimble +++ b/NimEdit.nimble @@ -1,11 +1,11 @@ [Package] name = "NimEdit" -version = "0.1.0" +version = "0.91" author = "Andreas Rumpf" description = "A beautiful SDL-based Nim IDE." -license = "MIT" +license = "Commercial" bin = "nimedit" [Deps] -Requires: "nim >= 0.11.3, sdl2#head" +Requires: "nim >= 0.11.3, sdl2#head, dialogs >= 1.0" diff --git a/buffer.nim b/buffer.nim index 77ec731..4e5a5a6 100644 --- a/buffer.nim +++ b/buffer.nim @@ -1,6 +1,6 @@ # Implementation uses a gap buffer with explicit undo stack. -import strutils, unicode +import strutils, unicode, intsets import styles, highlighters, common, themes import sdl2, sdl2/ttf, prims import buffertype, unihelp, languages @@ -70,6 +70,7 @@ proc newBuffer*(heading: string; mgr: ptr StyleManager): Buffer = result.selected.a = -1 result.selected.b = -1 result.bracketToHighlight = -1 + result.activeLines = initIntSet() proc clear*(result: Buffer) = result.front.setLen 0 @@ -91,6 +92,9 @@ proc clear*(result: Buffer) = result.undoIdx = 0 result.activeMarker = 0 result.cursorDim = (0, 0, 0) + result.activeLines = initIntSet() + result.filterLines = false + result.minimapVersion = 0 proc fullText*(b: Buffer): string = result = newStringOfCap(b.front.len + b.back.len) @@ -136,6 +140,18 @@ proc downFirstLineOffset(b: Buffer) = b.firstLineOffset = i+1 assert b.firstLineOffset == 0 or b[b.firstLineOffset-1] == '\L' +proc skipInactiveLinesUp(b: Buffer) = + if b.filterLines: + while b.firstLine > 0 and b.firstLine notin b.activeLines: + dec b.firstLine + upFirstLineOffset(b) + +proc skipInactiveLinesDown(b: Buffer) = + if b.filterLines: + while b.firstLine < b.numberOfLines-1 and b.firstLine notin b.activeLines: + inc b.firstLine + downFirstLineOffset(b) + proc scrollLines*(b: Buffer; amount: int) = let oldFirstLine = b.firstLine b.firstLine = clamp(b.firstLine+amount, 0, max(0, b.numberOfLines-1)) @@ -144,32 +160,37 @@ proc scrollLines*(b: Buffer; amount: int) = if amount < 0: while amount < 0: upFirstLineOffset(b) + skipInactiveLinesUp(b) inc amount elif amount > 0: while amount > 0: downFirstLineOffset(b) + skipInactiveLinesDown(b) dec amount - inc b.firstLine, amount proc scroll(b: Buffer; amount: int) = assert amount == 1 or amount == -1 # the cursor can be detached from the scroll position, so we need to perform # a general scrollLines: inc b.currentLine, amount + if b.filterLines: + let oldLine = b.currentLine + while b.currentLine < b.numberOfLines-1 and b.currentLine > 0 and + b.currentLine notin b.activeLines: + inc b.currentLine, amount + if b.currentLine notin b.activeLines: + b.currentLine = oldLine - amount + b.cursor = getLineOffset(b, b.currentLine) + elif oldLine != b.currentLine: + b.cursor = getLineOffset(b, b.currentLine) + if b.currentLine < b.firstLine: # bring into view: scrollLines(b, b.currentLine - b.firstLine) elif b.currentLine > b.firstLine + b.span-2: scrollLines(b, b.currentLine - (b.firstLine + b.span-2)) - when false: - inc b.currentLine, amount - if b.currentLine < b.firstLine: - assert b.firstLine == b.currentLine+1 - dec b.firstLine - upFirstLineOffset(b) - elif b.currentLine > b.firstLine + b.span-2: - inc b.firstLine - downFirstLineOffset(b) + +proc caretToActiveLine*(b: Buffer) = scroll(b, -1) proc getLine*(b: Buffer): int = b.currentLine proc getColumn*(b: Buffer): int = @@ -210,6 +231,7 @@ proc rawLeft*(b: Buffer) = let r = lastRune(b, b.cursor-1) if r[0] == Rune('\L'): scroll(b, -1) + if b.filterLines: return b.cursor -= r[1] b.desiredCol = getColumn(b) @@ -232,6 +254,7 @@ proc rawRight(b: Buffer) = if b.cursor < b.len: if b[b.cursor] == '\L': scroll(b, 1) + if b.filterLines: return b.cursor += graphemeLen(b, b.cursor) b.desiredCol = getColumn(b) @@ -266,6 +289,7 @@ proc up*(b: Buffer; jump: bool) = i += graphemeLen(b, i) dec col scroll(b, -1) + if b.filterLines: return b.cursor = max(0, i) cursorMoved(b) @@ -276,6 +300,7 @@ proc down*(b: Buffer; jump: bool) = while b.cursor < L: if b[b.cursor] == '\L': scroll(b, 1) + if b.filterLines: return break b.cursor += 1 b.cursor += 1 @@ -470,15 +495,6 @@ proc getSelectedText*(b: Buffer): string = for i in b.selected.a .. b.selected.b: result.add b[i] -proc getLineFromOffset(b: Buffer; pos: int): Natural = - result = 0 - var pos = pos - # do not count the newline at the very end at b[pos]: - if pos >= 0 and b[pos] == '\L': dec pos - while pos >= 0: - if b[pos] == '\L': inc result - dec pos - proc setCaret*(b: Buffer; pos: int) = b.cursor = pos b.currentLine = getLineFromOffset(b, b.cursor) diff --git a/buffertype.nim b/buffertype.nim index f8a797b..e5b102f 100644 --- a/buffertype.nim +++ b/buffertype.nim @@ -1,5 +1,5 @@ -import styles, languages, common +import styles, languages, common, intsets from times import Time type @@ -51,6 +51,9 @@ type indexer*, highlighter*: Indexer cursorDim*: tuple[x, y, h: int] timestamp*: Time + activeLines*: IntSet + filterLines*: bool + minimapVersion*: int proc getCell*(b: Buffer; i: Natural): Cell = if i < b.front.len: diff --git a/drawbuffer.nim b/drawbuffer.nim index 85127cd..97156d2 100644 --- a/drawbuffer.nim +++ b/drawbuffer.nim @@ -5,6 +5,15 @@ const CharBufSize = 80 RoomForMargin = 8 +proc getLineFromOffset(b: Buffer; pos: int): Natural = + result = 0 + var pos = pos + # do not count the newline at the very end at b[pos]: + if pos >= 0 and b[pos] == '\L': dec pos + while pos >= 0: + if b[pos] == '\L': inc result + dec pos + proc drawTexture(r: RendererPtr; font: FontPtr; msg: cstring; fg, bg: Color): TexturePtr = assert font != nil @@ -66,7 +75,10 @@ proc mouseAfterNewLine(b: Buffer; i: int; dim: Rect; maxh: cint) = if b.clicks > 0: if b.mouseX > dim.x and dim.y+maxh > b.mouseY: b.cursor = i - b.currentLine = max(b.firstLine + b.span, 0) + if b.filterLines: + b.currentLine = getLineFromOffset(b, i) + else: + b.currentLine = max(b.firstLine + b.span, 0) if b.clicks > 1: mouseSelectWholeLine(b) b.clicks = 0 cursorMoved(b) @@ -116,7 +128,10 @@ proc drawSubtoken(r: RendererPtr; db: var DrawBuffer; tex: TexturePtr; let p = point(db.b.mouseX, db.b.mouseY) if d.contains(p): db.b.cursor = i + whichColumn(db, ra, rb) - db.b.currentLine = max(db.b.firstLine + db.b.span, 0) + if db.b.filterLines: + db.b.currentLine = getLineFromOffset(db.b, db.b.cursor) + else: + db.b.currentLine = max(db.b.firstLine + db.b.span, 0) if db.b.clicks > 1: mouseSelectCurrentToken(db.b) db.b.clicks = 0 cursorMoved(db.b) @@ -387,6 +402,14 @@ proc spaceForLines*(b: Buffer; t: InternalTheme): Natural = if t.showLines: result = (b.numberOfLines+1).log10 * textSize(t.editorFontPtr, " ") +proc nextLineOffset(b: Buffer; line: var int; start: int): int = + result = start + if b.filterLines: + while line < b.numberOfLines and line notin b.activeLines: + while b[result] != '\L': inc result + inc result + inc line + proc draw*(t: InternalTheme; b: Buffer; dim: Rect; blink: bool; showLines=false) = let realOffset = getLineOffset(b, b.firstLine) @@ -394,7 +417,15 @@ proc draw*(t: InternalTheme; b: Buffer; dim: Rect; blink: bool; # XXX make this a real assertion when tested well echo "real offset ", realOffset, " wrong ", b.firstLineOffset assert false - var i = b.firstLineOffset + var renderLine = b.firstLine + var i = nextLineOffset(b, renderLine, b.firstLineOffset) + when false: + if b.filterLines: + b.firstLineOffset = i + b.firstLine = renderLine + if b.currentLine notin b.activeLines: + b.currentLine = b.firstLine + b.cursor = b.firstLineOffset let endY = dim.y + dim.h - 1 let endX = dim.x + dim.w - 1 var dim = dim @@ -402,20 +433,29 @@ proc draw*(t: InternalTheme; b: Buffer; dim: Rect; blink: bool; dim.h = endY let spl = cint(spaceForLines(b, t) + RoomForMargin) if showLines: - t.drawNumber(b.firstLine+1, b.currentLine+1, spl, dim.y) + t.drawNumber(renderLine+1, b.currentLine+1, spl, dim.y) dim.x = spl b.span = 0 i = t.drawTextLine(b, i, dim, blink) inc b.span let fontSize = t.editorFontSize.cint + let lineH = fontLineSkip(t.editorFontPtr) while dim.y+fontSize < endY and i <= len(b): + inc renderLine + let expectedLine = renderLine + i = nextLineOffset(b, renderLine, i) + if expectedLine != renderLine: + # show the gap: + hlineDotted(t.renderer, dim.x, endY, dim.y+lineH div 4, + t.indentation) + dim.y += lineH div 2 + if showLines: - t.drawNumber(b.firstLine+b.span+1, b.currentLine+1, spl, dim.y) + t.drawNumber(renderLine+1, b.currentLine+1, spl, dim.y) i = t.drawTextLine(b, i, dim, blink) inc b.span # we need to tell the buffer how many lines *can* be shown to prevent # that scrolling is triggered way too early: - let lineH = fontLineSkip(t.editorFontPtr) while dim.y+fontSize < endY: inc dim.y, lineH inc b.span diff --git a/minimaps.nim b/minimaps.nim index 643de87..0cb9367 100644 --- a/minimaps.nim +++ b/minimaps.nim @@ -3,7 +3,7 @@ import buffertype, buffer import - parseutils, strutils, + parseutils, strutils, intsets, compiler/ast, compiler/parser, compiler/llstream, compiler/msgs, @@ -12,27 +12,30 @@ import proc errorHandler(info: TLineInfo; msg: TMsgKind; arg: string) = discard "ignore errors for the minimap generation" -proc allDeclarations(n: PNode; minimap: Buffer) = - proc addDecl(n: PNode; minimap: Buffer) = +proc allDeclarations(n: PNode; minimap: Buffer; useActiveLines: bool) = + proc addDecl(n: PNode; minimap: Buffer; useActiveLines: bool) = if n.info.line >= 0: - let line = $n.info.line - minimap.insert(line & repeat(' ', 6 - line.len) & - renderTree(n, {renderNoBody, renderNoComments, renderDocComments, - renderNoPragmas}).replace("\L")) - minimap.insert("\L") + if useActiveLines: + minimap.activeLines.incl n.info.line-1 + else: + let line = $n.info.line + minimap.insert(line & repeat(' ', 6 - line.len) & + renderTree(n, {renderNoBody, renderNoComments, renderDocComments, + renderNoPragmas}).replace("\L")) + minimap.insert("\L") case n.kind of nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkTemplateDef, nkMacroDef: - addDecl(n, minimap) + addDecl(n, minimap, useActiveLines) of nkConstDef, nkTypeDef: - addDecl(n[0], minimap) + addDecl(n[0], minimap, useActiveLines) of nkIdentDefs: for i in 0..n.len-3: - addDecl(n[i], minimap) + addDecl(n[i], minimap, useActiveLines) of nkStmtList, nkStmtListExpr, nkConstSection, nkLetSection, nkVarSection, nkTypeSection: for i in 0..= 1: minimap.gotoLine(1, -1) diff --git a/nimedit.nim b/nimedit.nim index 3760bb6..559f45e 100644 --- a/nimedit.nim +++ b/nimedit.nim @@ -730,8 +730,14 @@ proc mainProc(ed: Editor) = ed.askForQuitTab() elif w.keysym.sym == ord('m'): if main.lang == langNim: - populateMinimap(ed.minimap, main) - focus = ed.minimap + when true: + main.filterLines = not main.filterLines + if main.filterLines: + filterMinimap(main) + caretToActiveLine main + else: + populateMinimap(ed.minimap, main) + focus = ed.minimap else: ed.statusMsg = "Minimap only supported for Nim." else: discard diff --git a/prims.nim b/prims.nim index 7c3ee8b..af4fce3 100644 --- a/prims.nim +++ b/prims.nim @@ -115,6 +115,13 @@ proc vlineDotted*(renderer: RendererPtr; x: int; y1: int; y2: int; c: Color) = drawPoint(renderer, x.cint, i.cint) inc i, 2 +proc hlineDotted*(renderer: RendererPtr; x1: int; x2: int; y: int; c: Color) = + setDrawColor(renderer, c) + var i = x1 + while i <= x2: + drawPoint(renderer, i.cint, y.cint) + inc i, 2 + proc vline*(renderer: RendererPtr; x: int; y1: int; y2: int; c: Color) = setDrawColor(renderer, c) drawLine(renderer, x.cint, y1.cint, x.cint, y2.cint) diff --git a/scrollbar.nim b/scrollbar.nim index 35ffbb8..8afc6de 100644 --- a/scrollbar.nim +++ b/scrollbar.nim @@ -1,6 +1,6 @@ ## Draws a vertical scrollbar for a buffer. -import buffertype, themes +import buffertype, themes, intsets import sdl2, sdl2/ttf, prims, tabbar const width = 15 @@ -27,7 +27,7 @@ proc drawScrollBar*(b: Buffer; t: InternalTheme; e: var Event; # the algorithm. # Determine how large the content is, and how big our window is - let contentSize = b.numberOfLines.float * fontSize.float + let contentSize = float(b.numberOfLines) * fontSize.float let windowSize = bufferRect.h.float let trackSize = windowSize