Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Overview

Linenoise-Swift

A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Supports

  • Mac OS and Linux
  • Line editing with emacs keybindings
  • History handling
  • Completion
  • Hints

Pure Swift

Implemented in pure Swift, with a Swifty API, this library is easy to embed in projects using Swift Package Manager, and requires no additional dependencies.

Contents

API

Quick Start

Linenoise-Swift is easy to use, and can be used as a replacement for Swift.readLine. Here is a simple example:

") } catch { print(error) } ">
let ln = LineNoise()

do {
	let input = try ln.getLine(prompt: "> ")
} catch {
	print(error)
}
	

Basics

Simply creating a new LineNoise object is all that is necessary in most cases, with STDIN used for input and STDOUT used for output by default. However, it is possible to supply different files for input and output if you wish:

// 'in' and 'out' are standard POSIX file handles
let ln = LineNoise(inputFile: in, outputFile: out)

History

Adding to History

Adding to the history is easy:

") ln.addHistory(input) } catch { print(error) } ">
let ln = LineNoise()

do {
	let input = try ln.getLine(prompt: "> ")
	ln.addHistory(input)
} catch {
	print(error)
}

Limit the Number of Items in History

You can optionally set the maximum amount of items to keep in history. Setting this to 0 (the default) will keep an unlimited amount of items in history.

ln.setHistoryMaxLength(100)

Saving the History to a File

ln.saveHistory(toFile: "/tmp/history.txt")

Loading History From a File

This will add all of the items from the file to the current history

ln.loadHistory(fromFile: "/tmp/history.txt")

History Editing Behavior

By default, any edits by the user to a line in the history will be discarded if the user moves forward or back in the history without pressing Enter. If you prefer to have all edits preserved, then use the following:

ln.preserveHistoryEdits = true

Completion

Completion example

Linenoise supports completion with tab. You can provide a callback to return an array of possible completions:

let ln = LineNoise()

ln.setCompletionCallback { currentBuffer in
    let completions = [
        "Hello, world!",
        "Hello, Linenoise!",
        "Swift is Awesome!"
    ]
    
    return completions.filter { $0.hasPrefix(currentBuffer) }
}

The completion callback gives you whatever has been typed before tab is pressed. Simply return an array of Strings for possible completions. These can be cycled through by pressing tab multiple times.

Hints

Hints example

Linenoise supports providing hints as you type. These will appear to the right of the current input, and can be selected by pressing Return.

The hints callback has the contents of the current line as input, and returns a tuple consisting of an optional hint string and an optional color for the hint text, e.g.:

let ln = LineNoise()

ln.setHintsCallback { currentBuffer in
    let hints = [
        "Carpe Diem",
        "Lorem Ipsum",
        "Swift is Awesome!"
    ]
    
    let filtered = hints.filter { $0.hasPrefix(currentBuffer) }
    
    if let hint = filtered.first {
        // Make sure you return only the missing part of the hint
        let hintText = String(hint.dropFirst(currentBuffer.count))
        
        // (R, G, B)
        let color = (127, 0, 127)
        
        return (hintText, color)
    } else {
        return (nil, nil)
    }
}

Acknowledgements

Linenoise-Swift is heavily based on the original linenoise library by Salvatore Sanfilippo (antirez)

