Linecross.nim is a multiline readline replacement library for Nim. In addition to regular editing capability it also has history support including search, callback support for bash style tab completion and callback for custom key handling.
- Cross-platform support - Works on Windows, Linux, Unix, macOS
- History management - Persistent command history with navigation and incremental search
- Tab completion - Customizable completion system with double-tab support
- Cut/paste operations - Internal and system clipboard integration
- Color support - Customizable prompt coloring
- Multiline editing - Intelligent cursor positioning and context-aware navigation
- Callback system - Extensible with custom key handlers and completion functions
- Minimal dependencies - Uses only Nim's standard library (std/terminal)
import linecross
# Initialize the library
initLinecross()
# Simple usage
while true:
let input = readline("nim> ")
if input == "quit":
break
echo "You typed: ", input
# Save history on exit
discard saveHistory("history.txt")# Load existing history
discard loadHistory("myapp_history.txt")
# Configure history
initLinecross(enableHistory = true)
# Custom history callbacks
proc loadMyHistory(): seq[string] =
# Your custom history loading logic
result = @["previous", "commands"]
proc saveMyHistory(entries: seq[string]): bool =
# Your custom history saving logic
return true
registerHistoryLoadCallback(loadMyHistory)
registerHistorySaveCallback(saveMyHistory)# Custom completion callback
proc myCompletions(buffer: string, cursorPos: int, isSecondTab: bool): string =
let commands = ["help", "quit", "save", "load"]
let word = buffer.split(' ')[^1]
for cmd in commands:
if cmd.startsWith(word):
return cmd[word.len..^1] # Return remaining part
if isSecondTab:
# Show available options on second tab
echo ""
echo "Available: ", commands.join(", ")
return ""
return ""
registerCompletionCallback(myCompletions)import std/terminal
# Set prompt color
setPromptColor(fgBlue, {styleBright})
# Or use in readline directly
let input = readline("$ ".fgRed & "nim> ".fgGreen)proc customKeyHandler(keyCode: int, buffer: string): bool =
case keyCode:
of 6: # Ctrl+F
echo "\nSpecial function triggered!"
return true # Key was handled
else:
return false # Let default handler process
registerCustomKeyCallback(customKeyHandler)Left Arrow,Ctrl-B- Move back one characterRight Arrow,Ctrl-F- Move forward one characterHome,Ctrl-A- Move to start of lineEnd,Ctrl-E- Move to end of line
Alt-B- Move back one wordAlt-F- Move forward one wordCtrl-Left- Move back one word (alternative)Ctrl-Right- Move forward one word (alternative)
Backspace- Delete character before cursorDelete,Ctrl-D- Delete character under cursor (Ctrl-D: EOF if empty)Ctrl-K- Cut from cursor to end of lineCtrl-U- Cut from start of line to cursor
Up Arrow,Ctrl-P- Previous command in historyDown Arrow,Ctrl-N- Next command in historyCtrl-R- Incremental reverse history search (if enabled)Ctrl-S- Incremental forward history search (if enabled)
Note: In multiline mode, Up/Down intelligently switch between line navigation and history based on cursor position.
Ctrl-Y- Paste from clipboardCtrl-V- Paste from clipboard (alternative)Insert- Paste from clipboard
Tab- Trigger completion (second tab shows options)Ctrl-L- Clear screen and redisplay lineCtrl-C- Abort current line (exit)Enter- Accept current line
Linecross supports multiline input with intelligent navigation.
Smart Up/Down behavior:
- First line + Up → Navigate to previous history
- Middle lines + Up/Down → Move between lines in current input
- Last line + Down → Navigate to next history
- Any line + Ctrl-P/Ctrl-N → Always navigate history
When enabled with enableHistorySearch = true, linecross enables incremental search through command history:
# Enable during initialization
initLinecross(enableHistory = true, enableHistorySearch = true)
# Use Ctrl-R to start reverse search
# Type characters to search - matches appear in real-time
# Press Enter to accept, Escape to cancelSearch Interface:
(reverse-i-search)`pattern': matching_command_here
Search Controls:
Ctrl-R- Start/continue reverse searchCtrl-S- Start/continue forward searchType characters- Add to search pattern (real-time matching)Backspace- Remove character from patternEnter- Accept current match and executeEscape/Ctrl-G- Cancel search and restore original input
# Basic initialization
initLinecross()
# With history disabled
initLinecross(enableHistory = false)
# With incremental history search enabled
initLinecross(enableHistory = true, enableHistorySearch = true)# Customize what constitutes word boundaries
setDelimiter(" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")Compile with -d:useSystemClipboard to enable system clipboard integration:
nim c -d:useSystemClipboard myapp.nimCustomKeyCallback- Handle custom key combinationsCompletionCallback- Provide tab completionHistoryLoadCallback- Custom history loadingHistorySaveCallback- Custom history saving
registerCustomKeyCallback(proc(key: int, buf: string): bool = ...)
registerCompletionCallback(proc(buf: string, pos: int, secondTab: bool): string = ...)
registerHistoryLoadCallback(proc(): seq[string] = ...)
registerHistorySaveCallback(proc(entries: seq[string]): bool = ...)readline(prompt: string): string- Main input functioninitLinecross(enableHistory: bool = true)- Initialize library
addToHistory(line: string)- Add line to historyloadHistory(filename: string): bool- Load history from filesaveHistory(filename: string): bool- Save history to fileclearHistory()- Clear all history
cutText(startPos, endPos: int)- Cut text range to clipboardpasteText(pos: int): int- Paste at position, return new positioncopyToClipboard(text: string)- Copy to clipboard
moveToWordStart(pos: int): int- Find start of wordmoveToWordEnd(pos: int): int- Find end of word
setPromptColor(color: ForegroundColor, style: set[Style])- Set prompt appearancesetDelimiter(delim: string)- Configure word boundaries
import linecross
proc main() =
initLinecross()
discard loadHistory(".myrepl_history")
echo "Simple REPL - type 'quit' to exit"
while true:
let input = readline("repl> ")
if input == "quit" or input == "":
break
# Process input
echo "Result: ", input.toUpperAscii()
discard saveHistory(".myrepl_history")
main()import linecross, std/[strutils, terminal]
proc completionHandler(buffer: string, cursorPos: int, isSecondTab: bool): string =
let commands = ["help", "list", "create", "delete", "quit"]
let words = buffer.split(' ')
let currentWord = if words.len > 0: words[^1] else: ""
var matches: seq[string] = @[]
for cmd in commands:
if cmd.startsWith(currentWord):
matches.add(cmd)
if matches.len == 1:
return matches[0][currentWord.len..^1] & " "
elif matches.len > 1 and isSecondTab:
echo ""
echo "Available: ", matches.join(", ")
return ""
return ""
proc main() =
initLinecross(enableHistory = true)
registerCompletionCallback(completionHandler)
setPromptColor(fgCyan, {styleBright})
discard loadHistory(".shell_history")
while true:
let input = readline("shell$ ")
case input:
of "quit", "exit": break
of "help":
echo "Commands: help, list, create, delete, quit"
of "clear":
clearScreen()
else:
echo "Unknown command: ", input
discard saveHistory(".shell_history")
main()MIT License