Math.NET Numerics
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

// 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()