Skip to content

Commit

Permalink
lines can be skipped; minimap uses this feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Araq committed Oct 23, 2015
1 parent d79c3f5 commit 738cf97
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 47 deletions.
6 changes: 3 additions & 3 deletions NimEdit.nimble
Original file line number Diff line number Diff line change
@@ -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"
56 changes: 36 additions & 20 deletions buffer.nim
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand All @@ -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 =
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion buffertype.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import styles, languages, common
import styles, languages, common, intsets
from times import Time

type
Expand Down Expand Up @@ -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:
Expand Down
52 changes: 46 additions & 6 deletions drawbuffer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -387,35 +402,60 @@ 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)
if b.firstLineOffset != realOffset:
# 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
dim.w = endX
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
Expand Down
37 changes: 24 additions & 13 deletions minimaps.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import buffertype, buffer

import
parseutils, strutils,
parseutils, strutils, intsets,
compiler/ast, compiler/parser,
compiler/llstream,
compiler/msgs,
Expand All @@ -12,34 +12,45 @@ 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..<n.len:
allDeclarations(n[i], minimap)
allDeclarations(n[i], minimap, useActiveLines)
else: discard

proc onEnter*(minimap: Buffer): int =
## returns the line it should be jumped to. -1 if nothing was selected.
result = -1
discard parseInt(minimap.getCurrentLine, result, 0)

proc filterMinimap*(b: Buffer) =
if b.minimapVersion != b.version:
b.minimapVersion = b.version
b.activeLines = initIntSet()
let ast = parser.parseString(b.fullText, b.filename, 0,
errorHandler)
allDeclarations(ast, b, true)

proc populateMinimap*(minimap, buffer: Buffer) =
# be smart and don't do unnecessary work:
if minimap.version != buffer.version or minimap.filename != buffer.filename:
Expand All @@ -50,6 +61,6 @@ proc populateMinimap*(minimap, buffer: Buffer) =
let ast = parser.parseString(buffer.fullText, buffer.filename, 0,
errorHandler)
minimap.clear()
allDeclarations(ast, minimap)
allDeclarations(ast, minimap, false)
if minimap.numberOfLines >= 1:
minimap.gotoLine(1, -1)
10 changes: 8 additions & 2 deletions nimedit.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions prims.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions scrollbar.nim
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down

0 comments on commit 738cf97

Please sign in to comment.