diff --git a/src/project.nim b/src/project.nim index a2933f8..1c7a475 100644 --- a/src/project.nim +++ b/src/project.nim @@ -1,7 +1,5 @@ -import std/[algorithm, os, sequtils, sets, strutils, tables, times] - -import bbansi -import utils +import std/[algorithm, os, sequtils, sets, strutils, sugar, tables, times] +import tmuxutils, term type Project* = object @@ -13,22 +11,45 @@ type proc pathToName(path: string): string = splitPath(path)[1].replace(".", "_") -proc newProject(path: string, open: bool): Project = +proc newProject(path: string, open: bool, name = ""): Project = result.location = path - result.name = path.pathToName() + result.name = + if name != "": name + else: path.pathToName() result.updated = getLastModificationTime(path) result.open = open proc newUnknownProject(name: string): Project = result.name = name + result.open = true proc getTsmDirs(): seq[string] = let tsmDirs = getEnv("TSM_DIRS") if tsmDirs == "": - bbEcho "[red]Please set [cyan]$TSM_DIRS[/] to a colon-delimited list of paths" - quit QuitFailure + termQuit "Please set [yellow]$TSM_DIRS[/] to a colon-delimited list of paths" result = tsmDirs.split(":") +proc findDuplicateProjects(paths: seq[string], + sessions: var HashSet[string]): seq[Project] = + var candidates: Table[string, seq[string]] + for p in paths: + candidates[p] = p.split(DirSep) + + let maxExtra = min(candidates.values.toSeq.mapIt(it.len)) + for i in 2..maxExtra: + let deduplicated = collect: + for path, pathSplit in candidates.pairs: + (name: pathSplit[^i..^1].joinPath, path: path) + if deduplicated.mapIt(it[0]).toHashSet.len == candidates.len: + for (name, path) in deduplicated: + let open = name in sessions + result.add newProject(path, open, name) + if open: sessions.excl name + break + + if result.len == 0: + termQuit "failed to deduplicate these paths:" & paths.join(", ") + proc findProjects*(open: bool = false): seq[Project] = var candidates: Table[string, seq[string]] var sessions = tmux.sessions.toHashSet() @@ -36,20 +57,19 @@ proc findProjects*(open: bool = false): seq[Project] = for devDir in getTsmDirs(): for path in walkDir(devDir): if ({path.kind} * {pcFile, pcLinkToFile}).len > 0: continue - let name = path.path.tailDir() - if name in candidates: + let name = path.path.splitPath.tail + if candidates.hasKeyOrPut(name, @[path.path]): candidates[name].add path.path - else: - candidates[name] = @[path.path] - # TODO: improve this to handle duplicate entries by appending parent? - for name, paths in candidates: + for name, paths in candidates.pairs: if len(paths) == 1: - let path = paths[0] - let open = path.pathToName in sessions + let + path = paths[0] + open = path.pathToName in sessions result.add newProject(path, open) - if open: - sessions.excl toHashSet([path.pathToName]) + if open: sessions.excl path.pathToName + else: + result &= findDuplicateProjects(paths, sessions) if open: result = result.filterIt(it.open) @@ -64,7 +84,8 @@ proc findProjects*(open: bool = false): seq[Project] = result = sessions.toSeq().mapIt(newUnknownProject(it)) & result if len(result) == 0: - echo "nothing to select" - quit 1 - + termError "nothing to select, check your [yellow]$TSM_DIRS" + termEcho "searched these directories: " + echo getTsmDirs().mapIt(" " & it).join("\n") + quit QuitFailure diff --git a/src/term.nim b/src/term.nim new file mode 100644 index 0000000..6fb7788 --- /dev/null +++ b/src/term.nim @@ -0,0 +1,17 @@ +import std/strutils +import bbansi + +const + sep = " [magenta]|[/] " + prefix = "[cyan]tsm[/]" & sep + errPrefix = prefix & "[red]error[/]" & sep + +proc termEcho*(x: varargs[string, `$`]) = + bbEcho prefix, x.join(" ") + +proc termError*(x: varargs[string, `$`]) = + bbEcho errPrefix, x.join(" ") + +proc termQuit*(x: varargs[string, `$`]) = + termError x + quit QuitFailure diff --git a/src/utils.nim b/src/tmuxutils.nim similarity index 69% rename from src/utils.nim rename to src/tmuxutils.nim index 665429b..62bd807 100644 --- a/src/utils.nim +++ b/src/tmuxutils.nim @@ -1,5 +1,7 @@ import std/[os, osproc, strformat, strutils] +import term + type Tmux = object active: bool @@ -8,24 +10,31 @@ type proc checkExe(names: varargs[string]) = for name in names: if findExe(name) == "": - echo "tsm requires " & name + termError "tsm requires " & name checkExe "tmux" +proc tmuxError(args: string, output: string = "") = + termError "failed to run: [bold]tmux", args + if output != "": + termError "see below for error" + echo output + quit QuitFailure + + proc cmdGet(tmux: Tmux, args: string): string = let (output, code) = execCmdEx("tmux " & args) - if code != 0: - echo "ERROR: failed to run: tmux ", args, "see below for error" - echo output - quit QuitFailure - return output + if code == 0: return output + tmuxError args, output template cmd(tmux: Tmux, args: string) = - discard execCmd("tmux " & args) + let code = execCmd "tmux " & args + if code != 0: tmuxError(args) + # discard tmux.cmdGet args proc newTmux(): Tmux = result.active = existsEnv("TMUX") - # check if server is active? + # check if server is active if execCmdEx("tmux run").exitCode == 0: result.sessions = ( result.cmdGet "list-sessions -F '#S'" @@ -44,5 +53,4 @@ proc new*(t: Tmux, session: string, loc: string) = else: t.cmd fmt"new-session -s {session} -c {loc}" - let tmux* = newTmux() diff --git a/src/tsm.nim b/src/tsm.nim index 8a8f58e..abc6d35 100644 --- a/src/tsm.nim +++ b/src/tsm.nim @@ -1,6 +1,6 @@ import std/[tables] -import selector, project, utils +import selector, project, tmuxutils proc tsm(open: bool = false) = let