Last active
August 29, 2015 14:16
-
-
Save vbfox/46a832b8ee6bbb7b2fcd to your computer and use it in GitHub Desktop.
A line-for-line port of getline.cs by Miguel de Icaza to F#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// getline.fs: A command line editor | |
// | |
// Authors: | |
// Miguel de Icaza ([email protected]) | |
// Julien Roncaglia ([email protected]) | |
// | |
// Copyright 2008 Novell, Inc. | |
// Copyright 2015 Julien Roncaglia <[email protected]> | |
// | |
// Dual-licensed under the terms of the MIT X11 license or the | |
// Apache License 2.0 | |
// | |
// TODO: | |
// Enter an error (a = 1); Notice how the prompt is in the wrong line | |
// This is caused by Stderr not being tracked by System.Console. | |
// Completion support | |
// Why is Thread.Interrupt not working? Currently I resort to Abort which is too much. | |
// | |
// Limitations in System.Console: | |
// Console needs SIGWINCH support of some sort | |
// Console needs a way of updating its position after things have been written | |
// behind its back (P/Invoke puts for example). | |
// System.Console needs to get the DELETE character, and report accordingly. | |
// | |
namespace Mono.Terminal | |
module GetLine = | |
open System | |
open System.Text | |
open System.IO | |
open System.Threading | |
open System.Reflection | |
type Completion = { Result : string list; Prefix : string } | |
type AutoCompleteHandler = delegate of string * int -> Completion | |
type KeyHandler = delegate of unit -> unit | |
type Command = | |
| Done = 1 | |
| Home = 2 | |
| End = 3 | |
| Left = 4 | |
| Right = 5 | |
| HistoryPrev = 6 | |
| HistoryNext = 7 | |
| Backspace = 8 | |
| TabOrComplete = 9 | |
| Yank = 10 | |
| DeleteChar = 11 | |
| Refresh = 12 | |
| ReverseSearch = 13 | |
| BackwardWord = 14 | |
| ForwardWord = 15 | |
| DeleteWord = 16 | |
| DeleteBackword = 17 | |
| Quote = 18 | |
| CmdKillToEOF = 19 | |
type Handler(cmd : Command, cki : ConsoleKeyInfo, h : KeyHandler) = | |
member val HandledCommand = cmd | |
member val CKI = cki | |
member val KeyHandler = h | |
new(cmd : Command, key, h : KeyHandler) = Handler(cmd, new ConsoleKeyInfo((char) 0, key, false, false, false), h) | |
new(cmd : Command, c, h : KeyHandler) = Handler(cmd, new ConsoleKeyInfo (c, ConsoleKey.Zoom, false, false, false), h) | |
static member Alt cmd c k h = Handler (cmd, new ConsoleKeyInfo (c, k, false, true, false), h) | |
static member Control cmd (c : char) h = Handler (cmd, (char) ((int)c - (int)'A' + 1), h) | |
/// Emulates the bash-like behavior, where edits done to the | |
/// history are recorded | |
type History(app : string, size : int) as x = | |
let mutable history : string array = Array.zeroCreate size | |
let mutable histfile = null | |
let mutable head = 0 | |
let mutable tail = 0 | |
let mutable cursor = 0 | |
let mutable count = 0 | |
do | |
if app <> null then | |
let dir = Environment.GetFolderPath (Environment.SpecialFolder.Personal) | |
histfile <- Path.Combine (dir, ".mal-history"); | |
if File.Exists histfile then | |
use sr = File.OpenText histfile | |
let mutable line = "" | |
let mutable cont = true | |
while cont do | |
line <- sr.ReadLine () | |
if line <> null then | |
if line <> "" then x.Append line | |
else | |
cont <- false | |
member x.Close () = | |
if histfile <> null then | |
use sw = File.CreateText histfile | |
let start = if count = history.Length then head else tail | |
for i = start to start + count - 1 do | |
let p = i % history.Length | |
sw.WriteLine history.[p] | |
/// Appends a value to the history | |
member x.Append s = | |
history.[head] <- s | |
head <- (head+1) % history.Length | |
if head = tail then | |
tail <- (tail+1 % history.Length) | |
if count <> history.Length then | |
count <- count + 1 | |
/// Updates the current cursor location with the string, | |
/// to support editing of history items. For the current | |
/// line to participate, an Append must be done before. | |
member x.Update s = | |
history.[cursor] <- s | |
member x.RemoveLast () = | |
head <- head-1; | |
if head < 0 then | |
head <- history.Length - 1 | |
member x.Accept s = | |
let t = if head-1 >= 0 then head-1 else history.Length - 1 | |
history.[t] <- s; | |
member x.PreviousAvailable () = | |
if count = 0 then | |
false | |
else | |
let next = if cursor-1 >= 0 then cursor-1 else count-1 | |
not (next = head) | |
member x.NextAvailable () = | |
if count = 0 then | |
false | |
else | |
let next = (cursor + 1) % history.Length | |
not (next = head) | |
/// Returns: a string with the previous line contents, or | |
/// nul if there is no data in the history to move to. | |
member x.Previous () = | |
if not (x.PreviousAvailable()) then | |
null | |
else | |
cursor <- cursor - 1 | |
if cursor < 0 then | |
cursor <- history.Length - 1 | |
history.[cursor] | |
member x.Next () = | |
if not (x.NextAvailable()) then | |
null | |
else | |
cursor <- (cursor + 1) % history.Length | |
history.[cursor] | |
member x.CursorToEnd () = | |
if head <> tail then | |
cursor <- head | |
member x.Dump () = | |
Console.WriteLine ("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count); | |
for i = 0 to history.Length - 1 do | |
let arrow = if i = cursor then "==>" else " " | |
Console.WriteLine (" {0} {1}: {2}", arrow, i, history.[i]); | |
member x.SearchBackward (term:string) = | |
let mutable i = 0 | |
let mutable found = false | |
while (i < count && not found) do | |
let mutable slot = cursor-i-1; | |
if slot < 0 then | |
slot <- history.Length+slot; | |
if slot >= history.Length then | |
slot <- 0 | |
if (history.[slot] <> null && history.[slot].IndexOf (term) <> -1) then | |
cursor <- slot; | |
found <- true | |
i <- i + 1 | |
if found then history.[cursor] else null | |
type LineEditor (name : string, histsize : int) as x = | |
// The text being edited. | |
let mutable text = new StringBuilder () | |
// The text as it is rendered (replaces (char)1 with ^A on display for example). | |
let rendered_text = new StringBuilder () | |
// The prompt specified, and the prompt shown to the user. | |
let mutable specified_prompt : string = null | |
let mutable shown_prompt : string = null | |
// The current cursor position, indexes into "text", for an index | |
// into rendered_text, use TextToRenderPos | |
let mutable cursor : int = 0 | |
// The row where we started displaying data. | |
let mutable home_row : int = 0 | |
// The maximum length that has been displayed on the screen | |
let mutable max_rendered: int = 0 | |
// If we are done editing, this breaks the interactive loop | |
let mutable done_editing = false | |
// The thread where the Editing started taking place | |
let mutable edit_thread : Thread = null | |
// Our object that tracks history | |
let history = new History (name, histsize) | |
// The contents of the kill buffer (cut/paste in Emacs parlance) | |
let mutable kill_buffer = ""; | |
// The string being searched for | |
let mutable search : string = null | |
let mutable last_search : string = null | |
// whether we are searching (-1= reverse; 0 = no; 1 = forward) | |
let mutable searching : int = 0 | |
// The position where we found the match. | |
let mutable match_at : int = 0 | |
// Used to implement the Kill semantics (multiple Alt-Ds accumulate) | |
let mutable last_command : Command = new Command() | |
/// Invoked when the user requests auto-completion using the tab character | |
[<DefaultValue>] | |
val mutable public AutoCompleteEvent : AutoCompleteHandler | |
let mutable handlers : Handler array = Array.zeroCreate 0 | |
new(name) = LineEditor(name, 10) | |
do | |
handlers <- | |
[| | |
new Handler (Command.Done, ConsoleKey.Enter, new KeyHandler(x.CmdDone)) | |
new Handler (Command.Home,ConsoleKey.Home, new KeyHandler(x.CmdHome)) | |
new Handler (Command.End,ConsoleKey.End, new KeyHandler(x.CmdEnd)) | |
new Handler (Command.Left,ConsoleKey.LeftArrow, new KeyHandler(x.CmdLeft)) | |
new Handler (Command.Right,ConsoleKey.RightArrow, new KeyHandler(x.CmdRight)) | |
new Handler (Command.HistoryPrev,ConsoleKey.UpArrow, new KeyHandler(x.CmdHistoryPrev)) | |
new Handler (Command.HistoryNext,ConsoleKey.DownArrow, new KeyHandler(x.CmdHistoryNext)) | |
new Handler (Command.Backspace,ConsoleKey.Backspace, new KeyHandler(x.CmdBackspace)) | |
new Handler (Command.DeleteChar,ConsoleKey.Delete, new KeyHandler(x.CmdDeleteChar)) | |
new Handler (Command.TabOrComplete,ConsoleKey.Tab, new KeyHandler(x.CmdTabOrComplete)) | |
// Emacs keys | |
Handler.Control Command.Home 'A' (new KeyHandler(x.CmdHome)) | |
Handler.Control Command.End 'E' (new KeyHandler(x.CmdEnd)) | |
Handler.Control Command.Left 'B' (new KeyHandler(x.CmdLeft)) | |
Handler.Control Command.Right 'F' (new KeyHandler(x.CmdRight)) | |
Handler.Control Command.HistoryPrev 'P' (new KeyHandler(x.CmdHistoryPrev)) | |
Handler.Control Command.HistoryNext 'N' (new KeyHandler(x.CmdHistoryNext)) | |
Handler.Control Command.CmdKillToEOF 'K' (new KeyHandler(x.CmdKillToEOF)) | |
Handler.Control Command.Yank 'Y' (new KeyHandler(x.CmdYank)) | |
Handler.Control Command.DeleteChar 'D' (new KeyHandler(x.CmdDeleteChar)) | |
Handler.Control Command.Refresh 'L' (new KeyHandler(x.CmdRefresh)) | |
Handler.Control Command.ReverseSearch 'R' (new KeyHandler(x.CmdReverseSearch)) | |
Handler.Alt Command.BackwardWord 'B' ConsoleKey.B (new KeyHandler(x.CmdBackwardWord)) | |
Handler.Alt Command.ForwardWord 'F' ConsoleKey.F (new KeyHandler(x.CmdForwardWord)) | |
Handler.Alt Command.DeleteWord 'D' ConsoleKey.D (new KeyHandler(x.CmdDeleteWord)) | |
Handler.Alt Command.DeleteBackword ((char)8) ConsoleKey.Backspace (new KeyHandler(x.CmdDeleteBackword)) | |
// DEBUG | |
//Handler.Control ('T', CmdDebug), | |
// quote | |
Handler.Control Command.Quote 'Q' (new KeyHandler (fun () -> x.HandleChar ((Console.ReadKey (true)).KeyChar))) | |
|] | |
member private x.CmdDebug () = | |
history.Dump () | |
Console.WriteLine () | |
x.Render () | |
member private x.Render () = | |
Console.Write (shown_prompt); | |
Console.Write (rendered_text); | |
let max = System.Math.Max (rendered_text.Length + shown_prompt.Length, max_rendered); | |
for i = rendered_text.Length + shown_prompt.Length to max_rendered - 1 do | |
Console.Write (' '); | |
max_rendered <- shown_prompt.Length + rendered_text.Length | |
// Write one more to ensure that we always wrap around properly if we are at the | |
// end of a line. | |
Console.Write (' ') | |
x.UpdateHomeRow (max) | |
member private x.UpdateHomeRow screenpos = | |
let lines = 1 + (screenpos / Console.WindowWidth); | |
home_row <- Console.CursorTop - (lines - 1); | |
if home_row < 0 then | |
home_row <- 0 | |
member private x.RenderFrom pos = | |
let rpos = x.TextToRenderPos (pos) | |
let mutable i = rpos; | |
while i < rendered_text.Length do | |
Console.Write (rendered_text.[i]) | |
i <- i + 1 | |
if (shown_prompt.Length + rendered_text.Length) > max_rendered then | |
max_rendered <- shown_prompt.Length + rendered_text.Length | |
else | |
let max_extra = max_rendered - shown_prompt.Length | |
while i < max_extra do | |
Console.Write (' ') | |
i <- i + 1 | |
member private x.ComputeRendered () = | |
rendered_text.Length <- 0; | |
for i = 0 to text.Length - 1 do | |
let c = (int) text.[i]; | |
if c < 26 then | |
if c = (int)'\t' then | |
rendered_text.Append (" ") |> ignore | |
else | |
rendered_text.Append ('^') |> ignore | |
rendered_text.Append ((char) (c + (int) 'A' - 1)) |> ignore | |
else | |
rendered_text.Append ((char)c) |> ignore | |
member private x.TextToRenderPos pos = | |
let mutable p = 0; | |
for i = 0 to pos - 1 do | |
let c = (int) text.[i]; | |
if c < 26 then | |
if c = 9 then | |
p <- p + 4; | |
else | |
p <- p + 2; | |
else | |
p <- p + 1 | |
p | |
member private x.TextToScreenPos pos = shown_prompt.Length + x.TextToRenderPos (pos) | |
member private x.LineCount with get () = (shown_prompt.Length + rendered_text.Length)/Console.WindowWidth | |
member private x.ForceCursor newpos = | |
cursor <- newpos; | |
let actual_pos = shown_prompt.Length + x.TextToRenderPos (cursor) | |
let row = home_row + (actual_pos/Console.WindowWidth) | |
let row = if row < Console.BufferHeight then row else Console.BufferHeight-1 | |
let col = actual_pos % Console.WindowWidth | |
Console.SetCursorPosition (col, row); | |
member private x.UpdateCursor newpos = | |
if cursor <> newpos then | |
x.ForceCursor newpos | |
member private x.InsertChar (c:char) = | |
let prev_lines = x.LineCount | |
text.Insert (cursor, c) |> ignore | |
x.ComputeRendered () | |
if prev_lines <> x.LineCount then | |
Console.SetCursorPosition (0, home_row) | |
x.Render () | |
cursor <- cursor + 1 | |
x.ForceCursor cursor | |
else | |
x.RenderFrom (cursor); | |
cursor <- cursor + 1 | |
x.ForceCursor cursor | |
x.UpdateHomeRow (x.TextToScreenPos (cursor)); | |
member private x.CmdDone () = | |
done_editing <- true | |
member val TabAtStartCompletes : bool = false | |
member private x.CmdTabOrComplete () = | |
let mutable complete = false; | |
if x.AutoCompleteEvent <> null then | |
if x.TabAtStartCompletes then | |
complete <- true | |
else | |
let mutable i = 0 | |
while i < cursor && not complete do | |
if not (Char.IsWhiteSpace (text.[i])) then | |
complete <- true | |
if complete then | |
let completion = x.AutoCompleteEvent.Invoke (text.ToString (), cursor) | |
let completions = completion.Result | |
if completions.Length <> 0 then | |
let ncompletions = completions.Length | |
if completions.Length = 1 then | |
x.InsertTextAtCursor (completions.[0]) | |
else | |
let mutable last = -1 | |
let mutable p = 0 | |
let mutable mismatch = false | |
while p < completions.[0].Length && not mismatch do | |
let c = completions.[0].[p] | |
let mutable i = 1 | |
while i < ncompletions && not mismatch do | |
if completions.[i].Length < p then mismatch <- true | |
if completions.[i].[p] <> c then mismatch <- true | |
if not mismatch then | |
last <- p; | |
p <- p + 1 | |
if last <> -1 then | |
x.InsertTextAtCursor (completions.[0].Substring (0, last+1)) | |
Console.WriteLine () | |
for s in completions do | |
Console.Write (completion.Prefix) | |
Console.Write (s) | |
Console.Write (' ') | |
Console.WriteLine () | |
x.Render () | |
x.ForceCursor (cursor) | |
else | |
x.HandleChar ('\t') | |
else | |
x.HandleChar ('t') | |
member private x.CmdHome () = x.UpdateCursor (0) | |
member private x.CmdEnd () = x.UpdateCursor (text.Length) | |
member private x.CmdLeft () = if cursor <> 0 then x.UpdateCursor (cursor-1) | |
member private x.CmdBackwardWord () = | |
let p = x.WordBackward (cursor) | |
if p <> -1 then x.UpdateCursor (p) | |
member private x.CmdForwardWord () = | |
let p = x.WordForward (cursor) | |
if p <> -1 then x.UpdateCursor (p); | |
member private x.CmdRight () = | |
if (cursor <> text.Length) then x.UpdateCursor (cursor+1); | |
member private x.RenderAfter p = | |
x.ForceCursor (p) | |
x.RenderFrom (p) | |
x.ForceCursor (cursor) | |
member private x.CmdBackspace () = | |
if (cursor <> 0) then | |
cursor <- cursor - 1 | |
text.Remove (cursor, 1) |> ignore | |
x.ComputeRendered () | |
x.RenderAfter (cursor) | |
member private x.CmdDeleteChar () = | |
// If there is no input, this behaves like EOF | |
if text.Length = 0 then | |
done_editing <- true | |
text <- null | |
Console.WriteLine () | |
else if (cursor <> text.Length) then | |
text.Remove (cursor, 1) |> ignore | |
x.ComputeRendered () | |
x.RenderAfter (cursor) | |
member private x.WordForward p = | |
if (p >= text.Length) then | |
-1 | |
else | |
let mutable i = p; | |
if Char.IsPunctuation (text.[p]) || Char.IsSymbol (text.[p]) || Char.IsWhiteSpace (text.[p]) then | |
while (i < text.Length && not (Char.IsLetterOrDigit (text.[i]))) do i <- i + 1 | |
while (i < text.Length && Char.IsLetterOrDigit (text.[i])) do i <- i + 1 | |
else | |
while (i < text.Length && Char.IsLetterOrDigit (text.[i])) do i <- i + 1 | |
if i <> p then i else -1 | |
member private x.WordBackward p = | |
if p = 0 then | |
-1 | |
else if p = 1 then | |
0 | |
else | |
let mutable i = p-1; | |
if Char.IsPunctuation (text.[i]) || Char.IsSymbol (text.[i]) || Char.IsWhiteSpace (text.[i]) then | |
while (i >= 0 && not (Char.IsLetterOrDigit (text.[i]))) do i <- i - 1 | |
while (i >= 0 && Char.IsLetterOrDigit (text.[i])) do i <- i - 1 | |
else | |
while (i >= 0 && Char.IsLetterOrDigit (text.[i])) do i <- i - 1 | |
i <- i + 1 | |
if i <> p then i else -1 | |
member private x.CmdDeleteWord () = | |
let pos = x.WordForward (cursor) | |
if pos <> -1 then | |
let k = text.ToString (cursor, pos-cursor) | |
if last_command = Command.DeleteWord then | |
kill_buffer <- kill_buffer + k; | |
else | |
kill_buffer <- k; | |
text.Remove (cursor, pos-cursor) |> ignore | |
x.ComputeRendered () | |
x.RenderAfter (cursor) | |
member private x.CmdDeleteBackword () = | |
let pos = x.WordBackward (cursor); | |
if pos <> -1 then | |
let k = text.ToString (pos, cursor-pos) | |
if last_command = Command.DeleteBackword then | |
kill_buffer <- k + kill_buffer; | |
else | |
kill_buffer <- k; | |
text.Remove (pos, cursor-pos) |> ignore | |
x.ComputeRendered () | |
x.RenderAfter (pos) | |
// | |
// Adds the current line to the history if needed | |
// | |
member private x.HistoryUpdateLine () = | |
history.Update (text.ToString ()) | |
member private x.CmdHistoryPrev () = | |
if history.PreviousAvailable () then | |
x.HistoryUpdateLine () | |
x.SetText (history.Previous ()) | |
member private x.CmdHistoryNext () = | |
if history.NextAvailable() then | |
history.Update (text.ToString ()) | |
x.SetText (history.Next ()); | |
member private x.CmdKillToEOF () = | |
kill_buffer <- text.ToString (cursor, text.Length-cursor); | |
text.Length <- cursor; | |
x.ComputeRendered (); | |
x.RenderAfter (cursor); | |
member private x.CmdYank () = | |
x.InsertTextAtCursor (kill_buffer) | |
member private x.InsertTextAtCursor str = | |
let prev_lines = x.LineCount; | |
text.Insert (cursor, str) |> ignore | |
x.ComputeRendered () | |
if prev_lines <> x.LineCount then | |
Console.SetCursorPosition (0, home_row) | |
x.Render () | |
cursor <- cursor + str.Length | |
x.ForceCursor (cursor) | |
else | |
x.RenderFrom (cursor) | |
cursor <- cursor + str.Length | |
x.ForceCursor (cursor) | |
x.UpdateHomeRow (x.TextToScreenPos (cursor)) | |
member private x.SetSearchPrompt s = | |
x.SetPrompt ("(reverse-i-search)`" + s + "': ") | |
member private x.ReverseSearch () = | |
let mutable search_backward = true | |
if cursor = text.Length then | |
// The cursor is at the end of the string | |
let p = (text.ToString()).LastIndexOf (search) | |
if p <> -1 then | |
match_at <- p | |
cursor <- p | |
x.ForceCursor (cursor) | |
search_backward <- false | |
else | |
// The cursor is somewhere in the middle of the string | |
let start = if cursor = match_at then cursor - 1 else cursor | |
if start <> -1 then | |
let p = (text.ToString()).LastIndexOf (search, start) | |
if p <> -1 then | |
match_at <- p | |
cursor <- p | |
x.ForceCursor (cursor) | |
search_backward <- false | |
if search_backward then | |
// Need to search backwards in history | |
x.HistoryUpdateLine () | |
let s = history.SearchBackward (search) | |
if s <> null then | |
match_at <- -1; | |
x.SetText (s); | |
x.ReverseSearch (); | |
member private x.CmdReverseSearch () = | |
if searching = 0 then | |
match_at <- -1 | |
last_search <- search | |
searching <- -1 | |
search <- "" | |
x.SetSearchPrompt ("") | |
else | |
if search = "" then | |
if last_search <> "" && last_search <> null then | |
search <- last_search | |
x.SetSearchPrompt (search) | |
x.ReverseSearch () | |
else | |
x.ReverseSearch () | |
member private x.SearchAppend (c:char) = | |
search <- search + (string)c | |
x.SetSearchPrompt (search) | |
// | |
// If the new typed data still matches the current text, stay here | |
// | |
let mutable still_matches = false | |
if cursor < text.Length then | |
let r = text.ToString (cursor, text.Length - cursor) | |
if r.StartsWith (search) then still_matches <- true | |
if not still_matches then | |
x.ReverseSearch (); | |
member private x.CmdRefresh () = | |
Console.Clear (); | |
max_rendered <- 0; | |
x.Render (); | |
x.ForceCursor (cursor); | |
member private x.InterruptEdit (sender:obj) (a:ConsoleCancelEventArgs) = | |
// Do not abort our program: | |
a.Cancel <- true; | |
// Interrupt the editor | |
edit_thread.Abort(); | |
member private x.HandleChar c = | |
if searching <> 0 then | |
x.SearchAppend (c) | |
else | |
x.InsertChar (c) | |
member private x.EditLoop () = | |
let mutable cki : ConsoleKeyInfo = new ConsoleKeyInfo() | |
while not done_editing do | |
let mutable modifier : ConsoleModifiers = new ConsoleModifiers() | |
cki <- Console.ReadKey (true) | |
if cki.Key = ConsoleKey.Escape then | |
cki <- Console.ReadKey (true); | |
modifier <- ConsoleModifiers.Alt | |
else | |
modifier <- cki.Modifiers | |
let mutable handled = false; | |
let mutable handler_index = 0 | |
while handler_index < handlers.Length && not handled do | |
let handler = handlers.[handler_index] | |
let t = handler.CKI; | |
if t.Key = cki.Key && t.Modifiers = modifier then | |
handled <- true | |
handler.KeyHandler.Invoke () | |
last_command <- handler.HandledCommand | |
else if t.KeyChar = cki.KeyChar && t.Key = ConsoleKey.Zoom then | |
handled <- true | |
handler.KeyHandler.Invoke() | |
last_command <- handler.HandledCommand | |
handler_index <- handler_index + 1 | |
if handled then | |
if searching <> 0 then | |
if last_command <> Command.ReverseSearch then | |
searching <- 0 | |
x.SetPrompt (specified_prompt) | |
else if (cki.KeyChar <> (char) 0) then | |
x.HandleChar (cki.KeyChar) | |
member private x.InitText (initial:string) = | |
text <- new StringBuilder (initial) | |
x.ComputeRendered () | |
cursor <- text.Length | |
x.Render () | |
x.ForceCursor (cursor) | |
member private x.SetText newtext = | |
Console.SetCursorPosition (0, home_row) | |
x.InitText (newtext) | |
member private x.SetPrompt newprompt = | |
shown_prompt <- newprompt | |
Console.SetCursorPosition (0, home_row) | |
x.Render () | |
x.ForceCursor (cursor) | |
member public x.Edit prompt initial = | |
edit_thread <- Thread.CurrentThread; | |
searching <- 0; | |
let cancelHandler = new ConsoleCancelEventHandler(x.InterruptEdit) | |
Console.CancelKeyPress.AddHandler cancelHandler | |
done_editing <- false | |
history.CursorToEnd () | |
max_rendered <- 0 | |
specified_prompt <- prompt | |
shown_prompt <- prompt; | |
x.InitText (initial); | |
history.Append (initial); | |
while not done_editing do | |
try | |
x.EditLoop () | |
with | |
| :? ThreadAbortException -> | |
searching <- 0; | |
Thread.ResetAbort () | |
Console.WriteLine () | |
x.SetPrompt (prompt) | |
x.SetText ("") | |
Console.WriteLine (); | |
Console.CancelKeyPress.RemoveHandler cancelHandler | |
if text = null then | |
history.Close () | |
null | |
else | |
let result = text.ToString () | |
if result <> "" then | |
history.Accept (result) | |
else | |
history.RemoveLast () | |
result | |
member public x. SaveHistory () = | |
history.Close () | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment