// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Nova.CodeDOM;
namespace Nova.UI
{
/// <summary>
/// The view model for a <see cref="CodeDOM.Literal"/>.
/// </summary>
public class LiteralVM : ExpressionVM
{
#region /* STATICS */
internal static void AddViewModelMapping()
{
CreateViewModel.Add(typeof(Literal),
delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new LiteralVM((Literal)codeObject, dictionary); });
}
#endregion
#region /* CONSTRUCTORS */
/// <summary>
/// Create a view model instance for the specified <see cref="CodeDOM.Literal"/>.
/// </summary>
public LiteralVM(Literal literal, Dictionary<CodeObject, CodeObjectVM> dictionary)
: base(literal, dictionary)
{ }
#endregion
#region /* PROPERTIES */
/// <summary>
/// The underlying <see cref="CodeDOM.Literal"/> model.
/// </summary>
public Literal Literal
{
get { return (Literal)CodeObject; }
}
#endregion
#region /* METHODS */
#endregion
#region /* RENDERING */
/// <summary>
/// Determines if commas should be displayed in numeric values.
/// </summary>
public static bool CommasInNumerics;
/// <summary>
/// Determines if actual or alternative symbols should be shown in strings/chars instead of escape sequences.
/// </summary>
public static bool NoEscapes;
/// <summary>
/// Determines if spaces should be rendered with visible symbols in strings/chars.
/// </summary>
public static bool VisibleSpaces;
/// <summary>
/// Determines if quotes around strings should be hidden.
/// </summary>
public static bool HideQuotes;
/// <summary>
/// The font used to render control characters.
/// </summary>
public static FontFamily ControlFont = new FontFamily("Arial Unicode MS");
public override Brush BorderBrush
{
get { return Brushes.LightGray; }
}
public override Brush BackgroundBrush
{
get { return Brushes.White; }
}
public override void RenderExpression(CodeRenderer renderer, RenderFlags flags)
{
string text = Literal.Text;
char ch = text[0];
if (ch == '"' || ch == '@')
RenderStringOrChar(renderer, text, false, this);
else if (ch == '\'')
RenderStringOrChar(renderer, text, true, this);
else if (char.IsDigit(ch) || (ch == '.') || (ch == '-' && text != "-Infinity") || (ch == '+'))
RenderNumeric(renderer, text, this);
else if (text == Literal.ParseTokenNull || text == Literal.ParseTokenTrue || text == Literal.ParseTokenFalse)
renderer.RenderText(text, KEYWORD_BRUSH, this);
else if (text == "NaN" || text == "Infinity" || text == "-Infinity")
renderer.RenderText(text, NORMAL_BRUSH, this);
else
renderer.RenderText(text, ERROR_BRUSH, this);
}
protected static void RenderStringOrChar(CodeRenderer renderer, string text, bool isChar, CodeObjectVM tag)
{
bool isVerbatim = (text[0] == '@');
// Render escape sequences (and special chars) in a different color, and in red if invalid.
int start = 0;
char escCh = '\0', escVal = '\0';
int escPos = 0, spacePos = 0;
int openQuotePos = (isVerbatim ? 1 : 0);
int i;
for (i = 0; i < text.Length; ++i)
{
char ch = text[i];
// Handle the verbatim marker (if any)
if (i == 0 && isVerbatim)
{
RenderText(renderer, text, ref start, 1, true, STRING_ESC_BRUSH, tag);
continue;
}
// Hide the starting double quote if the option is on
if (ch == '"' && i == openQuotePos)
{
if (HideQuotes)
start = openQuotePos + 1;
continue;
}
// Check if we're in a string of visible spaces
if (spacePos > 0)
{
if (ch == ' ')
{
++spacePos;
continue;
}
// Display visible spaces using an "open box" character
renderer.RenderText(new string('\x2423', spacePos), STRING_BRUSH, ControlFont, -2, tag);
start += spacePos;
spacePos = 0;
}
// Check if we're currently inside an escape sequence
if (escPos > 0)
{
bool processed = true;
bool done = false, error = false;
int end = i + 1;
if (escPos == 1)
{
if (isVerbatim)
{
if (ch == '"')
{
escVal = ch;
done = true;
}
else
error = true;
}
else
{
escCh = ch;
switch (ch)
{
case '0': done = true; break;
case 'a': escVal = '\a'; done = true; break;
case 'b': escVal = '\b'; done = true; break;
case 'f': escVal = '\f'; done = true; break;
case 'n': escVal = '\n'; done = true; break;
case 'r': escVal = '\r'; done = true; break;
case 't': escVal = '\t'; done = true; break;
case 'v': escVal = '\v'; done = true; break;
case 'x': case 'u': case 'U':
break;
case '\\': case '\'': case '"':
escVal = ch; done = true; break;
default: error = true; break;
}
}
}
else
{
// Handle 'x', 'u', and 'U' sequences
if (Uri.IsHexDigit(ch))
{
escVal = (char)((escVal * 16) + Uri.FromHex(ch));
done = (escPos == (escCh == 'U' ? 9 : 5));
}
else
{
// In this case, don't include the terminating char
--end;
processed = false;
done = true;
error = (escPos == 2 || escCh != 'x');
}
}
// Mark '\U' sequence red if in a char
if (done && escCh == 'U' && isChar)
error = true;
if (error)
{
// Render the entire escape sequence in red if any errors are found
RenderText(renderer, text, ref start, end, false, ERROR_BRUSH, tag);
escPos = 0;
}
else if (done)
{
// Display all escape sequences as actual chars if option is on
if (NoEscapes)
{
// Display control characters as special symbols with 2-3 letter names
if (escVal < 0x20 || escVal == 0x7f)
{
char sym = (escCh == 'n' ? '\u2424' : escVal == 0x7f ? '\u2421' : (char)('\u2400' + escVal));
renderer.RenderText(sym.ToString(), STRING_BRUSH, ControlFont, tag);
}
else
renderer.RenderText(escVal.ToString(), STRING_BRUSH, tag);
start = end;
}
// Mark "\'" escapes in strings and '\"' escapes in chars as unnecessary
else if ((escVal == '\'' && !isChar) || (escVal == '"' && isChar))
{
RenderText(renderer, text, ref start, end - 1, true, REDUNDANT_BRUSH, tag);
RenderText(renderer, text, ref start, end, true, STRING_BRUSH, tag);
}
// Display escape sequence
else
RenderText(renderer, text, ref start, end, true, STRING_ESC_BRUSH, tag);
escPos = 0;
}
else
++escPos;
if (processed)
continue;
}
// Handle escape sequence start
if ((ch == '\\' && !isVerbatim) || (ch == '"' && isVerbatim))
{
RenderText(renderer, text, ref start, i, true, STRING_BRUSH, tag);
escPos = 1;
escVal = '\0';
}
// Handle newlines in verbatim strings
else if (ch == '\n')
{
RenderText(renderer, text, ref start, i, true, STRING_BRUSH, tag);
++start; // Skip over the newline char
renderer.NewLine();
}
// Handle the start of some visible spaces
else if (ch == ' ' && VisibleSpaces)
{
RenderText(renderer, text, ref start, i, true, STRING_BRUSH, tag);
spacePos = 1;
}
}
// Hide ending double quote if option is on
// (do this here so quote will flush any active escape sequences)
if (HideQuotes && text[i - 1] == '"')
--i;
// Only cache if less than 16 in length
RenderText(renderer, text, ref start, i, (i - start < 16), STRING_BRUSH, tag);
}
protected static void RenderText(CodeRenderer renderer, string text, ref int start, int end, bool cache, Brush brush, CodeObjectVM tag)
{
int length = end - start;
if (length > 0)
{
string partial = text.Substring(start, length);
start = end;
if (partial.Length > 0)
renderer.RenderText(partial, brush, cache ? TextStyle.Normal : TextStyle.NoCache, tag);
}
}
protected static void RenderNumeric(CodeRenderer renderer, string text, CodeObjectVM tag)
{
if (text.StartsWith("0x"))
renderer.RenderText(text, NUMERIC_BRUSH, tag);
else
{
// Display numerics with commas if enabled
if (CommasInNumerics)
{
string formatted = text;
int count = 0;
for (int i = formatted.Length - 1; i > 0; --i)
{
if (char.IsDigit(formatted, i))
{
if (++count == 3 && char.IsDigit(formatted, i - 1))
formatted = formatted.Insert(i, ",");
else
continue;
}
count = 0;
}
text = formatted;
}
// Render the numeric, displaying any alpha chars in a different color (exponent indicator or type suffix)
bool isAlpha = false;
int pos, start = 0;
for (pos = 0; pos < text.Length; ++pos)
{
char ch = text[pos];
if (char.IsLetter(ch))
{
if (!isAlpha)
{
RenderText(renderer, text, ref start, pos, true, NUMERIC_BRUSH, tag);
isAlpha = true;
}
}
else
{
if (isAlpha)
{
RenderText(renderer, text, ref start, pos, true, NUMERIC_ALPHA_BRUSH, tag);
isAlpha = false;
}
}
}
RenderText(renderer, text, ref start, pos, true, (isAlpha ? NUMERIC_ALPHA_BRUSH : NUMERIC_BRUSH), tag);
}
}
public static void RenderConstantValue(CodeRenderer renderer, RenderFlags flags, object constantValue, bool hexFormat, CodeObjectVM tag)
{
renderer.RenderText(" ", PUNC_BRUSH, tag);
AssignmentVM.RenderOperator(renderer, OPERATOR_BRUSH, tag);
renderer.RenderText(" ", PUNC_BRUSH, tag);
Literal literal = new Literal(constantValue, hexFormat) { Parent = tag.CodeObject };
CreateVM(literal, tag).Render(renderer, flags | RenderFlags.ForceBorder);
}
public override void RenderToolTip(CodeRenderer renderer, RenderFlags flags)
{
TypeRefBaseVM.RenderType(renderer, CodeObject.GetType(), flags, this);
renderer.RenderText(" : ", NORMAL_BRUSH, TextStyle.Proportional | TextStyle.Bold);
RenderDescription(renderer);
RenderEvaluatedType(renderer, Literal.EvaluateTypeOrNamespace(), flags);
RenderMessagesInToolTip(renderer, flags);
RenderImplicitConversions(renderer, flags); // Render any implicit conversion if being passed as an argument
}
#endregion
#region /* COMMANDS */
public static readonly RoutedCommand CommasInNumericsCommand = new RoutedCommand("Show Commas In Numerics", typeof(LiteralVM));
public static readonly RoutedCommand NoEscapesCommand = new RoutedCommand("No Escapes in Strings/Chars", typeof(LiteralVM));
public static readonly RoutedCommand VisibleSpacesCommand = new RoutedCommand("Visible Spaces in Strings/Chars", typeof(LiteralVM));
public static readonly RoutedCommand HideQuotesCommand = new RoutedCommand("Hide String Quotes", typeof(LiteralVM));
public static new void BindCommands(Window window)
{
WPFUtil.AddCommandBinding(window, NoEscapesCommand, noEscapes_Executed);
WPFUtil.AddCommandBinding(window, VisibleSpacesCommand, visibleSpaces_Executed);
WPFUtil.AddCommandBinding(window, HideQuotesCommand, hideQuotes_Executed);
WPFUtil.AddCommandBinding(window, CommasInNumericsCommand, showCommasInNumerics_Executed);
}
private static void showCommasInNumerics_Executed(object sender, ExecutedRoutedEventArgs e)
{
CommasInNumerics = !CommasInNumerics;
CodeRenderer.ReRenderAll();
}
private static void noEscapes_Executed(object sender, ExecutedRoutedEventArgs e)
{
NoEscapes = !NoEscapes;
CodeRenderer.ReRenderAll();
}
private static void visibleSpaces_Executed(object sender, ExecutedRoutedEventArgs e)
{
VisibleSpaces = !VisibleSpaces;
CodeRenderer.ReRenderAll();
}
private static void hideQuotes_Executed(object sender, ExecutedRoutedEventArgs e)
{
HideQuotes = !HideQuotes;
CodeRenderer.ReRenderAll();
}
#endregion
}
}