// // // // // $Revision$ // using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Text; using System.IO; using System.Text; using System.Windows.Forms; using ICSharpCode.TextEditor.Actions; using ICSharpCode.TextEditor.Document; namespace ICSharpCode.TextEditor { /// /// This class is used for a basic text area control /// [ToolboxItem(false)] public abstract class TextEditorControlBase : UserControl { string currentFileName = null; int updateLevel = 0; IDocument document; /// /// This hashtable contains all editor keys, where /// the key is the key combination and the value the /// action. /// protected Dictionary editactions = new Dictionary(); [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ITextEditorProperties TextEditorProperties { get { return document.TextEditorProperties; } set { document.TextEditorProperties = value; OptionsChanged(); } } Encoding encoding; /// /// Current file's character encoding /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Encoding Encoding { get { if (encoding == null) return TextEditorProperties.Encoding; return encoding; } set { encoding = value; } } /// /// The current file name /// [Browsable(false)] [ReadOnly(true)] public string FileName { get { return currentFileName; } set { if (currentFileName != value) { currentFileName = value; OnFileNameChanged(EventArgs.Empty); } } } /// /// The current document /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IDocument Document { get { return document; } set { if (value == null) throw new ArgumentNullException("value"); if (document != null) { document.DocumentChanged -= OnDocumentChanged; } document = value; document.UndoStack.TextEditorControl = this; document.DocumentChanged += OnDocumentChanged; } } void OnDocumentChanged(object sender, EventArgs e) { OnTextChanged(e); } [EditorBrowsable(EditorBrowsableState.Always), Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))] public override string Text { get { return Document.TextContent; } set { Document.TextContent = value; } } [EditorBrowsable(EditorBrowsableState.Always), Browsable(true)] public new event EventHandler TextChanged { add { base.TextChanged += value; } remove { base.TextChanged -= value; } } static Font ParseFont(string font) { string[] descr = font.Split(new char[]{',', '='}); return new Font(descr[1], Single.Parse(descr[3])); } /// /// If set to true the contents can't be altered. /// [Browsable(false)] public bool IsReadOnly { get { return Document.ReadOnly; } set { Document.ReadOnly = value; } } /// /// true, if the textarea is updating it's status, while /// it updates it status no redraw operation occurs. /// [Browsable(false)] public bool IsInUpdate { get { return updateLevel > 0; } } /// /// supposedly this is the way to do it according to .NET docs, /// as opposed to setting the size in the constructor /// protected override Size DefaultSize { get { return new Size(100, 100); } } #region Document Properties /// /// If true spaces are shown in the textarea /// [Category("Appearance")] [DefaultValue(false)] [Description("If true spaces are shown in the textarea")] public bool ShowSpaces { get { return document.TextEditorProperties.ShowSpaces; } set { document.TextEditorProperties.ShowSpaces = value; OptionsChanged(); } } /// /// Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing). /// [Category("Appearance")] [DefaultValue(TextRenderingHint.SystemDefault)] [Description("Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).")] public TextRenderingHint TextRenderingHint { get { return document.TextEditorProperties.TextRenderingHint; } set { document.TextEditorProperties.TextRenderingHint = value; OptionsChanged(); } } /// /// If true tabs are shown in the textarea /// [Category("Appearance")] [DefaultValue(false)] [Description("If true tabs are shown in the textarea")] public bool ShowTabs { get { return document.TextEditorProperties.ShowTabs; } set { document.TextEditorProperties.ShowTabs = value; OptionsChanged(); } } /// /// If true EOL markers are shown in the textarea /// [Category("Appearance")] [DefaultValue(false)] [Description("If true EOL markers are shown in the textarea")] public bool ShowEOLMarkers { get { return document.TextEditorProperties.ShowEOLMarker; } set { document.TextEditorProperties.ShowEOLMarker = value; OptionsChanged(); } } /// /// If true the horizontal ruler is shown in the textarea /// [Category("Appearance")] [DefaultValue(false)] [Description("If true the horizontal ruler is shown in the textarea")] public bool ShowHRuler { get { return document.TextEditorProperties.ShowHorizontalRuler; } set { document.TextEditorProperties.ShowHorizontalRuler = value; OptionsChanged(); } } /// /// If true the vertical ruler is shown in the textarea /// [Category("Appearance")] [DefaultValue(true)] [Description("If true the vertical ruler is shown in the textarea")] public bool ShowVRuler { get { return document.TextEditorProperties.ShowVerticalRuler; } set { document.TextEditorProperties.ShowVerticalRuler = value; OptionsChanged(); } } /// /// The row in which the vertical ruler is displayed /// [Category("Appearance")] [DefaultValue(80)] [Description("The row in which the vertical ruler is displayed")] public int VRulerRow { get { return document.TextEditorProperties.VerticalRulerRow; } set { document.TextEditorProperties.VerticalRulerRow = value; OptionsChanged(); } } /// /// If true line numbers are shown in the textarea /// [Category("Appearance")] [DefaultValue(true)] [Description("If true line numbers are shown in the textarea")] public bool ShowLineNumbers { get { return document.TextEditorProperties.ShowLineNumbers; } set { document.TextEditorProperties.ShowLineNumbers = value; OptionsChanged(); } } /// /// If true invalid lines are marked in the textarea /// [Category("Appearance")] [DefaultValue(false)] [Description("If true invalid lines are marked in the textarea")] public bool ShowInvalidLines { get { return document.TextEditorProperties.ShowInvalidLines; } set { document.TextEditorProperties.ShowInvalidLines = value; OptionsChanged(); } } /// /// If true folding is enabled in the textarea /// [Category("Appearance")] [DefaultValue(true)] [Description("If true folding is enabled in the textarea")] public bool EnableFolding { get { return document.TextEditorProperties.EnableFolding; } set { document.TextEditorProperties.EnableFolding = value; OptionsChanged(); } } [Category("Appearance")] [DefaultValue(true)] [Description("If true matching brackets are highlighted")] public bool ShowMatchingBracket { get { return document.TextEditorProperties.ShowMatchingBracket; } set { document.TextEditorProperties.ShowMatchingBracket = value; OptionsChanged(); } } [Category("Appearance")] [DefaultValue(false)] [Description("If true the icon bar is displayed")] public bool IsIconBarVisible { get { return document.TextEditorProperties.IsIconBarVisible; } set { document.TextEditorProperties.IsIconBarVisible = value; OptionsChanged(); } } /// /// The width in spaces of a tab character /// [Category("Appearance")] [DefaultValue(4)] [Description("The width in spaces of a tab character")] public int TabIndent { get { return document.TextEditorProperties.TabIndent; } set { document.TextEditorProperties.TabIndent = value; OptionsChanged(); } } /// /// The line viewer style /// [Category("Appearance")] [DefaultValue(LineViewerStyle.None)] [Description("The line viewer style")] public LineViewerStyle LineViewerStyle { get { return document.TextEditorProperties.LineViewerStyle; } set { document.TextEditorProperties.LineViewerStyle = value; OptionsChanged(); } } /// /// The indent style /// [Category("Behavior")] [DefaultValue(IndentStyle.Smart)] [Description("The indent style")] public IndentStyle IndentStyle { get { return document.TextEditorProperties.IndentStyle; } set { document.TextEditorProperties.IndentStyle = value; OptionsChanged(); } } /// /// if true spaces are converted to tabs /// [Category("Behavior")] [DefaultValue(false)] [Description("Converts tabs to spaces while typing")] public bool ConvertTabsToSpaces { get { return document.TextEditorProperties.ConvertTabsToSpaces; } set { document.TextEditorProperties.ConvertTabsToSpaces = value; OptionsChanged(); } } /// /// if true spaces are converted to tabs /// [Category("Behavior")] [DefaultValue(false)] [Description("Hide the mouse cursor while typing")] public bool HideMouseCursor { get { return document.TextEditorProperties.HideMouseCursor; } set { document.TextEditorProperties.HideMouseCursor = value; OptionsChanged(); } } /// /// if true spaces are converted to tabs /// [Category("Behavior")] [DefaultValue(false)] [Description("Allows the caret to be placed beyond the end of line")] public bool AllowCaretBeyondEOL { get { return document.TextEditorProperties.AllowCaretBeyondEOL; } set { document.TextEditorProperties.AllowCaretBeyondEOL = value; OptionsChanged(); } } /// /// if true spaces are converted to tabs /// [Category("Behavior")] [DefaultValue(BracketMatchingStyle.After)] [Description("Specifies if the bracket matching should match the bracket before or after the caret.")] public BracketMatchingStyle BracketMatchingStyle { get { return document.TextEditorProperties.BracketMatchingStyle; } set { document.TextEditorProperties.BracketMatchingStyle = value; OptionsChanged(); } } /// /// The base font of the text area. No bold or italic fonts /// can be used because bold/italic is reserved for highlighting /// purposes. /// [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")] public override Font Font { get { return document.TextEditorProperties.Font; } set { document.TextEditorProperties.Font = value; OptionsChanged(); } } #endregion public abstract TextAreaControl ActiveTextAreaControl { get; } protected TextEditorControlBase() { GenerateDefaultActions(); HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(OnReloadHighlighting); } protected virtual void OnReloadHighlighting(object sender, EventArgs e) { if (Document.HighlightingStrategy != null) { try { Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(Document.HighlightingStrategy.Name); } catch (HighlightingDefinitionInvalidException ex) { MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } OptionsChanged(); } } public bool IsEditAction(Keys keyData) { return editactions.ContainsKey(keyData); } internal IEditAction GetEditAction(Keys keyData) { if (!IsEditAction(keyData)) { return null; } return (IEditAction)editactions[keyData]; } void GenerateDefaultActions() { editactions[Keys.Left] = new CaretLeft(); editactions[Keys.Left | Keys.Shift] = new ShiftCaretLeft(); editactions[Keys.Left | Keys.Control] = new WordLeft(); editactions[Keys.Left | Keys.Control | Keys.Shift] = new ShiftWordLeft(); editactions[Keys.Right] = new CaretRight(); editactions[Keys.Right | Keys.Shift] = new ShiftCaretRight(); editactions[Keys.Right | Keys.Control] = new WordRight(); editactions[Keys.Right | Keys.Control | Keys.Shift] = new ShiftWordRight(); editactions[Keys.Up] = new CaretUp(); editactions[Keys.Up | Keys.Shift] = new ShiftCaretUp(); editactions[Keys.Up | Keys.Control] = new ScrollLineUp(); editactions[Keys.Down] = new CaretDown(); editactions[Keys.Down | Keys.Shift] = new ShiftCaretDown(); editactions[Keys.Down | Keys.Control] = new ScrollLineDown(); editactions[Keys.Insert] = new ToggleEditMode(); editactions[Keys.Insert | Keys.Control] = new Copy(); editactions[Keys.Insert | Keys.Shift] = new Paste(); editactions[Keys.Delete] = new Delete(); editactions[Keys.Delete | Keys.Shift] = new Cut(); editactions[Keys.Home] = new Home(); editactions[Keys.Home | Keys.Shift] = new ShiftHome(); editactions[Keys.Home | Keys.Control] = new MoveToStart(); editactions[Keys.Home | Keys.Control | Keys.Shift] = new ShiftMoveToStart(); editactions[Keys.End] = new End(); editactions[Keys.End | Keys.Shift] = new ShiftEnd(); editactions[Keys.End | Keys.Control] = new MoveToEnd(); editactions[Keys.End | Keys.Control | Keys.Shift] = new ShiftMoveToEnd(); editactions[Keys.PageUp] = new MovePageUp(); editactions[Keys.PageUp | Keys.Shift] = new ShiftMovePageUp(); editactions[Keys.PageDown] = new MovePageDown(); editactions[Keys.PageDown | Keys.Shift] = new ShiftMovePageDown(); editactions[Keys.Return] = new Return(); editactions[Keys.Tab] = new Tab(); editactions[Keys.Tab | Keys.Shift] = new ShiftTab(); editactions[Keys.Back] = new Backspace(); editactions[Keys.Back | Keys.Shift] = new Backspace(); editactions[Keys.X | Keys.Control] = new Cut(); editactions[Keys.C | Keys.Control] = new Copy(); editactions[Keys.V | Keys.Control] = new Paste(); editactions[Keys.A | Keys.Control] = new SelectWholeDocument(); editactions[Keys.Escape] = new ClearAllSelections(); editactions[Keys.Divide | Keys.Control] = new ToggleComment(); editactions[Keys.OemQuestion | Keys.Control] = new ToggleComment(); editactions[Keys.Back | Keys.Alt] = new Actions.Undo(); editactions[Keys.Z | Keys.Control] = new Actions.Undo(); editactions[Keys.Y | Keys.Control] = new Redo(); editactions[Keys.Delete | Keys.Control] = new DeleteWord(); editactions[Keys.Back | Keys.Control] = new WordBackspace(); editactions[Keys.D | Keys.Control] = new DeleteLine(); editactions[Keys.D | Keys.Shift | Keys.Control] = new DeleteToLineEnd(); editactions[Keys.B | Keys.Control] = new GotoMatchingBrace(); } /// /// Call this method before a long update operation this /// 'locks' the text area so that no screen update occurs. /// public virtual void BeginUpdate() { ++updateLevel; } /// /// Call this method to 'unlock' the text area. After this call /// screen update can occur. But no automatical refresh occurs you /// have to commit the updates in the queue. /// public virtual void EndUpdate() { Debug.Assert(updateLevel > 0); updateLevel = Math.Max(0, updateLevel - 1); } public void LoadFile(string fileName) { LoadFile(fileName, true, true); } /// /// Loads a file given by fileName /// /// The name of the file to open /// Automatically load the highlighting for the file /// Automatically detect file encoding and set Encoding property to the detected encoding. public void LoadFile(string fileName, bool autoLoadHighlighting, bool autodetectEncoding) { using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { LoadFile(fileName, fs, autoLoadHighlighting, autodetectEncoding); } } /// /// Loads a file from the specified stream. /// /// The name of the file to open. Used to find the correct highlighting strategy /// if autoLoadHighlighting is active, and sets the filename property to this value. /// The stream to actually load the file content from. /// Automatically load the highlighting for the file /// Automatically detect file encoding and set Encoding property to the detected encoding. public void LoadFile(string fileName, Stream stream, bool autoLoadHighlighting, bool autodetectEncoding) { if (stream == null) throw new ArgumentNullException("stream"); BeginUpdate(); document.TextContent = String.Empty; document.UndoStack.ClearAll(); document.BookmarkManager.Clear(); if (autoLoadHighlighting) { try { document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategyForFile(fileName); } catch (HighlightingDefinitionInvalidException ex) { MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } if (autodetectEncoding) { Encoding encoding = this.Encoding; Document.TextContent = Util.FileReader.ReadFileContent(stream, ref encoding); this.Encoding = encoding; } else { using (StreamReader reader = new StreamReader(fileName, this.Encoding)) { Document.TextContent = reader.ReadToEnd(); } } this.FileName = fileName; Document.UpdateQueue.Clear(); EndUpdate(); OptionsChanged(); Refresh(); } /// /// Gets if the document can be saved with the current encoding without losing data. /// public bool CanSaveWithCurrentEncoding() { if (encoding == null || Util.FileReader.IsUnicode(encoding)) return true; // not a unicode codepage string text = document.TextContent; return encoding.GetString(encoding.GetBytes(text)) == text; } /// /// Saves the text editor content into the file. /// public void SaveFile(string fileName) { using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { SaveFile(fs); } this.FileName = fileName; } /// /// Saves the text editor content into the specified stream. /// Does not close the stream. /// public void SaveFile(Stream stream) { StreamWriter streamWriter = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8); // save line per line to apply the LineTerminator to all lines // (otherwise we might save files with mixed-up line endings) foreach (LineSegment line in Document.LineSegmentCollection) { streamWriter.Write(Document.GetText(line.Offset, line.Length)); if (line.DelimiterLength > 0) { char charAfterLine = Document.GetCharAt(line.Offset + line.Length); if (charAfterLine != '\n' && charAfterLine != '\r') throw new InvalidOperationException("The document cannot be saved because it is corrupted."); // only save line terminator if the line has one streamWriter.Write(document.TextEditorProperties.LineTerminator); } } streamWriter.Flush(); } public abstract void OptionsChanged(); // Localization ISSUES // used in insight window public virtual string GetRangeDescription(int selectedItem, int itemCount) { StringBuilder sb=new StringBuilder(selectedItem.ToString()); sb.Append(" from "); sb.Append(itemCount.ToString()); return sb.ToString(); } /// /// Overwritten refresh method that does nothing if the control is in /// an update cycle. /// public override void Refresh() { if (IsInUpdate) { return; } base.Refresh(); } protected override void Dispose(bool disposing) { if (disposing) { HighlightingManager.Manager.ReloadSyntaxHighlighting -= new EventHandler(OnReloadHighlighting); document.HighlightingStrategy = null; document.UndoStack.TextEditorControl = null; } base.Dispose(disposing); } protected virtual void OnFileNameChanged(EventArgs e) { if (FileNameChanged != null) { FileNameChanged(this, e); } } public event EventHandler FileNameChanged; } }