prototypes ensureDelegatedNamespace: #terminals. terminals addPrototype: #Backend derivedFrom: {ExternalResource}. "The device object/handle used for the terminal." Backend addImmutableSlot: #translations valued: {}. Backend addSlot: #ready valued: False. "Where the terminal is ready for input or waiting." _@(Backend traits) defaultColumns [80]. _@(Backend traits) defaultLines [24]. b@(Backend traits) isReady [b ready]. _@(Backend traits) display: lineString withPrompt: prompt cursorAt: index "The basic method for line-oriented display and interaction." [overrideThis]. terminals addPrototype: #Terminal derivedFrom: {Backend}. "The abstract terminal type." t@(Terminal traits) columns []. t@(Terminal traits) lines []. t@(Terminal traits) open []. t@(Terminal traits) close []. t@(Terminal traits) nextChord [ ]. t@(Terminal traits) beep "Send the beep/BEL character." [ t writer nextPut: $\b. t writer flush. ]. t@(Terminal traits) page "Display a simple header and page-down; exit if Q is pressed." [| chord | t writer ; '--More--'. t writer flush. chord: t nextChord. t newLine. (chord = $q or: [chord = $Q]) not ]. t@(Terminal traits) newLine "Terminate the current line." [ t writer ; '\n\r'. t writer flush. ]. t@(Terminal traits) newColumn [ t writer ; ' ' ]. terminals addPrototype: #DumbTerminal derivedFrom: {Terminal}. "The simple terminal type, not even VT100." t@(DumbTerminal traits) display: line withPrompt: prompt cursorAt: index [| string column | string: prompt ; line. column: index + prompt size. t newLine. column + 1 < t columns ifTrue: [t writer nextPutAll: string from: 0 to: (string size min: t columns). string size < t columns ifTrue: [t writer ; ($\s repeatedTimes: t columns - string size)]. t newLine. t writer nextPutAll: string from: 0 to: column] ifFalse: [t writer nextPutAll: string from: column + 1 - t columns to: column. t newLine. t writer nextPutAll: string from: column + 1 - t columns to: column]. t writer flush. ]. terminals addPrototype: #SmartTerminal derivedFrom: {Terminal}. "The terminal type that understands control- and escape-sequences for redraws, etc." SmartTerminal addSlot: #pointLine valued: 0. SmartTerminal addSlot: #activeString valued: ''. t@(Terminal traits) isSmart "Answer whether all of certain cursor movement signals are defined." [ "TODO: Up, Down, EOF, ColAddress, and either AutoRightMargin or EnterAMMode" ]. t@(SmartTerminal traits) open [ resend. ]. terminals addPrototype: #ConsoleLine derivedFrom: {Cloneable}. "Represents a line on the console, with a current position for editing." ConsoleLine addSlot: #lineString valued: ''. ConsoleLine addSlot: #position valued: 0. terminals addPrototype: #ConsoleLineEditor derivedFrom: {ConsoleLine}. "A line editor, used to manage some terminal interactions, and having editing state and behavior." ConsoleLineEditor addSlot: #terminal valued: Console. ConsoleLineEditor addSlot: #history valued: LinkedList newEmpty. ConsoleLineEditor addSlot: #killRing valued: LinkedList newEmpty. ConsoleLineEditor addSlot: #insertMode valued: True. ConsoleLineEditor addSlot: #mark. ConsoleLineEditor addSlot: #currentYank. ConsoleLineEditor addSlot: #lastYank. ConsoleLineEditor addSlot: #basePrompt valued: ''. ConsoleLineEditor addSlot: #currentWord valued: Nil. ConsoleLineEditor addSlot: #currentCompletions valued: {}. e@(ConsoleLineEditor traits) newOn: resource [ e clone on: resource ]. e@(ConsoleLineEditor traits) on: resource [ e terminal: resource. e ]. e@(ConsoleLineEditor traits) prompt [ e basePrompt ]. e@(ConsoleLineEditor traits) redrawLine "Re-run the display sequence for the current line." [ e terminal display: e lineString withPrompt: e prompt cursorAt: e position ]. e@(ConsoleLineEditor traits) nextChord "Process the next chord input." [| chord | e redrawLine. e forgetYank. e handle: e processNextChord. e saveState ]. e@(ConsoleLineEditor traits) toggleInsert "Toggle insert mode." [ e insertMode: e insertMode not ]. e@(ConsoleLineEditor traits) wordStart "Answer the position of the start of the nearest word (right under the cursor)." [| nonStart | nonStart: ((e position isPositive and: [(e lineString at: e position - 1) isDelimiter]) ifTrue: [e lineString indexOfLastSatisfying: [| :c | c isDelimiter not]] ifFalse: [0]). nonStart ifNil: [0] ifNotNil: [(e lineString indexOfLastSatisfying: [| :c | c isDelimiter]) ifNotNilDo: [| :index | index + 1]] ]. e@(ConsoleLineEditor traits) wordEnd "Answer the position of the end of the nearest word (right under the cursor)." [| nonEnd | nonEnd: ((e position < e lineString size and: [(e lineString at: e position) isDelimiter]) ifTrue: [e lineString indexOfFirstSatisfying: [| :c | c isDelimiter not]] ifFalse: [e position]). nonEnd ifNil: [e position] ifNotNil: [(e lineString indexOfFirstSatisfying: [| :c | c isDelimiter]) ifNotNil: [e lineString size]] ]. e@(ConsoleLineEditor traits) word "Answer the word under the cursor, relying on wordStart and wordEnd." [ e lineString copyFrom: e wordStart to: e wordEnd ]. e@(ConsoleLineEditor traits) completionsFor: word "Answer the completions for the given word." [overrideThis]. e@(ConsoleLineEditor traits) flushCompletions [e currentCompletions: e currentCompletions newEmpty]. e@(ConsoleLineEditor traits) complete: word "Answer a collection of the valid completions for a given word; this should allow for varying strategies per context." [ word = e currentWord ifTrue: [e currentCompletions isEmpty ifTrue: [e currentCompletions: (e completionsFor: word)]] ifFalse: [e currentWord: word. e currentCompletions: (e completionsFor: word)] ]. e@(ConsoleLineEditor traits) complete "Answer a collection of valid completions for the word under the cursor." [ e complete: e word ]. e@(ConsoleLineEditor traits) rememberYank [ e currentYank: e position ]. e@(ConsoleLineEditor traits) forgetYank [ e lastYank: e yank. e currentYank: Nil ]. e@(ConsoleLineEditor traits) tryYank [ e currentYank: e lastYank ]. e@(ConsoleLineEditor traits) yank [ e rememberYank. e killRing next ifNil: [e beep. e position] ifNotNilDo: [| :kill | e lineString: (e lineString copyFrom: 0 to: e currentYank) ; kill ; (e lineString copyFrom: e position). e position: e currentYank + kill size] ]. e@(ConsoleLineEditor traits) replace: word "Replace the word under the cursor with the given word." [| start | start: e wordStart. e lineString: (e lineString copyFrom: 0 to: start) ; word ; (e lineString copyFrom: e wordEnd). e position: e wordStart + word size. ]. e@(ConsoleLineEditor traits) isCursorInQuotedString "A simple test to see if the cursor is between quotes." [| start | start: e wordStart. start isNotNil and: [start isPositive] and: [(e lineString at: start - 1) isQuote] ]. "Commands" "TODO: refactor to re-use the UI Command abstraction?" ConsoleLineEditor addPrototype: #Command derivedFrom: {Cloneable}. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor Command traits) [overrideThis]. e@(ConsoleLineEditor traits) addCommand: name "Defines and returns a new attribute-named command object, with no slots. If one already exists, this re-uses it and ensures the slot properties." [| command | command: ((e traits hasSlotNamed: name) ifTrue: [e traits atSlotNamed: name] ifFalse: [e Command clone]). e traits addImmutableSlot: name valued: command. command ]. ConsoleLineEditor addPrototype: #AddCharacter derivedFrom: {ConsoleLineEditor Command}. ConsoleLineEditor AddCharacter addSlot: #char valued: $\0. c@(ConsoleLineEditor AddCharacter traits) newFor: char [| newC | newC: c clone. newC char: char. newC ]. e@(ConsoleLineEditor traits) handle: c@(ConsoleLineEditor AddCharacter traits) [ e lineString: (e lineString copyFrom: 0 to: e position) ; (c char as: String) ; (e lineString copyFrom: e position). e position: e position + 1 ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #DeleteCharBackwards) [ e position = 0 ifTrue: [^ Nil]. e lineString: (e lineString copyFrom: 0 to: e position - 1) ; (e lineString copyFrom: e position). e position: e position - 1 ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #DeleteCharForwards) [ e lineString: (e lineString copyFrom: 0 to: e position) ; (e lineString copyFrom: e position + 1). e position ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #DeleteCharForwardsOrEOF) [ e lineString = '' ifTrue: [e reader signalAtEnd]. e deleteCharForwards: Nil ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #DeleteWordBackwards) [| start | start: e wordStart. e lineString: (e lineString copyFrom: 0 to: start) ; (e lineString copyFrom: e position). e position: start ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #FinishInput) [ ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #UpcaseWord) [| end | end: e wordEnd. e lineString: (e lineString copyFrom: 0 to: e position) ; (e lineString copyFrom: e position to: end) toUppercase ; (e lineString copyFrom: end). e position: end ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #DowncaseWord) [| end | end: e wordEnd. e lineString: (e lineString copyFrom: 0 to: e position) ; (e lineString copyFrom: e position to: end) toLowercase ; (e lineString copyFrom: end). e position: end ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorToBOL) [e position: 0]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorToEOL) [e position: e lineString indexLast]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorCharForwards) [e position: e position + 1]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorCharBackwards) [e position: e position - 1]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorWordForwards) [e position: e wordEnd]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CursorWordBackwards) [e position: e wordStart]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #Undo) [e rewindState]. "FIX" e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #ToggleInsert) [e toggleInsert]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #HistoryPrevious) [ e history previous ifNil: [e beep] ifNotNilDo: [| :prev | e lineString: prev] ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #HistoryNext) [ e history next ifNil: [e beep] ifNotNilDo: [| :next | e lineString: next] ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #KillToEOL) [ e killRing addLast: (e lineString copyFrom: e position). e lineString: (e lineString copyFrom: 0 to: e position). e position ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #KillToBOL) [ e killRing addLast: (e lineString copyFrom: 0 to: e position). e lineString: (e lineString copyFrom: e position). e position: 0 ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #Yank) [ e tryYank ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CopyRegion) [| start end | e mark ifNil: [^ e position]. start: (e mark min: e position). end: (e mark max: e position). e killRing addLast: (e lineString copyFrom: start to: end). e mark: Nil. e position ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #CutRegion) [| start end | e mark ifNil: [^ e position]. start: (e mark min: e position). end: (e mark max: e position). e killRing addLast: (e lineString copyFrom: start to: end). e mark: Nil. e lineString: (e lineString copyFrom: 0 to: start) ; (e lineString copyFrom: end). e position: start ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #SetMark) [ e mark: e position ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #Help) [ ]. e@(ConsoleLineEditor traits) handle: _@(ConsoleLineEditor addCommand: #Complete) [| completions | completions: e complete. completions isEmpty ifTrue: [e beep. ^ Nil]. completions size = 1 ifTrue: [^ (e replace: completions first)]. e printInColumns: completions. ]. {'C-a' -> ConsoleLineEditor CursorToBOL. 'C-b' -> ConsoleLineEditor CursorCharBackwards. 'C-d' -> ConsoleLineEditor DeleteCharForwardsOrEOF. 'C-e' -> ConsoleLineEditor CursorToEOL. 'C-f' -> ConsoleLineEditor CursorCharForwards. 'C-k' -> ConsoleLineEditor KillToEOL. 'C-n' -> ConsoleLineEditor HistoryNext. 'C-p' -> ConsoleLineEditor HistoryPrevious. 'C-u' -> ConsoleLineEditor CutRegion. 'C-y' -> ConsoleLineEditor Yank. 'C--' -> ConsoleLineEditor Undo. 'M-b' -> ConsoleLineEditor CursorWordBackwards. 'M-f' -> ConsoleLineEditor CursorWordForwards. 'M-l' -> ConsoleLineEditor DowncaseWord. 'M-u' -> ConsoleLineEditor UpcaseWord. 'M-w' -> ConsoleLineEditor CopyRegion. 'C-Space' -> ConsoleLineEditor SetMark. 'C-Backspace' -> ConsoleLineEditor DeleteWordBackwards. 'Tab' -> ConsoleLineEditor Complete. 'Backspace' -> ConsoleLineEditor DeleteCharBackwards. 'Return' -> ConsoleLineEditor FinishInput. 'UpArrow' -> ConsoleLineEditor HistoryPrevious. 'DownArrow' -> ConsoleLineEditor HistoryNext. 'RightArrow' -> ConsoleLineEditor CursorCharForwards. 'LeftArrow' -> ConsoleLineEditor CursorCharBackwards. 'Insert' -> ConsoleLineEditor ToggleInsert. 'Delete' -> ConsoleLineEditor DeleteCharBackwards. 'Home' -> ConsoleLineEditor CursorToBOL. 'End' -> ConsoleLineEditor CursorToEOL } do: [| :assoc | ]. Console atSlotNamed: #traits put: DumbTerminal traits. Console addSlotsFrom: DumbTerminal.