Copy SourceTextView from the SourceWriterExample: https://github.com/xamarin/mac-samples/tree/main/SourceWriter

This commit is contained in:
Neil Brommer 2023-07-24 14:21:33 -07:00
parent 8507aa1512
commit b46debb420
14 changed files with 2643 additions and 4 deletions

View File

@ -52,4 +52,12 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.ViewFeatures" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<None Remove="SourceWriter\" />
<None Remove="SourceWriter\LanguageFormats\" />
</ItemGroup>
<ItemGroup>
<Folder Include="SourceWriter\" />
<Folder Include="SourceWriter\LanguageFormats\" />
</ItemGroup>
</Project>

View File

@ -522,7 +522,7 @@
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView wantsLayer="YES" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="rAS-np-zOl">
<textView wantsLayer="YES" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="rAS-np-zOl" customClass="SourceTextView">
<rect key="frame" x="0.0" y="0.0" width="700" height="300"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>

View File

@ -1,6 +1,8 @@
using System;
using System.Threading;
using AppKit;
using Cauldron.Macos.SourceWriter;
using Cauldron.Macos.SourceWriter.LanguageFormats;
namespace Cauldron.Macos;
@ -26,11 +28,11 @@ public partial class MainWindow : NSWindowController
.ContentView.DocumentView as NSOutlineView;
}
private NSTextView ScriptEditorTextBox
private SourceTextView ScriptEditorTextBox
{
get => (this.MainContentController
.SplitViewItems[0].ViewController.View as NSScrollView)
.ContentView.DocumentView as NSTextView;
.ContentView.DocumentView as SourceTextView;
}
public WebKit.WKWebView ScriptOutputWebView
@ -58,7 +60,7 @@ public partial class MainWindow : NSWindowController
this.RunScriptToolbarButton.Activated += RunScript;
NSTextView scriptTextBox = this.ScriptEditorTextBox;
SourceTextView scriptTextBox = this.ScriptEditorTextBox;
scriptTextBox.Font = NSFont.MonospacedSystemFont(new nfloat(14), NSFontWeight.Regular);
scriptTextBox.AutomaticQuoteSubstitutionEnabled = false;
scriptTextBox.AutomaticDashSubstitutionEnabled = false;
@ -67,6 +69,9 @@ public partial class MainWindow : NSWindowController
scriptTextBox.AutomaticTextCompletionEnabled = false;
scriptTextBox.AutomaticTextReplacementEnabled = false;
scriptTextBox.AutomaticLinkDetectionEnabled = false;
scriptTextBox.Formatter = new LanguageFormatter(scriptTextBox, new CSharpDescriptor());
scriptTextBox.Formatter.Reformat();
}
public void RunScript(object sender, EventArgs e)

View File

@ -0,0 +1,137 @@
using AppKit;
using Foundation;
namespace Cauldron.Macos.SourceWriter;
public class FormatDescriptor : NSObject
{
#region Computed Properties
/// <summary>Gets or sets the <c>FormatDescriptorType</c> for this format descriptor.</summary>
/// <value>The <c>FormatDescriptorType</c>.</value>
public FormatDescriptorType Type { get; set; } = FormatDescriptorType.Prefix;
/// <summary>
/// Gets or sets the forground color that text matching this format will be set to.
/// </summary>
/// <value>The <c>NSColor</c>.</value>
public NSColor Color { get; set; } = NSColor.Gray;
/// <summary>Gets or sets the character sequence that this format starts with.</summary>
/// <value>The starting <c>string</c> sequence.</value>
public string StartsWith { get; set; } = "";
/// <summary>
/// Gets or sets the character sequence that text matching this format ends with.
/// </summary>
/// <value>The ending <c>string</c> sequence.</value>
/// <remarks>
/// This value will be an empty string ("") if the <c>Type</c> is a <c>Prefix</c> format.
/// </remarks>
public string EndsWith { get; set; } = "";
/// <summary>
/// Gets or sets the index of the last matching character within either the <c>StartsWith</c> or
/// <c>EndsWith</c> based on the state of the <c>Active</c> property.
/// </summary>
/// <value>The index of the char.</value>
/// <remarks>
/// This value should ONLY be changed by the <see cref="\LanguageFormatter"/>.
/// </remarks>
public int CharIndex { get; set; } = 0;
/// <summary>
/// Gets or sets if this format has been "activated" (if the matching <c>StartsWith</c> character sequence
/// has been found).
/// </summary>
/// <value><c>true</c> if the matching <c>StartsWith</c> character sequence
/// has been found; otherwise, <c>false</c>.</value>
/// <remarks>
/// This value should ONLY be changed by the <see cref="LanguageFormatter"/>.
/// </remarks>
public bool Active { get; set; } = false;
/// <summary>
/// Gets a value indicating whether this <see cref="FormatDescriptor"/> is "triggered"
/// (all of the <c>StartsWith</c> or <c>EndsWith</c> characters have been found based on the
/// <c>Active</c> property).
/// </summary>
/// <value><c>true</c> if triggered; otherwise, <c>false</c>.</value>
public bool Triggered
{
get
{
if (Active)
{
return CharIndex > (EndsWith.Length - 1);
}
else
{
return CharIndex > (StartsWith.Length - 1);
}
}
}
#endregion
#region Constructors
/// <summary>Initializes a new instance of the <see cref="FormatDescriptor"/> class.</summary>
/// <param name="startsWith">The starting character sequence for this format.</param>
/// <param name="color">The <c>NSColor</c> that text in this sequence will be set too.</param>
/// <remarks>The <c>type</c> will automatically be set to <c>Prefix</c>.</remarks>
public FormatDescriptor(string startsWith, NSColor color)
{
this.Type = FormatDescriptorType.Prefix;
this.StartsWith = startsWith;
this.Color = color;
}
/// <summary>Initializes a new instance of the <see cref="FormatDescriptor"/> class.</summary>
/// <param name="startsWith">The starting character sequence for this format.</param>
/// <param name="endsWith">The ending character sequence for this format.</param>
/// <param name="color">The <c>NSColor</c> that text in this sequence will be set too.</param>
/// <remarks>The <c>type</c> will automatically be set to <c>Enclosure</c>.</remarks>
public FormatDescriptor(string startsWith, string endsWith, NSColor color)
{
this.Type = FormatDescriptorType.Enclosure;
this.StartsWith = startsWith;
this.EndsWith = endsWith;
this.Color = color;
}
#endregion
#region Public Methods
/// <summary>
/// Tests to see if the passed in character matches the character at <c>CharIndex</c> of
/// either the <c>StartsWith</c> or <c>EndsWith</c> character sequence based on the state
/// of the <c>Active</c> property.
/// </summary>
/// <returns><c>true</c>, if character was matched, <c>false</c> otherwise.</returns>
/// <param name="c">The character being tested.</param>
public bool MatchesCharacter(char c)
{
bool matches;
// Is this format currently active?
if (Active)
{
matches = c == EndsWith[CharIndex];
}
else
{
matches = (c == StartsWith[CharIndex]);
}
// Increment
if (matches)
{
++CharIndex;
}
return matches;
}
#endregion
}

View File

@ -0,0 +1,17 @@
namespace Cauldron.Macos.SourceWriter;
public enum FormatDescriptorType
{
/// <summary>
/// Defines a format that starts with a given character sequence and runs to
/// the end of the line.
/// </summary>
Prefix,
/// <summary>
/// Defines a format that is enclosed between a starting and ending character
/// sequence.
/// </summary>
Enclosure
}

View File

@ -0,0 +1,48 @@
using AppKit;
namespace Cauldron.Macos.SourceWriter;
public class KeywordDescriptor
{
#region Computed Properties
/// <summary>Gets or sets the <c>KeywordType</c>.</summary>
/// <value>The type.</value>
public KeywordType Type { get; set; } = KeywordType.Keyword;
/// <summary>
/// Gets or sets the <c>NSColor</c> that the <see cref="AppKit.TextKit.Formatter.LanguageFormatter"/>
/// will set this keyword to.
/// </summary>
/// <value>The <c>NSColor</c>.</value>
public NSColor Color { get; set; } = NSColor.Black;
/// <summary>Gets or sets the tooltip used to define this keyword.</summary>
/// <value>The tooltip.</value>
public string Tooltip { get; set; } = "";
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="KeywordDescriptor"/> class.
/// </summary>
public KeywordDescriptor() { }
/// <summary>
/// Initializes a new instance of the <see cref="KeywordDescriptor"/> class.
/// </summary>
/// <param name="type">Specifies the <c>KeywordType</c>.</param>
/// <param name="color">Specifies the <c>NSColor</c> that this keyword will be set to.</param>
/// <param name="toolTip">Defines the tool tip for this keyword.</param>
public KeywordDescriptor(KeywordType type, NSColor color, string toolTip)
{
this.Type = type;
this.Color = color;
this.Tooltip = toolTip;
}
#endregion
}

View File

@ -0,0 +1,104 @@
namespace Cauldron.Macos.SourceWriter;
public enum KeywordType
{
/// <summary>
/// A generic keyword that doesn't fall under one of the other types.
/// </summary>
Keyword,
/// <summary>
/// The generic variable type keyword such as <c>var</c>.
/// </summary>
Type,
/// <summary>
/// A variable type keyword such as <c>string</c> or <c>int</c>.
/// </summary>
ValueType,
/// <summary>
/// A reference variable type such as <c>object</c>.
/// </summary>
ReferenceType,
/// <summary>
/// An access modifier keyword such as <c>public</c> or <c>private</c>.
/// </summary>
AccessModifier,
/// <summary>
/// A geeneric modifier type of keyword.
/// </summary>
Modifier,
/// <summary>
/// A selection statement keyword such as <c>if</c>.
/// </summary>
SelectionStatement,
/// <summary>
/// An iteration statement keyword such as <c>for</c>.
/// </summary>
IterationStatement,
/// <summary>
/// A jump statement keyword such as <c>break</c>.
/// </summary>
JumpStatement,
/// <summary>
/// A exception handling statement keyword such as <c>try</c> or <c>catch</c>.
/// </summary>
ExceptionHandlingStatement,
/// <summary>
/// A generic statement keyword.
/// </summary>
Statement,
/// <summary>
/// A method parameters ketword such as <c>out</c>.
/// </summary>
MethodParameters,
/// <summary>
/// A namespace keyword.
/// </summary>
NamespaceKeyword,
/// <summary>
/// An operator keyword such as <c>sizeof</c>.
/// </summary>
OperatorKeyword,
/// <summary>
/// A conversion keyword such as <c>explicit</c>.
/// </summary>
ConversionKeyword,
/// <summary>
/// An access keyword such as <c>this</c>.
/// </summary>
AccessKeywords,
/// <summary>
/// A literal keyword such as <c>null</c>.
/// </summary>
LiteralKeywords,
/// <summary>
/// A contextual keyword such as <c>get</c> or <c>set</c>.
/// </summary>
ContextualKeywords,
/// <summary>
/// A query keywords such as <c>select</c>.
/// </summary>
QueryKeywords,
/// <summary>
/// A preprocessor directive keyword like <c>#if</c>.
/// </summary>
PreprocessorDirective
}

View File

@ -0,0 +1,49 @@
using Foundation;
namespace Cauldron.Macos.SourceWriter
{
public class LanguageClosure : NSObject
{
#region Computed Properties
/// <summary>Gets or sets the starting character for this closure.</summary>
/// <value>The starting character.</value>
public char StartingCharacter { get; set; }
/// <summary>Gets or sets the ending character for this closure.</summary>
/// <value>The ending character.</value>
public char EndingCharacter { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LanguageClosure"/> class.
/// </summary>
public LanguageClosure() { }
/// <summary>
/// Initializes a new instance of the <see cref="LanguageClosure"/> class.
/// </summary>
/// <param name="character">The character that both starts and ends this closure.</param>
public LanguageClosure(char character)
{
this.StartingCharacter = character;
this.EndingCharacter = character;
}
/// <summary>
/// Initializes a new instance of the <see cref="LanguageClosure"/> class.
/// </summary>
/// <param name="startingCharacter">The character that starts the closure.</param>
/// <param name="endingCharacter">The character that ends the closure.</param>
public LanguageClosure(char startingCharacter, char endingCharacter)
{
this.StartingCharacter = startingCharacter;
this.EndingCharacter = endingCharacter;
}
#endregion
}
}

View File

@ -0,0 +1,522 @@
using System;
using AppKit;
using Foundation;
using System.Collections.Generic;
namespace Cauldron.Macos.SourceWriter;
[Register("LanguageDescriptor")]
public class LanguageDescriptor : NSObject
{
#region Computed Properties
/// <summary>Gets the language identifier.</summary>
/// <value>The language identifier.</value>
public virtual string LanguageIdentifier { get => "Default"; }
/// <summary>
/// Gets or sets the language separators that can be used to define a "word" in the given
/// language.
/// </summary>
/// <value>The language separators.</value>
public virtual char[] LanguageSeparators { get; set; } = new char[] { '.' };
/// <summary>
/// Gets the escape character for the given language.
/// </summary>
/// <value>The escape character.</value>
public virtual char EscapeCharacter { get => '\\'; }
/// <summary>
/// Gets or sets the collection of <see cref="KeywordDescriptor"/> used to define
/// the keywords for this language.
/// </summary>
/// <value>The keywords.</value>
public Dictionary<string, KeywordDescriptor> Keywords { get; set; } = new Dictionary<string, KeywordDescriptor>();
/// <summary>
/// Gets or sets the collection of <see cref="FormatDescriptor"/> formats used to syntax
/// highlight this language.
/// </summary>
/// <value>The formats.</value>
public List<FormatDescriptor> Formats { get; set; } = new List<FormatDescriptor>();
/// <summary>
/// Gets or sets the collection of <see cref="LanguageClosure"/> used to auto complete to
/// closure of text such as (), [], "" or ''.
/// </summary>
/// <value>The closures.</value>
public List<LanguageClosure> Closures { get; set; } = new List<LanguageClosure>();
/// <summary>
/// Gets or sets the formatting commands that can be added to the user interface for the user to
/// select and apply to a selection of text in the editor.
/// </summary>
/// <value>The <see cref="LanguageFormatCommand"/> items.</value>
public List<LanguageFormatCommand> FormattingCommands { get; set; }
= new List<LanguageFormatCommand>();
/// <summary>Gets or sets the color of generic keywords.</summary>
/// <value>The <c>NSColor</c> of the keyword.</value>
[Export("KeywordColor")]
public NSColor KeywordColor
{
get => LoadColor("KeywordColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("KeywordColor");
SaveColor("KeywordColor", value, true);
DidChangeValue("KeywordColor");
}
}
/// <summary>Gets or sets the color of generic keyword type.</summary>
/// <value>The <c>NSColor</c> of the type.</value>
[Export("TypeColor")]
public NSColor TypeColor
{
get => LoadColor("TypeColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("TypeColor");
SaveColor("TypeColor", value, true);
DidChangeValue("TypeColor");
}
}
/// <summary>Gets or sets the color of a value type keyword.</summary>
/// <value>The <c>NSColor</c> of the value type.</value>
[Export("ValueTypeColor")]
public NSColor ValueTypeColor
{
get => LoadColor("ValueTypeColor", NSColor.Blue);
set
{
WillChangeValue("ValueTypeColor");
SaveColor("ValueTypeColor", value, true);
DidChangeValue("ValueTypeColor");
}
}
/// <summary>Gets or sets the color of a reference type keyword.</summary>
/// <value>The <c>NSColor</c> of the reference type.</value>
[Export("ReferenceTypeColor")]
public NSColor ReferenceTypeColor
{
get => LoadColor("ReferenceTypeColor", NSColor.FromRgba(0f, 0.56f, 0.80f, 1.0f));
set
{
WillChangeValue("ReferenceTypeColor");
SaveColor("ReferenceTypeColor", value, true);
DidChangeValue("ReferenceTypeColor");
}
}
/// <summary>Gets or sets the color of a access modifier keyword.</summary>
/// <value>The <c>NSColor</c> of the access modifier.</value>
[Export("AccessModifierColor")]
public NSColor AccessModifierColor
{
get => LoadColor("AccessModifierColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("AccessModifierColor");
SaveColor("AccessModifierColor", value, true);
DidChangeValue("AccessModifierColor");
}
}
/// <summary>Gets or sets the color of a generic modifier keyword.</summary>
/// <value>The <c>NSColor</c> of the modifier.</value>
[Export("ModifierColor")]
public NSColor ModifierColor
{
get => LoadColor("ModifierColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("ModifierColor");
SaveColor("ModifierColor", value, true);
DidChangeValue("ModifierColor");
}
}
/// <summary>Gets or sets the color of a selection statement keyword.</summary>
/// <value>The <c>NSColor</c> of the selection statement.</value>
[Export("SelectionStatementColor")]
public NSColor SelectionStatementColor
{
get => LoadColor("SelectionStatementColor", NSColor.FromRgba(0.50f, 0.25f, 0f, 1.0f));
set
{
WillChangeValue("SelectionStatementColor");
SaveColor("SelectionStatementColor", value, true);
DidChangeValue("SelectionStatementColor");
}
}
/// <summary>Gets or sets the color of a iteration statement keyword.</summary>
/// <value>The <c>NSColor</c> of the iteration statement.</value>
[Export("IterationStatementColor")]
public NSColor IterationStatementColor
{
get => LoadColor("IterationStatementColor", NSColor.FromRgba(0.50f, 0f, 0f, 1.0f));
set
{
WillChangeValue("IterationStatementColor");
SaveColor("IterationStatementColor", value, true);
DidChangeValue("IterationStatementColor");
}
}
/// <summary>Gets or sets the color of a jump statement keyword.</summary>
/// <value>The <c>NSColor</c> of the jump statement.</value>
[Export("JumpStatementColor")]
public NSColor JumpStatementColor
{
get => LoadColor("JumpStatementColor", NSColor.FromRgba(0.50f, 0.50f, 0.0f, 1.0f));
set
{
WillChangeValue("JumpStatementColor");
SaveColor("JumpStatementColor", value, true);
DidChangeValue("JumpStatementColor");
}
}
/// <summary>Gets or sets the color of a exception handling keyword.</summary>
/// <value>The <c>NSColor</c> of the exception handling.</value>
[Export("ExceptionHandlingColor")]
public NSColor ExceptionHandlingColor
{
get => LoadColor("ExceptionHandlingColor", NSColor.FromRgba(1f, 0f, 0f, 1.0f));
set
{
WillChangeValue("ExceptionHandlingColor");
SaveColor("ExceptionHandlingColor", value, true);
DidChangeValue("ExceptionHandlingColor");
}
}
/// <summary>Gets or sets the color of a generic statement keyword.</summary>
/// <value>The <c>NSColor</c> of the statement.</value>
[Export("StatementColor")]
public NSColor StatementColor
{
get => LoadColor("StatementColor", NSColor.FromRgba(1f, 0f, 0.50f, 1.0f));
set
{
WillChangeValue("StatementColor");
SaveColor("StatementColor", value, true);
DidChangeValue("StatementColor");
}
}
/// <summary>Gets or sets the color of a method parameter keyword.</summary>
/// <value>The <c>NSColor</c> of the method parameter.</value>
[Export("MethodParameterColor")]
public NSColor MethodParameterColor
{
get => LoadColor("MethodParameterColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("MethodParameterColor");
SaveColor("MethodParameterColor", value, true);
DidChangeValue("MethodParameterColor");
}
}
/// <summary>Gets or sets the color of a namespace keyword.</summary>
/// <value>The <c>NSColor</c> of the namespace.</value>
[Export("NamespaceColor")]
public NSColor NamespaceColor
{
get => LoadColor("NamespaceColor", NSColor.FromRgba(0.06f, 0.52f, 0.50f, 1.0f));
set
{
WillChangeValue("NamespaceColor");
SaveColor("NamespaceColor", value, true);
DidChangeValue("NamespaceColor");
}
}
/// <summary>Gets or sets the color of a operator keyword.</summary>
/// <value>The <c>NSColor</c> of the operator keyword.</value>
[Export("OperatorKeywordColor")]
public NSColor OperatorKeywordColor
{
get => LoadColor("OperatorKeywordColor", NSColor.FromRgba(0.80f, 0.40f, 1f, 1.0f));
set
{
WillChangeValue("OperatorKeywordColor");
SaveColor("OperatorKeywordColor", value, true);
DidChangeValue("OperatorKeywordColor");
}
}
/// <summary>Gets or sets the color of a conversion keyword.</summary>
/// <value>The <c>NSColor</c> of the conversion keyword.</value>
[Export("ConversionKeywordColor")]
public NSColor ConversionKeywordColor
{
get => LoadColor("ConversionKeywordColor", NSColor.Purple);
set
{
WillChangeValue("ConversionKeywordColor");
SaveColor("ConversionKeywordColor", value, true);
DidChangeValue("ConversionKeywordColor");
}
}
/// <summary>Gets or sets the color of a access keyword.</summary>
/// <value>The <c>NSColor</c> of the access keyword.</value>
[Export("AccessKeywordColor")]
public NSColor AccessKeywordColor
{
get => LoadColor("AccessKeywordColor", NSColor.Purple);
set
{
WillChangeValue("AccessKeywordColor");
SaveColor("AccessKeywordColor", value, true);
DidChangeValue("AccessKeywordColor");
}
}
/// <summary>Gets or sets the color of a literal keyword.</summary>
/// <value>The <c>NSColor</c> of the literal keyword.</value>
[Export("LiteralKeywordColor")]
public NSColor LiteralKeywordColor
{
get => LoadColor("LiteralKeywordColor", NSColor.Purple);
set
{
WillChangeValue("LiteralKeywordColor");
SaveColor("LiteralKeywordColor", value, true);
DidChangeValue("LiteralKeywordColor");
}
}
/// <summary>Gets or sets the color of a contextual keyword.</summary>
/// <value>The <c>NSColor</c> of the contextual keyword.</value>
[Export("ContextualKeywordColor")]
public NSColor ContextualKeywordColor
{
get => LoadColor("ContextualKeywordColor", NSColor.FromRgba(0f, 0.50f, 0.25f, 1.0f));
set
{
WillChangeValue("ContextualKeywordColor");
SaveColor("ContextualKeywordColor", value, true);
DidChangeValue("ContextualKeywordColor");
}
}
/// <summary>Gets or sets the color of a query keyword.</summary>
/// <value>The <c>NSColor</c> of the query keyword.</value>
[Export("QueryKeywordColor")]
public NSColor QueryKeywordColor
{
get => LoadColor("QueryKeywordColor", NSColor.Orange);
set
{
WillChangeValue("QueryKeywordColor");
SaveColor("QueryKeywordColor", value, true);
DidChangeValue("QueryKeywordColor");
}
}
/// <summary>Gets or sets the color of a preprocessor directive keyword.</summary>
/// <value>The <c>NSColor</c> of the preprocessor directive.</value>
[Export("PreprocessorDirectiveColor")]
public NSColor PreprocessorDirectiveColor
{
get => LoadColor("PreprocessorDirectiveColor", NSColor.FromRgba(0.69f, 0.03f, 0.61f, 1.0f));
set
{
WillChangeValue("PreprocessorDirectiveColor");
SaveColor("PreprocessorDirectiveColor", value, true);
DidChangeValue("PreprocessorDirectiveColor");
}
}
/// <summary>Gets or sets the color of a comment.</summary>
/// <value>The <c>NSColor</c> of the comment.</value>
[Export("CommentColor")]
public NSColor CommentColor
{
get => LoadColor("CommentColor", NSColor.Gray);
set
{
WillChangeValue("CommentColor");
SaveColor("CommentColor", value, true);
DidChangeValue("CommentColor");
}
}
/// <summary>Gets or sets the color of a string literal.</summary>
/// <value>The <c>NSColor</c> of the string literal.</value>
[Export("StringLiteralColor")]
public NSColor StringLiteralColor
{
get => LoadColor("StringLiteralColor", NSColor.Orange);
set
{
WillChangeValue("StringLiteralColor");
SaveColor("StringLiteralColor", value, true);
DidChangeValue("StringLiteralColor");
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.LanguageDescriptor"/> class.
/// </summary>
public LanguageDescriptor()
{
}
#endregion
#region Public Methods
/// <summary>Define this instance.</summary>
public virtual void Define()
{
// Initialize
this.Keywords.Clear();
this.Formats.Clear();
this.Closures.Clear();
this.FormattingCommands.Clear();
// Define the default set of closures
this.Closures.Add(new LanguageClosure('(', ')'));
this.Closures.Add(new LanguageClosure('[', ']'));
this.Closures.Add(new LanguageClosure('<', '>'));
this.Closures.Add(new LanguageClosure('{', '}'));
this.Closures.Add(new LanguageClosure('"'));
}
/// <summary>Formats the passed in string of text for previewing.</summary>
/// <returns>The string formatted for preview.</returns>
/// <param name="text">Text.</param>
public virtual string FormatForPreview(string text)
{
return text;
}
/// <summary>
/// Resets all of the <see cref="FormatDescriptor"/> for this language to their default states
/// of unmatched and inactive.
/// </summary>
/// <remarks>This should only be called ba a <see cref="LanguageFormatter"/>.</remarks>
public virtual void ClearFormats()
{
// Clear the process state of all formats
foreach (FormatDescriptor format in Formats)
{
format.CharIndex = 0;
format.Active = false;
}
}
/// <summary>
/// Converts the given color into a web style hex string in the form #RRBBGG or optionally
/// #RRBBGGAA.
/// </summary>
/// <returns>The web hex string representing the given color.</returns>
/// <param name="color">The <c>NSColor</c> to convert.</param>
/// <param name="withAlpha">
/// If set to <c>true</c> with the alpha (transparency) of the color will be included.
/// </param>
public static string NSColorToHexString(NSColor color, bool withAlpha)
{
// Break color into pieces
color.GetRgba(out nfloat red, out nfloat green, out nfloat blue, out nfloat alpha);
// Adjust to byte
alpha *= 255;
red *= 255;
green *= 255;
blue *= 255;
//With the alpha value?
if (withAlpha)
{
return string.Format("#{0:X2}{1:X2}{2:X2}{3:X2}",
(int)alpha, (int)red, (int)green, (int)blue);
}
else
{
return string.Format("#{0:X2}{1:X2}{2:X2}", (int)red, (int)green, (int)blue);
}
}
/// <summary>
/// Converts a web formatted hex string in the form #RRGGBB or #RRGGBBAA into a color.
/// </summary>
/// <returns>The <c>NSColor</c> represented by the hex string.</returns>
/// <param name="hexValue">The web formatted hex string in the form #RRGGBB or #RRGGBBAA.</param>
public static NSColor NSColorFromHexString(string hexValue)
{
string colorString = hexValue.Replace("#", "");
float red, green, blue, alpha;
// Convert color based on length
switch (colorString.Length)
{
case 3: // #RGB
red = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(0, 1)), 16) / 255f;
green = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(1, 1)), 16) / 255f;
blue = Convert.ToInt32(string.Format("{0}{0}", colorString.Substring(2, 1)), 16) / 255f;
return NSColor.FromRgba(red, green, blue, 1.0f);
case 6: // #RRGGBB
red = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
green = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
blue = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
return NSColor.FromRgba(red, green, blue, 1.0f);
case 8: // #AARRGGBB
alpha = Convert.ToInt32(colorString.Substring(0, 2), 16) / 255f;
red = Convert.ToInt32(colorString.Substring(2, 2), 16) / 255f;
green = Convert.ToInt32(colorString.Substring(4, 2), 16) / 255f;
blue = Convert.ToInt32(colorString.Substring(6, 2), 16) / 255f;
return NSColor.FromRgba(red, green, blue, alpha);
default:
throw new ArgumentOutOfRangeException(string.Format("Invalid color value '{0}'. "
+ "It should be a hex value of the form #RBG, #RRGGBB or #AARRGGBB", hexValue));
}
}
/// <summary>Loads the requested color from system-wide user defaults.</summary>
/// <returns>The <c>NSColor</c> for the given key or the default value if the key
/// cannot be found in the user defaults.</returns>
/// <param name="key">The user default key for the color.</param>
/// <param name="defaultValue">The default <c>NSColor</c> value.</param>
public NSColor LoadColor(string key, NSColor defaultValue)
{
// Attempt to read color, add the language ID to make unique
string hex = NSUserDefaults.StandardUserDefaults.StringForKey(LanguageIdentifier + key);
// Take action based on value
if (hex == null)
{
return defaultValue;
}
else
{
return NSColorFromHexString(hex);
}
}
/// <summary>
/// Saves the given color to the systwm-wide user defaults with the give keyword.
/// </summary>
/// <param name="color">The <c>NSColor</c> to save to the user defaults.</param>
/// <param name="key">The user default key to assign the color to.</param>
/// <param name="sync">If set to <c>true</c> sync changes to preferences.</param>
public void SaveColor(string key, NSColor color, bool sync)
{
// Save to default, add the language ID to make unique
NSUserDefaults.StandardUserDefaults.SetString(NSColorToHexString(color, true), LanguageIdentifier + key);
if (sync) NSUserDefaults.StandardUserDefaults.Synchronize();
}
#endregion
}

View File

@ -0,0 +1,81 @@
using Foundation;
using System.Collections.Generic;
namespace Cauldron.Macos.SourceWriter;
public class LanguageFormatCommand : NSObject
{
#region Computed Properties
/// <summary>Gets or sets the title that will appear in the Formatting Menu.</summary>
/// <value>The title.</value>
public string Title { get; set; } = "";
/// <summary>
/// Gets or sets the prefix that will be added to the start of the line (if no <c>Postfix</c>
/// has been defines), or that will be inserted to the start of the current selected text in the
/// document editor.
/// </summary>
/// <value>The prefix.</value>
public string Prefix { get; set; } = "";
/// <summary>
/// Gets or sets the postfix that will added to the end of the selected text in the document
/// editor. If empty (""), the <c>Prefix</c> will be inserted at the start of the line that the
/// cursor is on.
/// </summary>
/// <value>The postfix.</value>
public string Postfix { get; set; } = "";
/// <summary>
/// Gets or sets the sub <see cref="LanguageFormatCommand"/> commands that will be displayed
/// under this item in the Formatting Menu.
/// </summary>
/// <value>The sub commands.</value>
public List<LanguageFormatCommand> SubCommands { get; set; }
= new List<LanguageFormatCommand>();
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="LanguageFormatCommand"/> class.
/// </summary>
public LanguageFormatCommand() { }
/// <summary>
/// Initializes a new instance of the <see cref="LanguageFormatCommand"/> class.
/// </summary>
/// <param name="title">The title for the menu item.</param>
public LanguageFormatCommand(string title)
{
this.Title = title;
}
/// <summary>
/// Initializes a new instance of the <see cref="LanguageFormatCommand"/> class.
/// </summary>
/// <param name="title">The title for the menu item.</param>
/// <param name="prefix">The prefix to insert.</param>
public LanguageFormatCommand(string title, string prefix)
{
this.Title = title;
this.Prefix = prefix;
}
/// <summary>
/// Initializes a new instance of the <see cref="LanguageFormatCommand"/> class.
/// </summary>
/// <param name="title">The title for the menu item.</param>
/// <param name="prefix">The prefix to insert.</param>
/// <param name="postfix">The postfix to insert.</param>
public LanguageFormatCommand(string title, string prefix, string postfix)
{
this.Title = title;
this.Prefix = prefix;
this.Postfix = postfix;
}
#endregion
}

View File

