csharpfftfsharpintegrationinterpolationlinear-algebramathdifferentiationmatrixnumericsrandomregressionstatisticsmathnet
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
14 KiB
387 lines
14 KiB
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
#light
|
|
|
|
namespace Microsoft.FSharp.Compiler.Interactive
|
|
|
|
open System
|
|
open System.Text
|
|
open System.Collections.Generic
|
|
|
|
/// System.Console.ReadKey appears to return an ANSI character (not the expected the unicode character).
|
|
/// When this fix flag is true, this byte is converted to a char using the System.Console.InputEncoding.
|
|
/// This is a code-around for bug://1345.
|
|
/// Fixes to System.Console.ReadKey may break this code around, hence the option here.
|
|
module ConsoleOptions =
|
|
|
|
// Bug 4254 tracks making this fix up version specific, e.g.
|
|
// let fixupRequired = typeof<System.Console>.Assembly.GetName().Version.Major = 2
|
|
//
|
|
// Until we hear that this will be fixed in Dev10, we apply this fix always.
|
|
let fixNonUnicodeSystemConsoleReadKey = ref true
|
|
let readKeyFixup (c:char) =
|
|
if !fixNonUnicodeSystemConsoleReadKey then
|
|
// Assumes the c:char is actually a byte in the System.Console.InputEncoding.
|
|
// Convert it to a Unicode char through the encoding.
|
|
if 0 <= int c && int c <= 255 then
|
|
let chars = System.Console.InputEncoding.GetChars [| byte c |]
|
|
if chars.Length = 1 then
|
|
chars.[0] // fixed up char
|
|
else
|
|
assert("readKeyFixHook: InputEncoding.GetChars(single-byte) returned multiple chars" = "")
|
|
c // no fix up
|
|
else
|
|
assert("readKeyFixHook: given char is outside the 0..255 byte range" = "")
|
|
c // no fix up
|
|
else
|
|
c
|
|
|
|
type Style = Prompt | Out | Error
|
|
|
|
/// Class managing the command History.
|
|
type History() =
|
|
let list = new List<string>()
|
|
let mutable current = 0
|
|
|
|
member x.Count = list.Count
|
|
member x.Current =
|
|
if current >= 0 && current < list.Count then list.[current] else String.Empty
|
|
|
|
member x.Clear() = list.Clear(); current <- -1
|
|
member x.Add line =
|
|
match line with
|
|
| null | "" -> ()
|
|
| _ -> list.Add(line)
|
|
|
|
member x.AddLast line =
|
|
match line with
|
|
| null | "" -> ()
|
|
| _ -> list.Add(line); current <- list.Count
|
|
|
|
// Dead code
|
|
// member x.First() = current <- 0; x.Current
|
|
// member x.Last() = current <- list.Count - 1; x.Current;
|
|
|
|
member x.Previous() =
|
|
if (list.Count > 0) then
|
|
current <- ((current - 1) + list.Count) % list.Count
|
|
x.Current
|
|
|
|
member x.Next() =
|
|
if (list.Count > 0) then
|
|
current <- (current + 1) % list.Count
|
|
x.Current
|
|
|
|
/// List of available optionsCache
|
|
|
|
type Options() =
|
|
inherit History()
|
|
let mutable root = ""
|
|
member x.Root with get() = root and set(v) = (root <- v)
|
|
|
|
/// Cursor position management
|
|
|
|
module Utils =
|
|
let guard(f) =
|
|
try f()
|
|
with e ->
|
|
Microsoft.FSharp.Compiler.ErrorLogger.warning(Failure(Printf.sprintf "Note: an unexpected exception in fsi.exe readline console support. Consider starting fsi.exe with the --no-readline option and report the stack trace below to the .NET or Mono implementors\n%s\n%s\n" e.Message e.StackTrace));
|
|
|
|
[<Sealed>]
|
|
type Cursor =
|
|
static member ResetTo(top,left) =
|
|
Utils.guard(fun () ->
|
|
Console.CursorTop <- top;
|
|
Console.CursorLeft <- left)
|
|
static member Move(inset, delta) =
|
|
let position = Console.CursorTop * (Console.BufferWidth - inset) + (Console.CursorLeft - inset) + delta
|
|
let top = position / (Console.BufferWidth - inset)
|
|
let left = inset + position % (Console.BufferWidth - inset)
|
|
Cursor.ResetTo(top,left)
|
|
|
|
type Anchor =
|
|
{top:int; left:int}
|
|
static member Current(inset) = {top=Console.CursorTop;left= max inset Console.CursorLeft}
|
|
|
|
member p.PlaceAt(inset, index) =
|
|
//printf "p.top = %d, p.left = %d, inset = %d, index = %d\n" p.top p.left inset index;
|
|
let left = inset + (( (p.left - inset) + index) % (Console.BufferWidth - inset))
|
|
let top = p.top + ( (p.left - inset) + index) / (Console.BufferWidth - inset)
|
|
Cursor.ResetTo(top,left)
|
|
|
|
type ReadLineConsole(complete: (string option * string -> seq<string>)) =
|
|
let promptColor= Console.ForegroundColor
|
|
let outColor = Console.ForegroundColor
|
|
let errorColor = Console.ForegroundColor
|
|
let history = new History()
|
|
let mutable complete = complete
|
|
member x.SetCompletion(v) = complete <- v
|
|
|
|
/// Inset all inputs by this amount
|
|
member x.Prompt = "> "
|
|
member x.Prompt2 = "- "
|
|
member x.Inset = x.Prompt.Length
|
|
|
|
member x.GetOptions(input:string) =
|
|
/// Tab optionsCache available in current context
|
|
let optionsCache = new Options()
|
|
|
|
let rec look parenCount i =
|
|
if i <= 0 then i else
|
|
match input.Chars(i - 1) with
|
|
| c when Char.IsLetterOrDigit(c) (* or Char.IsWhiteSpace(c) *) -> look parenCount (i-1)
|
|
| '.' | '_' -> look parenCount (i-1)
|
|
| '}' | ')' | ']' -> look (parenCount+1) (i-1)
|
|
| '(' | '{' | '[' -> look (parenCount-1) (i-1)
|
|
| _ when parenCount > 0 -> look parenCount (i-1)
|
|
| _ -> i
|
|
let start = look 0 input.Length
|
|
|
|
let name = input.Substring(start, input.Length - start);
|
|
if (name.Trim().Length > 0) then
|
|
let lastDot = name.LastIndexOf('.');
|
|
let attr, pref, root =
|
|
if (lastDot < 0) then
|
|
None, name, input.Substring(0, start)
|
|
else
|
|
Some(name.Substring(0, lastDot)),
|
|
name.Substring(lastDot + 1),
|
|
input.Substring(0, start + lastDot + 1)
|
|
//printf "attr, pref, root = %s\n" (any_to_string (attr, pref, root))
|
|
try
|
|
complete(attr,pref)
|
|
|> Seq.filter(fun option -> option.StartsWith(pref,StringComparison.Ordinal))
|
|
|> Seq.iter (fun option -> optionsCache.Add(option))
|
|
// engine.Evaluate(String.Format("dir({0})", attr)) as IEnumerable;
|
|
optionsCache.Root <-root;
|
|
with e ->
|
|
optionsCache.Clear();
|
|
optionsCache,true;
|
|
else
|
|
optionsCache,false;
|
|
|
|
member x.MapCharacter(c) : string =
|
|
match c with
|
|
| '\x1A'-> "^Z";
|
|
| _ -> "^?"
|
|
|
|
member x.GetCharacterSize(c) =
|
|
if (Char.IsControl(c))
|
|
then x.MapCharacter(c).Length
|
|
else 1
|
|
|
|
static member TabSize = 4;
|
|
|
|
member x.ReadLine() =
|
|
|
|
let checkLeftEdge(prompt) =
|
|
let currLeft = Console.CursorLeft
|
|
if currLeft < x.Inset then
|
|
if currLeft = 0 then Console.Write (if prompt then x.Prompt2 else String(' ',x.Inset))
|
|
Utils.guard(fun () ->
|
|
Console.CursorLeft <- x.Inset);
|
|
|
|
// The caller writes the primary prompt. If we are reading the 2nd and subsequent lines of the
|
|
// input we're responsible for writing the secondary prompt.
|
|
checkLeftEdge(true);
|
|
|
|
/// Cursor anchor - position of !anchor when the routine was called
|
|
let anchor = ref (Anchor.Current(x.Inset));
|
|
/// Length of the output currently rendered on screen.
|
|
let rendered = ref 0
|
|
/// Input has changed, therefore options cache is invalidated.
|
|
let changed = ref false
|
|
/// Cache of optionsCache
|
|
let optionsCache = ref (new Options())
|
|
|
|
let writeBlank() =
|
|
Console.Write(' ');
|
|
checkLeftEdge(false)
|
|
let writeChar(c) =
|
|
if Console.CursorTop = Console.BufferHeight - 1 && Console.CursorLeft = Console.BufferWidth - 1 then
|
|
//printf "bottom right!\n";
|
|
anchor := { !anchor with top = (!anchor).top - 1 };
|
|
checkLeftEdge(true);
|
|
if (Char.IsControl(c)) then
|
|
let s = x.MapCharacter(c)
|
|
Console.Write(s);
|
|
rendered := !rendered + s.Length;
|
|
else
|
|
Console.Write(c);
|
|
rendered := !rendered + 1;
|
|
checkLeftEdge(true)
|
|
|
|
/// The console input buffer.
|
|
let input = new StringBuilder()
|
|
/// Current position - index into the input buffer
|
|
let current = ref 0;
|
|
|
|
let render() =
|
|
//printf "render\n";
|
|
let curr = !current
|
|
let text = input.ToString()
|
|
(!anchor).PlaceAt(x.Inset,0);
|
|
let output = new StringBuilder()
|
|
let mutable position = -1
|
|
for i = 0 to input.Length - 1 do
|
|
if (i = curr) then
|
|
position <- output.Length
|
|
let c = input.Chars(i)
|
|
if (Char.IsControl(c)) then
|
|
output.Append(x.MapCharacter(c)) |> ignore;
|
|
else
|
|
output.Append(c) |> ignore;
|
|
|
|
if (curr = input.Length) then
|
|
position <- output.Length;
|
|
|
|
// render the current text, computing a new value for "rendered"
|
|
let text = output.ToString();
|
|
let old_rendered = !rendered
|
|
rendered := 0;
|
|
for i = 0 to input.Length - 1 do
|
|
writeChar(input.Chars(i));
|
|
|
|
// blank out any dangling old text
|
|
for i = !rendered to old_rendered - 1 do
|
|
writeBlank();
|
|
|
|
(!anchor).PlaceAt(x.Inset,position);
|
|
|
|
render();
|
|
|
|
let insertChar(c:char) =
|
|
if (!current = input.Length) then
|
|
current := !current + 1;
|
|
input.Append(c) |> ignore;
|
|
writeChar(c)
|
|
else
|
|
input.Insert(!current, c) |> ignore;
|
|
current := !current + 1;
|
|
render();
|
|
|
|
let insertTab() =
|
|
for i = ReadLineConsole.TabSize - (!current % ReadLineConsole.TabSize) downto 1 do
|
|
insertChar(' ')
|
|
|
|
let moveLeft() =
|
|
if (!current > 0 && (!current - 1 < input.Length)) then
|
|
current := !current - 1
|
|
let c = input.Chars(!current)
|
|
Cursor.Move(x.Inset, - x.GetCharacterSize(c))
|
|
|
|
let moveRight() =
|
|
if (!current < input.Length) then
|
|
let c = input.Chars(!current);
|
|
current := !current + 1;
|
|
Cursor.Move(x.Inset, x.GetCharacterSize(c));
|
|
|
|
let setInput(line:string) =
|
|
input.Length <- 0;
|
|
input.Append(line) |> ignore;
|
|
current := input.Length;
|
|
render()
|
|
|
|
let tabPress(shift) =
|
|
let opts,prefix =
|
|
if !changed then
|
|
changed := false;
|
|
x.GetOptions(input.ToString());
|
|
else
|
|
!optionsCache,false
|
|
optionsCache := opts;
|
|
|
|
if (opts.Count > 0) then
|
|
let part =
|
|
if shift
|
|
then opts.Previous()
|
|
else opts.Next();
|
|
setInput(opts.Root + part);
|
|
else
|
|
if (prefix) then
|
|
Console.Beep();
|
|
else
|
|
insertTab();
|
|
|
|
let delete() =
|
|
if (input.Length > 0 && !current < input.Length) then
|
|
input.Remove(!current, 1) |> ignore;
|
|
render();
|
|
|
|
let insert(key: ConsoleKeyInfo) =
|
|
// REVIEW: is this F6 rewrite required? 0x1A looks like Ctrl-Z.
|
|
// REVIEW: the Ctrl-Z code is not recognised as EOF by the lexer.
|
|
// REVIEW: looks like a relic of the port of readline, which is currently removable.
|
|
let c = if (key.Key = ConsoleKey.F6) then '\x1A' else key.KeyChar
|
|
let c = ConsoleOptions.readKeyFixup c
|
|
insertChar(c);
|
|
|
|
let backspace() =
|
|
if (input.Length > 0 && !current > 0) then
|
|
input.Remove(!current - 1, 1) |> ignore;
|
|
current := !current - 1;
|
|
render();
|
|
|
|
let enter() =
|
|
Console.Write("\n");
|
|
let line = input.ToString();
|
|
if (line = "\x1A") then null
|
|
else
|
|
if (line.Length > 0) then
|
|
history.AddLast(line);
|
|
line;
|
|
|
|
let rec read() =
|
|
let key = Console.ReadKey(true)
|
|
|
|
match (key.Key) with
|
|
| ConsoleKey.Backspace ->
|
|
backspace();
|
|
change()
|
|
| ConsoleKey.Delete ->
|
|
delete();
|
|
change()
|
|
| ConsoleKey.Enter ->
|
|
enter()
|
|
| ConsoleKey.Tab ->
|
|
tabPress(key.Modifiers &&& ConsoleModifiers.Shift <> enum 0);
|
|
read()
|
|
| ConsoleKey.UpArrow ->
|
|
setInput(history.Previous());
|
|
change()
|
|
| ConsoleKey.DownArrow ->
|
|
setInput(history.Next());
|
|
change()
|
|
| ConsoleKey.RightArrow ->
|
|
moveRight()
|
|
change()
|
|
| ConsoleKey.LeftArrow ->
|
|
moveLeft()
|
|
change()
|
|
| ConsoleKey.Escape ->
|
|
setInput(String.Empty);
|
|
change()
|
|
| ConsoleKey.Home ->
|
|
current := 0;
|
|
(!anchor).PlaceAt(x.Inset,0)
|
|
change()
|
|
| ConsoleKey.End ->
|
|
current := input.Length;
|
|
(!anchor).PlaceAt(x.Inset,!rendered);
|
|
change()
|
|
| _ ->
|
|
// Note: If KeyChar=0, the not a proper char, e.g. it could be part of a multi key-press character,
|
|
// e.g. e-acute is ' and e with the French (Belgium) IME and US Intl KB.
|
|
// Here: skip KeyChar=0 (except for F6 which maps to 0x1A (ctrl-Z?)).
|
|
if key.KeyChar <> '\000' || key.Key = ConsoleKey.F6 then
|
|
insert(key);
|
|
change()
|
|
else
|
|
// Skip and read again.
|
|
read()
|
|
|
|
and change() =
|
|
changed := true;
|
|
read()
|
|
read()
|
|
|
|
|