Comments
  • Bugfix history navigation after adding duplicate lines

    Bugfix history navigation after adding duplicate lines

    This fixes a small bug caused by the history index not always being reset.

    So a user could go through a session like this:

    > 1    # user typed 1, then enter
    > 2    # user typed 2, then enter
    > 2    # user pressed key-up, then enter
    > 1    # user pressed key-up again and expected 2 but this time the history index was off
    
    opened by tlk 2
  • Update Nimble dependency to 8.0.0..<9.0.0

    Update Nimble dependency to 8.0.0..<9.0.0

    Linenoise depends on Nimble, and specifies 7.0.1 upwards as the required version. This creates a conflict in packages that also depend on nimble, but are using the more recent 8.* versions.

    For example, the following simple Package.swift...

    // swift-tools-version:5.1
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "swiftpm",
        dependencies: [
            .package(url: "https://github.com/andybest/linenoise-swift.git", from: "0.0.0"),
            .package(url: "https://github.com/Quick/Nimble.git", from: "8.0.0")
        ],
        targets: [which this package depends on.
            .target(
                name: "swiftpm",
                dependencies: ["LineNoise", "Nimble"]),
            .testTarget(
                name: "swiftpmTests",
                dependencies: ["swiftpm"]),
        ]
    )
    

    ... fails with the error:

    error: the package dependency graph could not be resolved; possibly because of these requirements:    https://github.com/andybest/linenoise-swift.git @ 0.0.0..<1.0.0
    
    opened by phlippieb 1
  • Xcode 10.1 support?

    Xcode 10.1 support?

    Have you had any luck with LineNoise in the Xcode 10.1 debug console? It is reporting itself for me as TERM=screen, but it's not honoring any escape codes, and so I just get the codes printed in the console. It used to work fine in Xcode 9.

    If I run an app from the command line it works fine, it's only in the Xcode debug console that I'm having trouble.

    opened by ewanmellor 1
  • Add an option to preserve user edits to history.

    Add an option to preserve user edits to history.

    This adds a new flag, LineNoise.preserveHistoryEdits. The default is to follow existing behavior, which is that any edits by the user to a line in the history will be discarded if the user moves forward or back in the history without pressing Enter. Setting this flag to true, will cause all history edits will be preserved, even if the user is just moving backwards and forwards in the history. This matches bash's (presumably readline's) behavior.

    opened by ewanmellor 1
  • Report the chosen mode as LineNoise.mode.

    Report the chosen mode as LineNoise.mode.

    Change the way that LineNoise is initialized, so that we decide immediately what mode we need to use (i.e. which path in getLine we are going to take). Add an enum LineNoise.Mode with unsupportedTTY / supportedTTY / notATTY as options. Add LineNoise.mode to report the value that we chose.

    This has two advantages: 1) we don't check isatty and ProcessInfo.processInfo.environment["TERM"] with every call to getLine, and 2) client code can use the mode if it wants to behave differently for each case.

    Merge the two initializers at the same time (one was just setting default args for the other).

    opened by ewanmellor 1
  • Terminal escape sequences under Xcode

    Terminal escape sequences under Xcode

    When running under Xcode, the output gets pretty messed up due to the use of (for Xcode unknown) terminal escape sequences. It would be great if linenoise could then fall back to a dumb line edit mode for better debugging from within Xcode.

    opened by mickeyl 0
  • Feature request and bug report

    Feature request and bug report

    Hi Andy. I have a feature request and a bug report.

    1. Feature request: would it be possible to support specifying a custom colour for the prompt?

    2. Bug report: if the prompt or the input contain a non-ascii character the cursor disappears and reappears at the top of the terminal while the character itself is displayed as a garbled collection of other non-ascii characters.

    Thanks for your time.

    opened by dmulholl 0
  • Add Ctrl-H and Ctrl-F handlers to fix observed issues

    Add Ctrl-H and Ctrl-F handlers to fix observed issues

    Hi. Thanks for this library!

    This pull request fixes two issues.

    1. Missing Ctrl-H handler. This usually works like backspace, deleting the character to the left. Observed behaviour is odd - it deletes the character to the left but keeps moving the cursor further to the right. Cloning your existing backspace handler fixes the issue.

    2. Missing Ctrl-F handler. This usually moves the cursor to the right. This actually does work even without a dedicated handler but the cursor keeps moving to the right beyond the end of the string as it bypasses the location == buffer.endIndex check in EditState. Adding a Ctrl-F handler modeled on Ctrl-B fixes the issue.

    I think my editor also deleted a ton of whitespace on empty lines, polluting the diff. Sorry about that!

    opened by dmulholl 0
  • Throw EOF if readLine returns nil.

    Throw EOF if readLine returns nil.

    Throw EOF if readLine returns nil, rather than returning the empty string. This fixes the semantics in unsupportedTTY mode to match the supportedTTY mode. (EOF on unsupported TTYs can occur in the Xcode console if the user closes the stream using Ctrl-D.)

    opened by ewanmellor 0
  • Multibyte UTF-8 characters break the line editor

    Multibyte UTF-8 characters break the line editor

    Description

    I am writing a REPL for the Lambda Calculus and I incorporated line noise-Swift to provide line editing functionality. Unfortunately, The Greek letter lambda (λ) is encoded in UTF-8 as two bytes: CE BB. linenoise-swift handles input one byte at a time and tries to split the λ. The same problem occurs for any Unicode code point that takes more than one byte to stop in UTF-8, i.e. everything except 7-bit US ASCII.

    How to Reproduce

    Run the linenoiseDemo command line app. Type in a few characters and then a λ. The cursor will be repositioned at the start of the line and garbage appended to the end of the line. Here is an example:

    Type 'exit' to quit
     gdggfdsgdsλ
    utput: gdggfdsgdsλ
    ? 
    

    If you are having trouble producing a λ from your keyboard, the problem still manifests if you copy-paste it from the text of this issue.

    Further Information

    I made an attempt to fix the issue myself. You can see my attempt here. The patch is a lot bigger than you might expect because adding support for multibyte UTF-8 exposes another more subtle bug.

    Consider the following code in class EditLine

        func insertCharacter(_ char: Character) {
            let origLoc = location
            let origEnd = buffer.endIndex
            buffer.insert(char, at: location)
            location = buffer.index(after: location)
            
            if origLoc == origEnd {
                location = buffer.endIndex
            }
        }
    

    The Apple Documentation for insert(_:, at:) says

    Calling this method invalidates any existing indices for use with this string

    This means that location, origLoc and origEnd are all invalid after the insert. If it's a single byte character we get away with it. If not, location ends up as a garbage value and causes a process abort when it is next used. I ended up changing the types of buffer to [Character] and location to Int as the easy way out.

    NB I can give you a pull request or a patch, if it helps, but it hasn't been extensively tested and probably still breaks with composed characters e.g. emoji.

    opened by jeremy-pereira 1
