git.haldean.org e / master find.nim
master

Tree @master (Download .tar.gz)

find.nim @masterraw · history · blame

import algorithm
import options
import os
import re
import sets
import sequtils
import strutils

type
  Found = seq[string]
  Score = (float, float)

let exclude = toHashSet([".git", "gen", "inst"])

proc includef(fname: string): bool =
  var x = fname
  while true:
    let (dir, base) = splitPath(x)
    if exclude.contains(base):
      return false
    if base[0] == '.':
      return false
    if dir == "/" or dir == "":
      return true
    x = dir

proc find*(root: string): Found =
  var found = newSeq[string]()
  for file in walkDirRec(root):
    if includef(file):
      found.add(file)
  return found

proc lcss(s1, s2: string): int =
  let
    m1 = s1.len - 1
    m2 = s2.len - 1
  var
    cache = newSeq[seq[int]]()
    maxlen = 0

  for i in 0..m1:
    var s = newSeq[int]()
    for i in 0..m2:
      s.add(0)
    cache.add(s)

  for i1 in 0..m1:
    for i2 in 0..m2:
      if s1[i1] == s2[i2]:
        var x = 0
        if i1 == 0 or i2 == 0:
          x = 1
        else:
          x = cache[i1-1][i2-1] + 1
        cache[i1][i2] = x
        if x > maxlen:
          maxlen = x
  return maxlen

proc score(path, search: string): Score =
  let
    (_, base) = splitPath(path)
    basescore = lcss(search, base).float
    fullscore = lcss(search, path).float
  # In the event of a tie, break the tie by which has the longest match in the
  # basename of the file. Negated so that lower scores are better, so we can
  # ascending-sort on score and file name
  return (-fullscore, -basescore)

proc find*(root, search: string): Found =
  var patstr = ""
  for ch in search:
    patstr = patstr & ".*" & ch
  patstr = patstr & ".*"
  let pat = re(patstr, {reIgnoreCase})

  let found = find(root).filter do (abspath: string) -> bool:
    return match(relativePath(abspath, root), pat)

  var scored = found.map do (abspath: string) -> (Score, string):
    let relpath = relativePath(abspath, root)
    return (score(relpath, search), relpath)

  scored.sort()
  return scored.map do (p: (Score, string)) -> string: p[1]

proc findInCurrent*(): Found =
  return find(getCurrentDir())

proc findInCurrent*(search: string): Found =
  return find(getCurrentDir(), search)

when isMainModule:
  var found: Found
  if paramCount() == 0:
    found = findInCurrent()
  else:
    found = findInCurrent(paramstr(1))
  for f in found:
    echo f

import blit
import buf
import input

type
  FindBuf* = ref object of Buf
    search: string

proc refresh(fb: FindBuf) =
  let found = findInCurrent(fb.search)
  fb.set("find file: " & fb.search & "\n" & found.join("\n"))

proc newFindBuf*(): Buf =
  var fb = FindBuf(blit: newBlit(Size(w: 0, h: 0)))
  fb.refresh()
  return fb

method handlekey*(this: FindBuf, k: Key): bool =
  if k.isEscape():
    return false
  if k.isBackspace():
    this.search = this.search.substr(0, this.search.len - 2)
  else:
    let c = toChar(k)
    if isNone(c):
      return false
    this.search = this.search & c.get()
  this.refresh()
  return true