@ -0,0 +1,241 @@
namespace Cauldron.Macos.SourceWriter.LanguageFormats;
public class CSharpDescriptor : LanguageDescriptor
{
#region Computed Properties
/// <summary>Gets the language identifier.</summary>
/// <value>The language identifier.</value>
public override string LanguageIdentifier { get => "CSharp"; }
/// <summary>Gets or sets the language separators for C#</summary>
/// <value>The language separators.</value>
public override char[] LanguageSeparators { get; set; } = new char[]
{
'=', '+', '-', '*', '/', '%', '&', '<', '>', ';', ':', '^', '!', '~', '?', '|', ',', '"',
'\'', '(', ')', '[', ']', '{', '}'
};
#endregion
#region Constructors
/// <summary>Initializes a new instance of the <see cref="CSharpDescriptor"/> class.</summary>
public CSharpDescriptor() { }
#endregion
#region Override Methods
/// <summary>Define this instance.</summary>
public override void Define()
{
// Call base class
base.Define();
// Value Types
// Keywords.Add("", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, ""));
Keywords.Add("bool", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "It is used to declare variables to store the Boolean values, true and false."));
Keywords.Add("byte", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The byte keyword denotes an integral type that stores values between 0 to 255."));
Keywords.Add("char", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The char keyword is used to declare an instance of the System.Char structure that the .NET Framework uses to represent a Unicode character."));
Keywords.Add("decimal", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The decimal keyword denotes an integral type that stores values between (-7.9 x 10^28 to 7.9 x 10^28) / (10^(0 to 28))."));
Keywords.Add("double", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The double keyword signifies a simple type that stores 64-bit floating-point values."));
Keywords.Add("enum", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The enum keyword is used to declare an enumeration, a distinct type that consists of a set of named constants called the enumerator list."));
Keywords.Add("float", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The float keyword signifies a simple type that stores 32-bit floating-point values. "));
Keywords.Add("int", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The int keyword denotes an integral type that stores values between -2,147,483,648 to 2,147,483,647."));
Keywords.Add("long", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The long keyword denotes an integral type that stores values between 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807."));
Keywords.Add("sbyte", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The sbyte keyword indicates an integral type that stores values between -128 to 127."));
Keywords.Add("short", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The short keyword denotes an integral data type that stores values between -32,768 to 32,767."));
Keywords.Add("struct", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "A struct type is a value type that is typically used to encapsulate small groups of related variables, such as the coordinates of a rectangle or the characteristics of an item in an inventory."));
Keywords.Add("uint", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The uint keyword signifies an integral type that stores values between 0 to 4,294,967,295."));
Keywords.Add("ulong", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The ulong keyword denotes an integral type that stores values between 0 to 18,446,744,073,709,551,615."));
Keywords.Add("ushort", new KeywordDescriptor(KeywordType.ValueType, ValueTypeColor, "The ushort keyword indicates an integral data type that stores values between 0 to 65,535."));
// Reference Types
// Keywords.Add("", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, ""));
Keywords.Add("class", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "Classes are declared using the keyword class."));
Keywords.Add("delegate", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "The declaration of a delegate type is similar to a method signature. It has a return value and any number of parameters of any type."));
Keywords.Add("dynamic", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "The dynamic type enables the operations in which it occurs to bypass compile-time type checking. Instead, these operations are resolved at run time."));
Keywords.Add("interface", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "An interface contains only the signatures of methods, properties, events or indexers. "));
Keywords.Add("object", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "In the unified type system of C#, all types, predefined and user-defined, reference types and value types, inherit directly or indirectly from Object. You can assign values of any type to variables of type object. "));
Keywords.Add("string", new KeywordDescriptor(KeywordType.ReferenceType, ReferenceTypeColor, "The string type represents a sequence of zero or more Unicode characters. string is an alias for String in the .NET Framework."));
// Generic Types
// Keywords.Add("", new KeywordDescriptor(KeywordType.Type, TypeColor, ""));
Keywords.Add("void", new KeywordDescriptor(KeywordType.Type, TypeColor, "When used as the return type for a method, void specifies that the method doesn't return a value."));
Keywords.Add("var", new KeywordDescriptor(KeywordType.Type, TypeColor, "An implicitly typed local variable is strongly typed just as if you had declared the type yourself, but the compiler determines the type."));
// Access modifiers
// Keywords.Add("", new KeywordDescriptor(KeywordType.AccessModifier, AccessModifierColor, ""));
Keywords.Add("public", new KeywordDescriptor(KeywordType.AccessModifier, AccessModifierColor, "The public keyword is an access modifier for types and type members. There are no restrictions on accessing public members."));
Keywords.Add("private", new KeywordDescriptor(KeywordType.AccessModifier, AccessModifierColor, "Private members are accessible only within the body of the class or the struct in which they are declared."));
Keywords.Add("internal", new KeywordDescriptor(KeywordType.AccessModifier, AccessModifierColor, "Internal types or members are accessible only within files in the same assembly."));
Keywords.Add("protected", new KeywordDescriptor(KeywordType.AccessModifier, AccessModifierColor, "A protected member is accessible within its class and by derived class instances."));
// Modifiers
// Keywords.Add("", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, ""));
Keywords.Add("abstract", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The abstract modifier indicates that the thing being modified has a missing or incomplete implementation."));
Keywords.Add("async", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "Use the async modifier to specify that a method, lambda expression, or anonymous method is asynchronous. "));
Keywords.Add("const", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "You use the const keyword to declare a constant field or a constant local. Constant fields and locals aren't variables and may not be modified. "));
Keywords.Add("event", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The event keyword is used to declare an event in a publisher class."));
Keywords.Add("extern", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The extern modifier is used to declare a method that is implemented externally. "));
Keywords.Add("in", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "For generic type parameters, the in keyword specifies that the type parameter is contravariant. You can use the in keyword in generic interfaces and delegates."));
Keywords.Add("override", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The override modifier is required to extend or modify the abstract or virtual implementation of an inherited method, property, indexer, or event."));
Keywords.Add("readonly", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class."));
Keywords.Add("sealed", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "When applied to a class, the sealed modifier prevents other classes from inheriting from it."));
Keywords.Add("static", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "Use the static modifier to declare a static member, which belongs to the type itself rather than to a specific object. "));
Keywords.Add("unsafe", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The unsafe keyword denotes an unsafe context, which is required for any operation involving pointers."));
Keywords.Add("virtual", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class. "));
Keywords.Add("volatile", new KeywordDescriptor(KeywordType.Modifier, ModifierColor, "The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time. "));
// Selection Statements
// Keywords.Add("", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, ""));
Keywords.Add("if", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, "An if statement identifies which statement to run based on the value of a Boolean expression."));
Keywords.Add("else", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, "In an if-else statement, if condition evaluates to true, the then-statement runs. If condition is false, the else-statement runs. "));
Keywords.Add("switch", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, "The switch statement is a control statement that selects a switch section to execute from a list of candidates."));
Keywords.Add("case", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, "Each case label specifies a constant value."));
Keywords.Add("default", new KeywordDescriptor(KeywordType.SelectionStatement, SelectionStatementColor, "f no case label contains a matching value, control is transferred to the default section, if there is one."));
// Iteration Statements
// Keywords.Add("", new KeywordDescriptor(KeywordType.IterationStatement, IterationStatementColor, ""));
Keywords.Add("do", new KeywordDescriptor(KeywordType.IterationStatement, IterationStatementColor, "The do statement executes a statement or a block of statements repeatedly until a specified expression evaluates to false. "));
Keywords.Add("for", new KeywordDescriptor(KeywordType.IterationStatement, IterationStatementColor, "By using a for loop, you can run a statement or a block of statements repeatedly until a specified expression evaluates to false."));
Keywords.Add("foreach", new KeywordDescriptor(KeywordType.IterationStatement, IterationStatementColor, "The foreach statement repeats a group of embedded statements for each element in an array or an object collection that implements the System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> interface. "));
Keywords.Add("while", new KeywordDescriptor(KeywordType.IterationStatement, IterationStatementColor, "The while statement executes a statement or a block of statements until a specified expression evaluates to false."));
// Jump Statements
// Keywords.Add("", new KeywordDescriptor(KeywordType.JumpStatement, JumpStatementColor, ""));
Keywords.Add("break", new KeywordDescriptor(KeywordType.JumpStatement, JumpStatementColor, "The break statement terminates the closest enclosing loop or switch statement in which it appears. Control is passed to the statement that follows the terminated statement, if any."));
Keywords.Add("continue", new KeywordDescriptor(KeywordType.JumpStatement, JumpStatementColor, "The continue statement passes control to the next iteration of the enclosing while, do, for, or foreach statement in which it appears."));
Keywords.Add("goto", new KeywordDescriptor(KeywordType.JumpStatement, JumpStatementColor, "The goto statement transfers the program control directly to a labeled statement."));
Keywords.Add("return", new KeywordDescriptor(KeywordType.JumpStatement, JumpStatementColor, "The return statement terminates execution of the method in which it appears and returns control to the calling method. It can also return an optional value."));
// Exception Handling Statements
// Keywords.Add("", new KeywordDescriptor(KeywordType.ExceptionHandlingStatement, ExceptionHandlingColor, ""));
Keywords.Add("throw", new KeywordDescriptor(KeywordType.ExceptionHandlingStatement, ExceptionHandlingColor, "The throw statement is used to signal the occurrence of an anomalous situation (exception) during the program execution."));
Keywords.Add("try", new KeywordDescriptor(KeywordType.ExceptionHandlingStatement, ExceptionHandlingColor, "The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions."));
Keywords.Add("catch", new KeywordDescriptor(KeywordType.ExceptionHandlingStatement, ExceptionHandlingColor, "The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions."));
Keywords.Add("finally", new KeywordDescriptor(KeywordType.ExceptionHandlingStatement, ExceptionHandlingColor, "A common usage of catch and finally together is to obtain and use resources in a try block, deal with exceptional circumstances in a catch block, and release the resources in the finally block."));
// Statements
// Keywords.Add("", new KeywordDescriptor(KeywordType.Statement, StatementColor, ""));
Keywords.Add("checked", new KeywordDescriptor(KeywordType.Statement, StatementColor, "The checked keyword is used to explicitly enable overflow checking for integral-type arithmetic operations and conversions."));
Keywords.Add("unchecked", new KeywordDescriptor(KeywordType.Statement, StatementColor, "The unchecked keyword is used to suppress overflow-checking for integral-type arithmetic operations and conversions."));
Keywords.Add("fixed", new KeywordDescriptor(KeywordType.Statement, StatementColor, "The fixed statement prevents the garbage collector from relocating a movable variable. The fixed statement is only permitted in an unsafe context."));
Keywords.Add("lock", new KeywordDescriptor(KeywordType.Statement, StatementColor, "The lock keyword marks a statement block as a critical section by obtaining the mutual-exclusion lock for a given object, executing a statement, and then releasing the lock."));
// Method Parameters
// Keywords.Add("", new KeywordDescriptor(KeywordType.MethodParameters, MethodParameterColor, ""));
Keywords.Add("params", new KeywordDescriptor(KeywordType.MethodParameters, MethodParameterColor, "By using the params keyword, you can specify a method parameter that takes a variable number of arguments."));
Keywords.Add("ref", new KeywordDescriptor(KeywordType.MethodParameters, MethodParameterColor, "The ref keyword causes an argument to be passed by reference, not by value."));
Keywords.Add("out", new KeywordDescriptor(KeywordType.MethodParameters, MethodParameterColor, "The out keyword causes arguments to be passed by reference. This is like the ref keyword, except that ref requires that the variable be initialized before it is passed. "));
// Namespace Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.NamespaceKeyword, NamespaceColor, ""));
Keywords.Add("namespace", new KeywordDescriptor(KeywordType.NamespaceKeyword, NamespaceColor, "The namespace keyword is used to declare a scope that contains a set of related objects."));
Keywords.Add("using", new KeywordDescriptor(KeywordType.MethodParameters, NamespaceColor, "Allows the use of types in a namespace so that you do not have to qualify the use of a type in that namespace."));
// Operator Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, ""));
Keywords.Add("as", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "You can use the as operator to perform certain types of conversions between compatible reference types or nullable types."));
Keywords.Add("await", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes."));
Keywords.Add("is", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Checks if an object is compatible with a given type."));
Keywords.Add("new", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Used to create objects and invoke constructors."));
Keywords.Add("sizeof", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Used to obtain the size in bytes for an unmanaged type."));
Keywords.Add("typeof", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Used to obtain the System.Type object for a type."));
Keywords.Add("true", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Represents the boolean value true."));
Keywords.Add("false", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Represents the boolean value false."));
Keywords.Add("stackalloc", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "The stackalloc keyword is used in an unsafe code context to allocate a block of memory on the stack."));
Keywords.Add("nameof", new KeywordDescriptor(KeywordType.OperatorKeyword, OperatorKeywordColor, "Used to obtain the simple (unqualified) string name of a variable, type, or member. "));
// Conversion Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.ConversionKeyword, ConversionKeywordColor, ""));
Keywords.Add("explicit", new KeywordDescriptor(KeywordType.ConversionKeyword, ConversionKeywordColor, "The explicit keyword declares a user-defined type conversion operator that must be invoked with a cast. "));
Keywords.Add("implicit", new KeywordDescriptor(KeywordType.ConversionKeyword, ConversionKeywordColor, "The implicit keyword is used to declare an implicit user-defined type conversion operator. "));
Keywords.Add("operator", new KeywordDescriptor(KeywordType.ConversionKeyword, ConversionKeywordColor, "Use the operator keyword to overload a built-in operator or to provide a user-defined conversion in a class or struct declaration."));
// Access Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.AccessKeywords, AccessKeywordColor, ""));
Keywords.Add("base", new KeywordDescriptor(KeywordType.AccessKeywords, AccessKeywordColor, "Accesses the members of the base class."));
Keywords.Add("this", new KeywordDescriptor(KeywordType.AccessKeywords, AccessKeywordColor, "Refers to the current instance of the class."));
// Literal Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.LiteralKeywords, LiteralKeywordColor, ""));
Keywords.Add("null", new KeywordDescriptor(KeywordType.LiteralKeywords, LiteralKeywordColor, "The null keyword is a literal that represents a null reference, one that does not refer to any object. null is the default value of reference-type variables."));
// Contextual Keywords
// Keywords.Add("", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, ""));
Keywords.Add("add", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The add contextual keyword is used to define a custom event accessor that is invoked when client code subscribes to your event."));
Keywords.Add("get", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The get keyword defines an accessor method in a property or indexer that retrieves the value of the property or the indexer element."));
Keywords.Add("global", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The global contextual keyword, when it comes before the :: operator, refers to the global namespace, which is the default namespace for any C# program and is otherwise unnamed."));
Keywords.Add("partial", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "Partial type definitions allow for the definition of a class, struct, interface or method to be split into multiple files or definitions."));
Keywords.Add("remove", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The remove contextual keyword is used to define a custom event accessor that is invoked when client code unsubscribes from your event."));
Keywords.Add("set", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The set keyword defines an accessor method in a property or indexer that assigns the value of the property or the indexer element."));
Keywords.Add("where", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "In a generic type definition, the where clause is used to specify constraints on the types that can be used as arguments for a type parameter defined in a generic declaration."));
Keywords.Add("value", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "The contextual keyword value is used in the set accessor in ordinary property declarations. It is similar to an input parameter on a method. "));
Keywords.Add("yield", new KeywordDescriptor(KeywordType.ContextualKeywords, ContextualKeywordColor, "When you use the yield keyword in a statement, you indicate that the method, operator, or get accessor in which it appears is an iterator. "));
// Query Keywords (LINQ)
// Keywords.Add("", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, ""));
Keywords.Add("from", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "A query expression must begin with a from clause. Additionally, a query expression can contain sub-queries, which also begin with a from clause. "));
Keywords.Add("select", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "In a query expression, the select clause specifies the type of values that will be produced when the query is executed."));
Keywords.Add("group", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The group clause returns a sequence of IGrouping<TKey,TElement> objects that contain zero or more items that match the key value for the group."));
Keywords.Add("into", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The into contextual keyword can be used to create a temporary identifier to store the results of a group, join or select clause into a new identifier."));
Keywords.Add("orderby", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "In a query expression, the orderby clause causes the returned sequence or subsequence (group) to be sorted in either ascending or descending order."));
Keywords.Add("join", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The join clause is useful for associating elements from different source sequences that have no direct relationship in the object model."));
Keywords.Add("let", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "In a query expression, it is sometimes useful to store the result of a sub-expression in order to use it in subsequent clauses. You can do this with the let keyword."));
Keywords.Add("ascending", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The ascending contextual keyword is used in the orderby clause in query expressions to specify that the sort order is from smallest to largest."));
Keywords.Add("descending", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The descending contextual keyword is used in the orderby clause in query expressions to specify that the sort order is from largest to smallest."));
Keywords.Add("on", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The on contextual keyword is used in the join clause of a query expression to specify the join condition."));
Keywords.Add("equals", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The equals contextual keyword is used in a join clause in a query expression to compare the elements of two sequences."));
Keywords.Add("by", new KeywordDescriptor(KeywordType.QueryKeywords, QueryKeywordColor, "The by contextual keyword is used in the group clause in a query expression to specify how the returned items should be grouped."));
// Preprocessor Directive
// Keywords.Add("", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, ""));
Keywords.Add("#if", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "When the C# compiler encounters an #if directive, followed eventually by an #endif directive, it will compile the code between the directives only if the specified symbol is defined."));
Keywords.Add("#else", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#else lets you create a compound conditional directive, so that, if none of the expressions in the preceding #if or (optional) #elif directives to true, the compiler will evaluate all code between #else and the subsequent #endif."));
Keywords.Add("#elif", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#elif lets you create a compound conditional directive. The #elif expression will be evaluated if neither the preceding #if (C# Reference) nor any preceding, optional, #elif directive expressions evaluate to true."));
Keywords.Add("#endif", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#endif specifies the end of a conditional directive, which began with the #if directive."));
Keywords.Add("#define", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "You use #define to define a symbol."));
Keywords.Add("#undef", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#undef lets you undefine a symbol, such that, by using the symbol as the expression in a #if directive, the expression will evaluate to false."));
Keywords.Add("#warning", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#warning lets you generate a level one warning from a specific location in your code."));
Keywords.Add("#error", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#error lets you generate an error from a specific location in your code."));
Keywords.Add("#line", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#line lets you modify the compiler's line number and (optionally) the file name output for errors and warnings. This example shows how to report two warnings associated with line numbers."));
Keywords.Add("#region", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#region lets you specify a block of code that you can expand or collapse when using the outlining feature."));
Keywords.Add("#endregion", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#endregion marks the end of a #region block."));
Keywords.Add("#pragma", new KeywordDescriptor(KeywordType.PreprocessorDirective, PreprocessorDirectiveColor, "#pragma gives the compiler special instructions for the compilation of the file in which it appears."));
// Define formats
Formats.Add(new FormatDescriptor("//", CommentColor));
Formats.Add(new FormatDescriptor("/*", "*/", CommentColor));
Formats.Add(new FormatDescriptor("\"", "\"", StringLiteralColor));
Formats.Add(new FormatDescriptor("'", "'", StringLiteralColor));
// Define formatting commands
FormattingCommands.Add(new LanguageFormatCommand("Comment", "//"));
FormattingCommands.Add(new LanguageFormatCommand("Block Comment", "/*", "*/"));
FormattingCommands.Add(new LanguageFormatCommand());
var Directives = new LanguageFormatCommand("Preprocessor Directives");
Directives.SubCommands.Add(new LanguageFormatCommand("If", "#if \n", "\n#endif\n"));
Directives.SubCommands.Add(new LanguageFormatCommand("If..Else", "#if \n", "\n#else\n\n#endif\n"));
Directives.SubCommands.Add(new LanguageFormatCommand("Define", "#define "));
Directives.SubCommands.Add(new LanguageFormatCommand("Undefine", "#undef"));
Directives.SubCommands.Add(new LanguageFormatCommand("Warning", "#warning"));
Directives.SubCommands.Add(new LanguageFormatCommand("Error", "#error"));
Directives.SubCommands.Add(new LanguageFormatCommand("Line", "#line"));
Directives.SubCommands.Add(new LanguageFormatCommand("Region", "#region \n", "\n#endregion\n"));
Directives.SubCommands.Add(new LanguageFormatCommand("Pragma", "#pragma"));
FormattingCommands.Add(Directives);
}
/// <summary>Formats the passed in string of text for previewing.</summary>
/// <returns>The string formatted for preview.</returns>
/// <param name="text">Text.</param>
public override string FormatForPreview(string text)
{
return "<pre><code>" + text + "</code></pre>";
}
#endregion
}

View File

@ -0,0 +1,482 @@
using AppKit;
using Foundation;
namespace Cauldron.Macos.SourceWriter;
[Register("LanguageFormatter")]
public class LanguageFormatter : NSObject
{
#region Private Variables
/// <summary>The current language syntax highlighting descriptor.</summary>
private LanguageDescriptor _language = new();
#endregion
#region Computed Properties
/// <summary>
/// Gets or sets the text view that this language formatter will be performing syntax
/// highlighting on.
/// </summary>
/// <value>The <c>NSTextView</c> to syntax highlight.</value>
public NSTextView TextEditor { get; set; }
/// <summary>Gets or sets the newline character used to define a given line of text.</summary>
/// <value>The newline character.</value>
public char Newline { get; set; } = '\n';
/// <summary>
/// Gets or sets the Unitext line separator used to define a given line of text.
/// </summary>
/// <value>The line separator.</value>
public char LineSeparator { get; set; } = '\u2028';
/// <summary>
/// Gets or sets the Unitext paragraph separator used to define a given paragraph of text.
/// </summary>
/// <value>The paragraph separator.</value>
public char ParagraphSeparator { get; set; } = '\u2029';
/// <summary>
/// Gets or sets the descriptor used to define the syntax highlighting rules for a given
/// language.
/// </summary>
/// <value>The <see cref="LanguageDescriptor"/> to syntax highlight.</value>
public LanguageDescriptor Language
{
get { return _language; }
set
{
_language = value;
_language.Define();
Reformat();
}
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="LanguageFormatter"/> class.
/// </summary>
/// <param name="textEditor">
/// The <c>NSTextView</c> that this language formatter will syntax highlight.
/// </param>
/// <param name="language">The <see cref="LanguageDescriptor"/> defining the
/// language syntax highlighting rules.</param>
public LanguageFormatter(NSTextView textEditor, LanguageDescriptor language)
{
this.TextEditor = textEditor;
this.Language = language;
}
#endregion
#region Public Methods
/// <summary>
/// Forces all of the text in the attached <c>NSTextView</c> (the <c>TextEditor</c> property) to
/// have its syntax rehighlighted by re-running the formatter.
/// </summary>
public virtual void Reformat()
{
// Reformat all text in the view control
NSRange range = new(0, TextEditor.Value.Length);
TextEditor.LayoutManager.RemoveTemporaryAttribute(NSStringAttributeKey.ForegroundColor, range);
HighlightSyntaxRegion(TextEditor.Value, range);
TextEditor.SetNeedsDisplay(TextEditor.Frame, false);
}
/// <summary>Determines whether the passed in character is a language separator.</summary>
/// <returns>
/// <c>true</c> if the character is a language separator; otherwise, <c>false</c>.
/// </returns>
/// <param name="c">The character being tested.</param>
public virtual bool IsLanguageSeparator(char c)
{
// Found separator?
for (var n = 0; n < Language.LanguageSeparators.Length; ++n)
{
if (Language.LanguageSeparators[n] == c)
return true;
}
// Not found
return false;
}
/// <summary>
/// Finds the word boundries as defined by the <c>LanguageSeparators</c> in the
/// <see cref="AppKit.TextKit.Formatter.LanguageDescriptor"/> that is currently
/// being syntax highlighted.
/// </summary>
/// <returns>An <c>NSRange</c> containing the starting and ending character locations
/// of the current word.</returns>
/// <param name="text">The string to be searched.</param>
/// <param name="position">
/// The <c>NSRange</c> specifying the starting location of a possible word.
/// </param>
public virtual NSRange FindWordBoundries(string text, NSRange position)
{
NSRange results = new(position.Location, 0);
bool found = false;
// Find starting "word" boundry
while (results.Location > 0 && !found)
{
var c = text[(int)results.Location - 1];
found = char.IsWhiteSpace(c) || IsLanguageSeparator(c);
if (!found) results.Location -= 1;
};
// Find ending "word" boundry
found = false;
while ((int)(results.Location + results.Length) < text.Length && !found)
{
var c = text[(int)(results.Location + results.Length)];
found = char.IsWhiteSpace(c) || IsLanguageSeparator(c);
if (!found) results.Length += 1;
};
return results;
}
/// <summary>
/// Finds the line boundries as defined by the <c>NewLine</c>, <c>LineSeparator</c>
/// and <c>ParagraphSeparator</c> characters.
/// </summary>
/// <returns>An <c>NSRange</c> containing the starting and ending character locations
/// of the current line of text.</returns>
/// <param name="text">The string to be searched.</param>
/// <param name="position">The <c>NSRange</c> specifying the starting location of a possible
/// line of text.</param>
public virtual NSRange FindLineBoundries(string text, NSRange position)
{
NSRange results = position;
bool found = false;
// Find starting line boundry
while (results.Location > 0 && !found)
{
var c = text[(int)results.Location - 1];
found = (c == Newline || c == LineSeparator || c == ParagraphSeparator);
if (!found) results.Location -= 1;
};
// Find ending line boundry
found = false;
while ((int)(results.Location + results.Length) < text.Length && !found)
{
var c = text[(int)(results.Location + results.Length)];
found = (c == Newline || c == LineSeparator || c == ParagraphSeparator);
if (!found) results.Length += 1;
};
return results;
}
/// <summary>
/// Finds the start of line for the given location in the text as defined by the <c>NewLine</c>,
/// <c>LineSeparator</c> and <c>ParagraphSeparator</c> characters.
/// </summary>
/// <returns>
/// A <c>NSRange</c> containing the start of the line to the current cursor position.
/// </returns>
/// <param name="text">The text to find the start of the line in.</param>
/// <param name="position">
/// The current location of the cursor in the text and possible selection.
/// </param>
public virtual NSRange FindStartOfLine(string text, NSRange position)
{
NSRange results = new(position.Location, position.Length);
bool found = false;
// Find starting line boundry
while (results.Location > 0 && !found)
{
var c = text[(int)results.Location - 1];
found = (c == Newline || c == LineSeparator || c == ParagraphSeparator);
if (!found) results.Location -= 1;
};
// Calculate length
results.Length = position.Location - results.Location;
return results;
}
/// <summary>
/// Finds the start of end for the given location in the text as defined by the <c>NewLine</c>,
/// <c>LineSeparator</c> and <c>ParagraphSeparator</c> characters.
/// </summary>
/// <returns>
/// A <c>NSRange</c> containing the end of the line from the current cursor position.
/// </returns>
/// <param name="text">The text to find the end of the line in.</param>
/// <param name="position">
/// The current location of the cursor in the text and possible selection.
/// </param>
public virtual NSRange FindEndOfLine(string text, NSRange position)
{
NSRange results = position;
// Find ending line boundry
bool found = false;
while ((int)(results.Location + results.Length) < text.Length && !found)
{
char c = text[(int)(results.Location + results.Length)];
found = (c == Newline || c == LineSeparator || c == ParagraphSeparator);
if (!found) results.Length += 1;
};
return results;
}
/// <summary>Tests to see if the preceeding character is whitespace or terminator.</summary>
/// <returns>
/// <c>true</c>, if character is whitespace or terminator, <c>false</c> otherwise.
/// </returns>
/// <param name="text">The text to test.</param>
/// <param name="position">The current cursor position inside the text.</param>
/// <remarks>Returns <c>true</c> if at start of line.</remarks>
public virtual bool PreceedingCharacterIsWhitespaceOrTerminator(string text, NSRange position)
{
// At start of line?
if (position.Location == 0)
{
// Yes, always true
return true;
}
// Found?
char c = text[(int)(position.Location - 1)];
bool found = c == ' ' | c == Newline || c == LineSeparator || c == ParagraphSeparator;
// Return result
return found;
}
/// <summary>Tests to see if the trailing character is whitespace or terminator.</summary>
/// <returns>
/// <c>true</c>, if character is whitespace or terminator, <c>false</c> otherwise.
/// </returns>
/// <param name="text">The text to test.</param>
/// <param name="position">The current cursor position inside the text.</param>
/// <remarks>Returns <c>true</c> if at end of line.</remarks>
public virtual bool TrailingCharacterIsWhitespaceOrTerminator(string text, NSRange position)
{
// At end of line?
if (position.Location >= text.Length - 1)
{
// Yes, always true
return true;
}
// Found?
char c = text[(int)(position.Location + 1)];
bool found = c == ' ' | c == Newline || c == LineSeparator || c == ParagraphSeparator;
// Return result
return found;
}
/// <summary>
/// Uses the current <c>Language</c> (<see cref="LanguageDescriptor"/>) to syntax highlight the
/// given word in the attached <c>TextEditor</c> (<c>NSTextView</c>) at the given character
/// locations.
/// </summary>
/// <param name="word">The possible keyword to highlight.</param>
/// <param name="range">An <c>NSRange</c> specifying the starting and ending character locations
/// for the word to highlight.</param>
/// <remarks>
/// TODO: The Text Kit <c>SetTemporaryAttributes</c> routines are handiling the format of
/// character strings such as HTML or XML tag incorrectly.
/// </remarks>
public virtual void HighlightSyntax(string word, NSRange range)
{
try
{
// Found a keyword?
if (Language.Keywords.TryGetValue(word, out KeywordDescriptor info))
{
// Yes, adjust attributes
TextEditor.LayoutManager.SetTemporaryAttributes(
new NSDictionary(NSStringAttributeKey.ForegroundColor, info.Color), range);
}
else
{
TextEditor.LayoutManager.RemoveTemporaryAttribute(
NSStringAttributeKey.ForegroundColor, range);
}
}
catch
{
// Ignore any exceptions at this point
}
}
/// <summary>
/// Based on the current <c>Language</c> (<see cref="AppKit.TextKit.Formatter.LanguageDescriptor"/>),
/// highlight the syntax of the given character region.
/// </summary>
/// <param name="text">The string value to be syntax highlighted.</param>
/// <param name="position">The starting location of the text to be highlighted.</param>
public virtual void HighlightSyntaxRegion(string text, NSRange position)
{
NSRange range = FindLineBoundries(text, position);
string word = "";
nint location = range.Location;
char l = ' ';
NSRange segment = new(range.Location, 0);
bool handled = false;
FormatDescriptor inFormat = null;
// Initialize
Language.ClearFormats();
// Process all characters in range
for (int n = 0; n < range.Length; ++n)
{
// Get next character
char c = text[(int)(range.Location + n)];
//Console.Write ("[{0}]={1}", n, c);
// Excape character?
if (c == Language.EscapeCharacter || l == Language.EscapeCharacter)
{
// Was the last chanacter an escape?
if (l == Language.EscapeCharacter)
{
// Handling outlying format exception
c = ' ';
}
handled = true;
// Are we inside a format?
if (inFormat != null)
{
// Yes, increase segment count
++segment.Length;
}
}
else
{
// Are we inside of a formatter?
if (inFormat == null)
{
// No, see if this character is recognized by a formatter
foreach (FormatDescriptor format in Language.Formats)
{
if (format.MatchesCharacter(c))
{
if (format.Triggered)
{
Language.ClearFormats();
inFormat = format;
inFormat.Active = true;
segment = new NSRange((range.Location + n) - (inFormat.StartsWith.Length - 1), inFormat.StartsWith.Length);
//Console.WriteLine ("Found Format [{0}] = {1}", inFormat.StartsWith, segment);
}
handled = true;
}
}
}
else
{
// Prefix or enclosure?
if (inFormat.Type == FormatDescriptorType.Prefix)
{
// At end of line?
if (c == Newline || c == LineSeparator || c == ParagraphSeparator)
{
++segment.Length;
TextEditor.LayoutManager.SetTemporaryAttributes(new NSDictionary(NSStringAttributeKey.ForegroundColor, inFormat.Color), segment);
//Console.WriteLine ("Complete Prefix [{0}] = {1}", inFormat.StartsWith, segment);
location = range.Location + n + 1;
word = "";
inFormat = null;
Language.ClearFormats();
}
else
{
++segment.Length;
}
handled = true;
}
else
{
if (inFormat.MatchesCharacter(c))
{
if (inFormat.Triggered)
{
++segment.Length;
TextEditor.LayoutManager.SetTemporaryAttributes(new NSDictionary(NSStringAttributeKey.ForegroundColor, inFormat.Color), segment);
//Console.WriteLine ("Complete Enclosure [{0}] = {1}", inFormat.EndsWith, segment);
inFormat = null;
Language.ClearFormats();
}
}
++segment.Length;
handled = true;
}
}
}
// Has this character already been handled?
if (!handled)
{
// No, handle normal characters
bool found = char.IsWhiteSpace(c) || IsLanguageSeparator(c);
if (found)
{
segment = new NSRange(location, word.Length);
if (segment.Length > 0)
{
HighlightSyntax(word, segment);
}
location = range.Location + n + 1;
word = "";
}
else
{
word += c;
}
// Clear any fully unmatched formats
if (inFormat == null)
{
Language.ClearFormats();
}
}
// Save last character
l = c;
handled = false;
}
// Finalize
if (inFormat != null)
{
if (inFormat.Type == FormatDescriptorType.Prefix)
{
TextEditor.LayoutManager.SetTemporaryAttributes(new NSDictionary(NSStringAttributeKey.ForegroundColor, inFormat.Color), segment);
//Console.WriteLine ("Finalize Prefix [{0}] = {1}", inFormat.StartsWith, segment);
}
Language.ClearFormats();
}
else if (word != "")
{
segment = new NSRange(location, word.Length);
if (segment.Length > 0)
{
HighlightSyntax(word, segment);
}
}
//Console.WriteLine (";");
}
#endregion
}

View File

@ -0,0 +1,762 @@
using System;
using AppKit;
using CoreGraphics;
using Foundation;
namespace Cauldron.Macos.SourceWriter;
[Register("SourceTextView")]
public class SourceTextView : NSTextView
{
#region Static Constants
/// <summary>Defines the constant Unicode value of the enter key.</summary>
public const int EnterKey = 13;
/// <summary>Defines the constant Unicode value of the tab key.</summary>
public const int TabKey = 9;
/// <summary>Defines the constant Unicode value of the shift-tab key.</summary>
public const int ShiftTabKey = 25;
#endregion
#region Private Variables
/// <summary>The current language formatter used to highlight syntax.</summary>
private LanguageFormatter _formatter;
/// <summary>Should the editor auto complete closures.</summary>
private bool _completeClosures = true;
/// <summary>Should the editor auto wrap selected text in. </summary>
private bool _wrapClosures = true;
/// <summary>
/// Should the edit select the section of text that has just been wrapped in a closure.
/// </summary>
private bool _selectAfterWrap = true;
/// <summary>Should the editor provide auto completion of partial words.</summary>
private bool _allowAutoComplete = true;
/// <summary>
/// Should the editor auto complete keywords as defined in the current language.
/// </summary>
private bool _autoCompleteKeywords = true;
/// <summary>Should the editor use the default words list for auto complete.</summary>
private bool _autoCompleteDefaultWords = true;
/// <summary>Should the editor only use default words if the keyword list is empty.</summary>
private bool _defaultWordsOnlyIfKeywordsEmpty = true;
#endregion
#region Computed Properties
/// <summary>
/// Gets or sets the <see cref="LanguageFormatter"/> used to perform
/// syntax highlighting on this <c>NSTextView</c> containing the contents of the document being
/// edited.
/// </summary>
/// <value>The <see cref="LanguageFormatter"/> for the selected language.</value>
[Export("Formatter")]
public LanguageFormatter Formatter
{
get { return _formatter; }
set
{
WillChangeValue("Formatter");
_formatter = value;
DidChangeValue("Formatter");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/> allows auto complete
/// of partial words.
/// </summary>
/// <value><c>true</c> if allows auto complete; otherwise, <c>false</c>.</value>
[Export("AllowAutoComplete")]
public bool AllowAutoComplete
{
get { return _allowAutoComplete; }
set
{
WillChangeValue("AllowAutoComplete");
_allowAutoComplete = value;
DidChangeValue("AllowAutoComplete");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/> auto completes keywords.
/// </summary>
/// <value><c>true</c> if auto completes keywords; otherwise, <c>false</c>.</value>
[Export("AutoCompleteKeywords")]
public bool AutoCompleteKeywords
{
get { return _autoCompleteKeywords; }
set
{
WillChangeValue("AutoCompleteKeywords");
_autoCompleteKeywords = value;
DidChangeValue("AutoCompleteKeywords");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ASourceTextView"/> auto completes
/// default words.
/// </summary>
/// <value><c>true</c> if auto complete default words; otherwise, <c>false</c>.</value>
[Export("AutoCompleteDefaultWords")]
public bool AutoCompleteDefaultWords
{
get { return _autoCompleteDefaultWords; }
set
{
WillChangeValue("AutoCompleteDefaultWords");
_autoCompleteDefaultWords = value;
DidChangeValue("AutoCompleteDefaultWords");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/>
/// uses the default words (provided by OS X) only if keywords empty.
/// </summary>
/// <value><c>true</c> if use the default words only if keywords empty; otherwise, <c>false</c>.</value>
[Export("DefaultWordsOnlyIfKeywordsEmpty")]
public bool DefaultWordsOnlyIfKeywordsEmpty
{
get { return _defaultWordsOnlyIfKeywordsEmpty; }
set
{
WillChangeValue("DefaultWordsOnlyIfKeywordsEmpty");
_defaultWordsOnlyIfKeywordsEmpty = value;
DidChangeValue("DefaultWordsOnlyIfKeywordsEmpty");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/> complete closures.
/// </summary>
/// <value><c>true</c> if complete closures; otherwise, <c>false</c>.</value>
[Export("CompleteClosures")]
public bool CompleteClosures
{
get { return _completeClosures; }
set
{
WillChangeValue("CompleteClosures");
_completeClosures = value;
DidChangeValue("CompleteClosures");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/> wrap closures.
/// </summary>
/// <value><c>true</c> if wrap closures; otherwise, <c>false</c>.</value>
[Export("WrapClosures")]
public bool WrapClosures
{
get { return _wrapClosures; }
set
{
WillChangeValue("WrapClosures");
_wrapClosures = true;
DidChangeValue("WrapClosures");
}
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="SourceTextView"/> selects
/// the text that has just been wrapped in a closure.
/// </summary>
/// <value><c>true</c> if select after wrap; otherwise, <c>false</c>.</value>
[Export("SelectAfterWrap")]
public bool SelectAfterWrap
{
get { return _selectAfterWrap; }
set
{
WillChangeValue("SelectAfterWrap");
_selectAfterWrap = value;
DidChangeValue("SelectAfterWrap");
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.SourceTextView"/> class.
/// </summary>
public SourceTextView()
{
// Init
Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.SourceTextView"/> class.
/// </summary>
/// <param name="frameRect">Frame rect.</param>
public SourceTextView(CGRect frameRect) : base(frameRect)
{
// Init
Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.SourceTextView"/> class.
/// </summary>
/// <param name="frameRect">Frame rect.</param>
/// <param name="container">Container.</param>
public SourceTextView(CGRect frameRect, NSTextContainer container) : base(frameRect, container)
{
// Init
Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.SourceTextView"/> class.
/// </summary>
/// <param name="coder">Coder.</param>
public SourceTextView(NSCoder coder) : base(coder)
{
// Init
Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="AppKit.TextKit.Formatter.SourceTextView"/> class.
/// </summary>
/// <param name="handle">Handle.</param>
public SourceTextView(IntPtr handle) : base(handle)
{
// Init
Initialize();
}
/// <summary>
/// Initialize this instance.
/// </summary>
private void Initialize()
{
// Init
this.Delegate = new SourceTextViewDelegate(this);
}
#endregion
#region Private Methods
/// <summary>
/// Calculates the indent level by counting the number of tab characters
/// at the start of the current line.
/// </summary>
/// <returns>The indent level as the number of tabs.</returns>
/// <param name="line">The line of text being processed.</param>
private static int CalculateIndentLevel(string line)
{
int indent = 0;
// Process all characters in the line
for (int n = 0; n < line.Length; ++n)
{
var code = (int)line[n];
// Are we on a tab character?
if (code == TabKey)
{
++indent;
}
else
{
break;
}
}
// Return result
return indent;
}
/// <summary>
/// Creates a string of n number of tab characters that will be used to keep
/// the tab level of the current line of text.
/// </summary>
/// <returns>A string of n tab characters.</returns>
/// <param name="indentLevel">The number of tab characters to insert in the string.</param>
private static string TabIndent(int indentLevel)
{
string indent = "";
// Assemble string
for (int n = 0; n < indentLevel; ++n)
{
indent += (char)TabKey;
}
// Return indention
return indent;
}
/// <summary>
/// Increases the tab indent on the given section of text.
/// </summary>
/// <returns>The text with the tab indent increased by one.</returns>
/// <param name="text">The text to indent.</param>
private string IncreaseTabIndent(string text)
{
string output = "";
// Add first intent
output += (char)TabKey;
for (int n = 0; n < text.Length; ++n)
{
var c = text[n];
bool found = c == Formatter.Newline
|| c == Formatter.LineSeparator
|| c == Formatter.ParagraphSeparator;
// Include char in output
output += c;
// Increase tab level?
if (found)
{
// Yes
output += (char)TabKey;
}
}
// Return results
return output;
}
/// <summary>Decreases the tab indent for the given text</summary>
/// <returns>The text with the tab indent decreased by one.</returns>
/// <param name="text">The text to outdent.</param>
private string DecreaseTabIndent(string text)
{
string output = "";
bool consume = true;
// Add first intent
for (int n = 0; n < text.Length; ++n)
{
var c = text[n];
bool found = (c == Formatter.Newline || c == Formatter.LineSeparator || c == Formatter.ParagraphSeparator);
// Include char in output?
if ((int)c == TabKey && consume)
{
consume = false;
}
else
{
output += c;
}
// Decrease tab level?
if (found)
{
// Yes
consume = true;
}
}
// Return results
return output;
}
#endregion
#region Public Methods
/// <summary>Indents the currently selected text.</summary>
public void IndentText()
{
// Grab range
var range = Formatter.FindLineBoundries(TextStorage.Value, SelectedRange);
var line = TextStorage.Value.Substring((int)range.Location, (int)range.Length);
// Increase tab indent
var output = IncreaseTabIndent(line);
// Reformat section
TextStorage.BeginEditing();
Replace(range, output);
TextStorage.EndEditing();
SelectedRange = new NSRange(range.Location, output.Length);
Formatter.HighlightSyntaxRegion(TextStorage.Value, SelectedRange);
}
/// <summary>Outdents the currently selected text.</summary>
public void OutdentText()
{
// Grab range
NSRange range = Formatter.FindLineBoundries(TextStorage.Value, SelectedRange);
string line = TextStorage.Value.Substring((int)range.Location, (int)range.Length);
// Decrease tab indent
string output = DecreaseTabIndent(line);
// reformat section
TextStorage.BeginEditing();
Replace(range, output);
TextStorage.EndEditing();
SelectedRange = new NSRange(range.Location, output.Length);
Formatter.HighlightSyntaxRegion(TextStorage.Value, SelectedRange);
}
/// <summary>Performs the formatting command on the currectly selected range of text.</summary>
/// <param name="command">
/// The <see cref="AppKit.TextKit.Formatter.LanguageFormatCommand"/> to apply.
/// </param>
public void PerformFormattingCommand(LanguageFormatCommand command)
{
NSRange range = SelectedRange;
// Apply to start of line?
if (command.Postfix == "")
{
// Yes, find start
range = Formatter.FindLineBoundries(TextStorage.Value, SelectedRange);
}
// Yes, get selected text
string line = TextStorage.Value.Substring((int)range.Location, (int)range.Length);
// Apply command
string output = command.Prefix;
output += line;
output += command.Postfix;
TextStorage.BeginEditing();
Replace(range, output);
TextStorage.EndEditing();
Formatter.HighlightSyntaxRegion(TextStorage.Value, range);
}
#endregion
#region Override Methods
/// <summary>
/// Look for special keys being pressed and does specific processing based on the key.
/// </summary>
/// <param name="theEvent">The event.</param>
public override void KeyDown(NSEvent theEvent)
{
NSRange range;
string line;
int indentLevel = 0;
bool consumeKeystroke = false;
// Avoid processing if no Formatter has been attached
if (Formatter == null)
return;
// Trap all errors
try
{
// Get the code of current character
char c = theEvent.Characters[0];
int charCode = (int)theEvent.Characters[0];
// Preprocess based on character code
switch (charCode)
{
case EnterKey:
// Get the tab indent level
range = Formatter.FindLineBoundries(TextStorage.Value, SelectedRange);
line = TextStorage.Value.Substring((int)range.Location, (int)range.Length);
indentLevel = CalculateIndentLevel(line);
break;
case TabKey:
// Is a range selected?
if (SelectedRange.Length > 0)
{
// Increase tab indent over the entire selection
IndentText();
consumeKeystroke = true;
}
break;
case ShiftTabKey:
// Is a range selected?
if (SelectedRange.Length > 0)
{
// Increase tab indent over the entire selection
OutdentText();
consumeKeystroke = true;
}
break;
default:
// Are we completing closures
if (CompleteClosures)
{
if (WrapClosures && SelectedRange.Length > 0)
{
// Yes, see if we are starting a closure
foreach (LanguageClosure closure in Formatter.Language.Closures)
{
// Found?
if (closure.StartingCharacter == c)
{
// Yes, get selected text
nint location = SelectedRange.Location;
line = TextStorage.Value.Substring((int)SelectedRange.Location, (int)SelectedRange.Length);
string output = "";
output += closure.StartingCharacter;
output += line;
output += closure.EndingCharacter;
TextStorage.BeginEditing();
Replace(SelectedRange, output);
TextStorage.EndEditing();
if (SelectAfterWrap)
{
SelectedRange = new NSRange(location, output.Length);
}
consumeKeystroke = true;
Formatter.HighlightSyntaxRegion(TextStorage.Value, SelectedRange);
}
}
}
else
{
// Yes, see if we are in a language defined closure
foreach (LanguageClosure closure in Formatter.Language.Closures)
{
// Found?
if (closure.StartingCharacter == c)
{
// Is this a valid location for a completion?
if (Formatter.TrailingCharacterIsWhitespaceOrTerminator(TextStorage.Value, SelectedRange))
{
// Yes, complete closure
consumeKeystroke = true;
string output = "";
output += closure.StartingCharacter;
output += closure.EndingCharacter;
TextStorage.BeginEditing();
InsertText(new NSString(output));
TextStorage.EndEditing();
SelectedRange = new NSRange(SelectedRange.Location - 1, 0);
}
}
}
}
}
break;
}
// Call base to handle event
if (!consumeKeystroke)
base.KeyDown(theEvent);
// Post process based on character code
switch (charCode)
{
case EnterKey:
// Tab indent the new line to the same level
if (indentLevel > 0)
{
string indent = TabIndent(indentLevel);
TextStorage.BeginEditing();
InsertText(new NSString(indent));
TextStorage.EndEditing();
}
break;
}
}
catch
{
// Call base to process on any error
base.KeyDown(theEvent);
}
this.Formatter.Reformat();
//Console.WriteLine ("Key: {0}", (int)theEvent.Characters[0]);
}
/// <summary>
/// Called when a drag operation is started for this <see cref="SourceTextView"/>.
/// </summary>
/// <returns>The entered.</returns>
/// <param name="sender">Sender.</param>
/// <remarks>
/// See Apple's drag and drop docs for more details (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/DragandDrop.html)
/// </remarks>
//public override NSDragOperation DraggingEntered(NSDraggingInfo sender)
//{
// // When we start dragging, inform the system that we will be handling this as
// // a copy/paste
// return NSDragOperation.Copy;
//}
/// <summary>
/// Process any drag operations initialized by the user to this <see cref="SourceTextView"/>.
/// If one or more files have dragged in, the contents of those files will be copied into the document at the
/// current cursor location.
/// </summary>
/// <returns><c>true</c>, if drag operation was performed, <c>false</c> otherwise.</returns>
/// <param name="sender">The caller that initiated the drag operation.</param>
/// <remarks>
/// See Apple's drag and drop docs for more details (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/DragandDrop.html)
/// </remarks>
//public override bool PerformDragOperation(NSDraggingInfo sender)
//{
// // Attempt to read filenames from pasteboard
// var plist = (NSArray)sender.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType);
// // Was a list of files returned from Finder?
// if (plist != null)
// {
// // Yes, process list
// for (nuint n = 0; n < plist.Count; ++n)
// {
// // Get the current file
// var path = plist.GetItem<NSString>(n);
// var url = NSUrl.FromString(path);
// var contents = File.ReadAllText(path);
// // Insert contents at cursor
// NSRange range = SelectedRange;
// TextStorage.BeginEditing();
// Replace(range, contents);
// TextStorage.EndEditing();
// // Expand range to fully encompass new content and
// // reformat
// range = new NSRange(range.Location, contents.Length);
// range = Formatter.FindLineBoundries(TextStorage.Value, range);
// Formatter.HighlightSyntaxRegion(TextStorage.Value, range);
// }
// // Inform caller of success
// return true;
// }
// else
// {
// // No, allow base class to handle
// return base.PerformDragOperation(sender);
// }
//}
/// <summary>Reads the selection from pasteboard.</summary>
/// <returns>
/// <c>true</c>, if the selection was read from the pasteboard, <c>false</c> otherwise.
/// </returns>
/// <param name="pboard">The pasteboard being read.</param>
/// <remarks>
/// This method is overridden to update the formatting after the user pastes text into the view.
/// </remarks>
public override bool ReadSelectionFromPasteboard(NSPasteboard pboard)
{
// Console.WriteLine ("Read selection from pasteboard");
bool result = base.ReadSelectionFromPasteboard(pboard);
Formatter?.Reformat();
return result;
}
/// <summary>Reads the selection from pasteboard.</summary>
/// <returns>
/// <c>true</c>, if the selection was read from the pasteboard, <c>false</c> otherwise.
/// </returns>
/// <param name="pboard">The pasteboard being read.</param>
/// <param name="type">The type of data being read from the pasteboard.</param>
/// <remarks>
/// This method is overridden to update the formatting after the user pastes text into the view.
/// </remarks>
public override bool ReadSelectionFromPasteboard(NSPasteboard pboard, string type)
{
// Console.WriteLine ("Read selection from pasteboard also");
var result = base.ReadSelectionFromPasteboard(pboard, type);
Formatter?.Reformat();
return result;
}
#endregion
#region Events
/// <summary>Occurs when source cell clicked.</summary>
/// <remarks>NOTE: This replaces the built-in <c>CellClicked</c> event because we
/// are providing a custom <c>NSTextViewDelegate</c> and it is unavialable.</remarks>
public event EventHandler<NSTextViewClickedEventArgs> SourceCellClicked;
/// <summary>Raises the source cell clicked event.</summary>
/// <param name="sender">The controller raising the event.</param>
/// <param name="e">Arguments defining the event.</param>
internal void RaiseSourceCellClicked(object sender, NSTextViewClickedEventArgs e)
{
this.SourceCellClicked?.Invoke(sender, e);
}
/// <summary>Occurs when source cell double clicked.</summary>
/// <remarks>NOTE: This replaces the built-in <c>CellDoubleClicked</c> event because we
/// are providing a custom <c>NSTextViewDelegate</c> and it is unavialable.</remarks>
public event EventHandler<NSTextViewDoubleClickEventArgs> SourceCellDoubleClicked;
/// <summary>Raises the source cell double clicked event.</summary>
/// <param name="sender">The controller raising the event.</param>
/// <param name="e">Arguments defining the event.</param>
internal void RaiseSourceCellDoubleClicked(object sender, NSTextViewDoubleClickEventArgs e)
{
this.SourceCellDoubleClicked?.Invoke(sender, e);
}
/// <summary>Occurs when source cell dragged.</summary>
/// <remarks>
/// NOTE: This replaces the built-in <c>DragCell</c> event because we are providing a custom
/// <c>NSTextViewDelegate</c> and it is unavialable.
/// </remarks>
public event EventHandler<NSTextViewDraggedCellEventArgs> SourceCellDragged;
/// <summary>Raises the source cell dragged event.</summary>
/// <param name="sender">The controller raising the event.</param>
/// <param name="e">Arguments defining the event.</param>
internal void RaiseSourceCellDragged(object sender, NSTextViewDraggedCellEventArgs e)
{
this.SourceCellDragged?.Invoke(sender, e);
}
/// <summary>Occurs when source selection changed.</summary>
/// <remarks>
/// NOTE: This replaces the built-in <c>DidChangeSelection</c> event because we are providing a
/// custom <c>NSTextViewDelegate</c> and it is unavialable.
/// </remarks>
public event EventHandler SourceSelectionChanged;
/// <summary>Raises the source selection changed event.</summary>
/// <param name="sender">The controller raising the event.</param>
/// <param name="e">Arguments defining the event.</param>
internal void RaiseSourceSelectionChanged(object sender, EventArgs e)
{
this.SourceSelectionChanged?.Invoke(sender, e);
}
/// <summary>Occurs when source typing attributes changed.</summary>
/// <remarks>NOTE: This replaces the built-in <c>DidChangeTypingAttributes</c> event because we
/// are providing a custom <c>NSTextViewDelegate</c> and it is unavialable.</remarks>
public event EventHandler SourceTypingAttributesChanged;
/// <summary>Raises the source typing attributes changed event.</summary>
/// <param name="sender">The controller raising the event.</param>
/// <param name="e">Arguments defining the event.</param>
internal void RaiseSourceTypingAttributesChanged(object sender, EventArgs e)
{
this.SourceTypingAttributesChanged?.Invoke(sender, e);
}
#endregion
}

View File

@ -0,0 +1,183 @@
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections.Generic;
namespace Cauldron.Macos.SourceWriter
{
public class SourceTextViewDelegate : NSTextViewDelegate
{
#region Computed Properties
/// <summary>
/// Gets or sets the text editor.
/// </summary>
/// <value>The <see cref="SourceTextView"/> this delegate is attached to.</value>
public SourceTextView TextEditor { get; set; }
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="SourceTextViewDelegate"/> class.
/// </summary>
/// <param name="textEditor">Text editor.</param>
public SourceTextViewDelegate(SourceTextView textEditor)
{
this.TextEditor = textEditor;
}
#endregion
#region Override Methods
/// <summary>
/// Based on the user preferences set on the parent <see cref="SourceTextView"/>, this
/// method returns the available list of partial word completions.
/// </summary>
/// <returns>The list of word completions that will be presented to the user.</returns>
/// <param name="textView">The source <see cref="SourceTextView"/>.</param>
/// <param name="words">
/// A list of default words automatically provided by OS X in the user's language.
/// </param>
/// <param name="charRange">The cursor location where the partial word exists.</param>
/// <param name="index">
/// The word that should be selected when the list is displayed (usually 0 meaning the first
/// item in the list). Pass -1 for no selected items.
/// </param>
public override string[] GetCompletions(NSTextView textView, string[] words, NSRange charRange, ref nint index)
{
List<string> completions = new();
// Is auto complete enabled?
if (TextEditor.AllowAutoComplete)
{
// Use keywords in auto complete?
if (TextEditor.AutoCompleteKeywords)
{
// Yes, grab word being expanded
NSRange range = TextEditor.Formatter.FindWordBoundries(
TextEditor.TextStorage.Value, charRange);
string word = TextEditor.TextStorage.Value.Substring(
(int)range.Location, (int)range.Length);
// Scan the keywords for the a possible match
foreach (string keyword in TextEditor.Formatter.Language.Keywords.Keys)
{
// Found?
if (keyword.Contains(word))
{
completions.Add(keyword);
}
}
}
// Use default words?
if (TextEditor.AutoCompleteDefaultWords)
{
// Only if keywords list is empty?
if (TextEditor.DefaultWordsOnlyIfKeywordsEmpty)
{
if (completions.Count == 0)
{
// No keywords, add defaults
completions.AddRange(words);
}
}
else
{
// No, always include default words
completions.AddRange(words);
}
}
}
// Return results
return completions.ToArray();
}
/// <summary>Called when the cell is clicked.</summary>
/// <param name="textView">The <see cref="AppKit.TextKit.Formatter.SourceTextView"/>.</param>
/// <param name="cell">The cell being acted upon.</param>
/// <param name="cellFrame">The onscreen frame of the cell.</param>
/// <param name="charIndex">The index of the character clicked.</param>
/// <remarks>
/// Because a custom <c>Delegate</c> has been attached to the <c>NSTextView</c>, the normal
/// events will not work so we are using this method to call custom
/// <see cref="SourceTextView"/> events instead.
/// </remarks>
public override void CellClicked(NSTextView textView, NSTextAttachmentCell cell,
CGRect cellFrame, nuint charIndex)
{
// Pass through to Text Editor event
TextEditor.RaiseSourceCellClicked(TextEditor,
new NSTextViewClickedEventArgs(cell, cellFrame, charIndex));
}
/// <summary>Called when the cell is double-clicked.</summary>
/// <param name="textView">The <see cref="SourceTextView"/>.</param>
/// <param name="cell">The cell being acted upon.</param>
/// <param name="cellFrame">The onscreen frame of the cell.</param>
/// <param name="charIndex">The index of the character clicked.</param>
/// <remarks>
/// Because a custom <c>Delegate</c> has been attached to the <c>NSTextView</c>, the normal
/// events will not work so we are using this method to call custom
/// <see cref="SourceTextView"/> events instead.
/// </remarks>
public override void CellDoubleClicked(NSTextView textView, NSTextAttachmentCell cell,
CGRect cellFrame, nuint charIndex)
{
// Pass through to Text Editor event
TextEditor.RaiseSourceCellDoubleClicked(TextEditor,
new NSTextViewDoubleClickEventArgs(cell, cellFrame, charIndex));
}
/// <summary>Called when the cell is dragged.</summary>
/// <param name="view">The <see cref="SourceTextView"/>.</param>
/// <param name="cell">The cell being acted upon.</param>
/// <param name="rect">The onscreen frame of the cell.</param>
/// <param name="theevent">An event defining the drag operation.</param>
/// <remarks>
/// Because a custom <c>Delegate</c> has been attached to the <c>NSTextView</c>, the normal
/// events will not work so we are using this method to call custom
/// <see cref="SourceTextView"/> events instead.
/// </remarks>
//public override void DraggedCell(NSTextView view, NSTextAttachmentCell cell, CGRect rect,
// NSEvent theevent)
//{
// // Pass through to Text Editor event
// TextEditor.RaiseSourceCellDragged(TextEditor, new NSTextViewDraggedCellEventArgs(cell, rect, theevent));
//}
/// <summary>Called when the text selection has changed.</summary>
/// <param name="notification">A notification defining the change.</param>
/// <remarks>
/// Because a custom <c>Delegate</c> has been attached to the <c>NSTextView</c>, the normal
/// events will not work so we are using this method to call custom
/// <see cref="SourceTextView"/> events instead.
/// </remarks>
public override void DidChangeSelection(NSNotification notification)
{
// Pass through to Text Editor event
TextEditor.RaiseSourceSelectionChanged(TextEditor, EventArgs.Empty);
}
/// <summary>Called when the typing attributes has changed.</summary>
/// <param name="notification">A notification defining the change.</param>
/// <remarks>
/// Because a custom <c>Delegate</c> has been attached to the <c>NSTextView</c>, the normal
/// events will not work so we are using this method to call custom
/// <see cref="SourceTextView"/> events instead.
/// </remarks>
public override void DidChangeTypingAttributes(NSNotification notification)
{
// Pass through to Text Editor event
TextEditor.RaiseSourceTypingAttributesChanged(TextEditor, EventArgs.Empty);
}
#endregion
}
}