Owner
Andy Best
Andy Best
SwiftyTextTable - A lightweight Swift library for generating text tables

SwiftyTextTable A lightweight Swift library for generating text tables. Swift Language Support SwiftyTextTable is now Swift 4.0 compatible! The last r

Scott Hoyt 283 Dec 23, 2022
A library and CLI Utility to manage NVRAM Stuff, written in Swift.

NVRAMKit A Library and CLI Utility to manage NVRAM Stuff, written in Swift. Library Adding Library to Project Simply add this line to the dependencies

Serena 7 Sep 25, 2022
Swift tool to generate Module Interfaces for Swift projects.

ModuleInterface Swift tool to generate Module Interfaces for Swift projects. What is a Module Interface A Module Interface is what we commonly get usi

Jorge Revuelta 75 Dec 21, 2022
Swift-cli - Example of building command-line tools in Swift

swift-cli Example of building command-line tools in Swift Step 1: Create CLI wit

Noah Gift 2 Jan 17, 2022
Compose beautiful command line interfaces in Swift

Commander is a small Swift framework allowing you to craft beautiful command line interfaces in a composable way. Usage Simple Hello World i

Kyle Fuller 1.5k Dec 29, 2022
Guaka - Smart and beautiful POSIX compliant CLI framework for Swift.

Guaka - Smart and beautiful POSIX compliant CLI framework for Swift. It helps you create modern and familiar CLI apps in the vein of widely used proje

Omar Abdelhafith 1.1k Dec 24, 2022
Progress.swift ⌛ Add beautiful progress bars to your loops.

Progress.swift ⌛ Just wrap the SequenceType in your loop with the Progress SequenceType and you'll automatically get beautiful progress bars. Updating

Justus Kandzi 304 Dec 1, 2022
Straightforward, type-safe argument parsing for Swift

Swift Argument Parser Usage Begin by declaring a type that defines the information that you need to collect from the command line. Decorate each store

Apple 2.9k Jan 7, 2023
SwiftCLI - A powerful framework for developing CLIs in Swift

SwiftCLI A powerful framework for developing CLIs, from the simplest to the most complex, in Swift.

Jake Heiser 793 Jan 4, 2023
SwiftShell - A Swift framework for shell scripting.

Run shell commands | Parse command line arguments | Handle files and directories Swift 5.1 - 5.3 | Swift 4 | Swift 3 | Swift 2 SwiftShell A library fo

Kare Morstol 973 Jan 2, 2023
Shell scripting in Swift

Shwift Shell-scripting in Swift DISCLAIMER: Shwift depends on Swift's incoming concurrency features. As such, it requires a recent Swift toolchain, an

George Lyon 32 Sep 15, 2022
Swift utilities for running commands.

Swift Commands Swift utilities for running commands. The Commands module allows you to take a system command as a string and return the standard outpu

Phil 41 Jan 2, 2023
A CLI too powered by Swift to provision environments using an up.toml manifest file

tuist-up tuist up was originally a Tuist built-in command to provision environments by reading the requirements in a manifest file Setup.swift. Althou

Tuist 5 Mar 31, 2022
Terminal string styling for Swift.

Terminal string styling for Swift. Integration Swift Package Manager (SPM) You can use The Swift Package Manager to install ColorizeSwift by adding it

Michał Tynior 281 Dec 22, 2022
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 2022
✏️Expressive styling on terminal string. (chalk for swift)

Chalk Expressive styling on terminal string. Highlights Expressive API 256/TrueColor support Nest styles Auto downgrading to terminal supported color

Luo Xiu 59 Jun 10, 2022
A starting point to create CLI utilities with swift

cli tuist template A starting point to create CLI utilities with swift Installation Just create a Tuist folder and a Templates folder inside it. Creat

humdrum 6 May 3, 2022
A Tuist Template to quickly create CLI apps in Swift

macOS CLI Template Motivation I'm writing more and more Swift CLI apps these days. And as I solve more problems with this litte tools, I find that I'm

Diego Freniche 21 Dec 28, 2022
A cli program written in swift (with async/await) that removes the unnecessary parts of xcframeworks.

xctrim A cli program written in swift (with async/await) that removes the unnecessary parts of xcframeworks. Usecase Say you downloaded firebase sdk a

Mustafa Yusuf 32 Jul 18, 2022