Click here to Skip to main content
15,891,629 members
Articles / Desktop Programming / Windows Forms

Using ICSharpCode.TextEditor

Rate me:
Please Sign up or sign in to vote.
4.82/5 (60 votes)
12 Nov 2008MIT8 min read 392.5K   23.4K   194  
Use TextEditorControl to put a syntax-highlighting editor in your application.
// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision: 3078 $</version>
// </file>

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;

namespace ICSharpCode.TextEditor
{
	/// <summary>
	/// This class paints the textarea.
	/// </summary>
	[ToolboxItem(false)]
	public class TextAreaControl : Panel
	{
		TextEditorControl motherTextEditorControl;
		
		HRuler     hRuler     = null;
		
		VScrollBar vScrollBar = new VScrollBar();
		HScrollBar hScrollBar = new HScrollBar();
		TextArea   textArea;
		bool       doHandleMousewheel = true;
		bool       disposed;
		
		public TextArea TextArea {
			get {
				return textArea;
			}
		}
		
		public SelectionManager SelectionManager {
			get {
				return textArea.SelectionManager;
			}
		}
		
		public Caret Caret {
			get {
				return textArea.Caret;
			}
		}
		
		[Browsable(false)]
		public IDocument Document {
			get {
				if (motherTextEditorControl != null)
					return motherTextEditorControl.Document;
				return null;
			}
		}
		
		public ITextEditorProperties TextEditorProperties {
			get {
				if (motherTextEditorControl != null)
					return motherTextEditorControl.TextEditorProperties;
				return null;
			}
		}
		
		public VScrollBar VScrollBar {
			get {
				return vScrollBar;
			}
		}
		
		public HScrollBar HScrollBar {
			get {
				return hScrollBar;
			}
		}
		
		public bool DoHandleMousewheel {
			get {
				return doHandleMousewheel;
			}
			set {
				doHandleMousewheel = value;
			}
		}
		
		public TextAreaControl(TextEditorControl motherTextEditorControl)
		{
			this.motherTextEditorControl = motherTextEditorControl;
			
			this.textArea                = new TextArea(motherTextEditorControl, this);
			Controls.Add(textArea);
			
			vScrollBar.ValueChanged += new EventHandler(VScrollBarValueChanged);
			Controls.Add(this.vScrollBar);
			
			hScrollBar.ValueChanged += new EventHandler(HScrollBarValueChanged);
			Controls.Add(this.hScrollBar);
			ResizeRedraw = true;
			
			Document.TextContentChanged += DocumentTextContentChanged;
			Document.DocumentChanged += AdjustScrollBarsOnDocumentChange;
			Document.UpdateCommited  += DocumentUpdateCommitted;
		}
		
		protected override void Dispose(bool disposing)
		{
			if (disposing) {
				if (!disposed) {
					disposed = true;
					Document.TextContentChanged -= DocumentTextContentChanged;
					Document.DocumentChanged -= AdjustScrollBarsOnDocumentChange;
					Document.UpdateCommited  -= DocumentUpdateCommitted;
					motherTextEditorControl = null;
					if (vScrollBar != null) {
						vScrollBar.Dispose();
						vScrollBar = null;
					}
					if (hScrollBar != null) {
						hScrollBar.Dispose();
						hScrollBar = null;
					}
					if (hRuler != null) {
						hRuler.Dispose();
						hRuler = null;
					}
				}
			}
			base.Dispose(disposing);
		}
		
		void DocumentTextContentChanged(object sender, EventArgs e)
		{
			// after the text content is changed abruptly, we need to validate the
			// caret position - otherwise the caret position is invalid for a short amount
			// of time, which can break client code that expects that the caret position is always valid
			Caret.ValidateCaretPos();
		}
		
		protected override void OnResize(System.EventArgs e)
		{
			base.OnResize(e);
			ResizeTextArea();
		}
		
		public void ResizeTextArea()
		{
			int y = 0;
			int h = 0;
			if (hRuler != null) {
				hRuler.Bounds = new Rectangle(0,
				                              0,
				                              Width - SystemInformation.HorizontalScrollBarArrowWidth,
				                              textArea.TextView.FontHeight);
				
				y = hRuler.Bounds.Bottom;
				h = hRuler.Bounds.Height;
			}
			
			textArea.Bounds = new Rectangle(0, y,
			                                Width - SystemInformation.HorizontalScrollBarArrowWidth,
			                                Height - SystemInformation.VerticalScrollBarArrowHeight - h);
			SetScrollBarBounds();
		}
		
		public void SetScrollBarBounds()
		{
			vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right, 0, SystemInformation.HorizontalScrollBarArrowWidth, Height - SystemInformation.VerticalScrollBarArrowHeight);
			hScrollBar.Bounds = new Rectangle(0,
			                                  textArea.Bounds.Bottom,
			                                  Width - SystemInformation.HorizontalScrollBarArrowWidth,
			                                  SystemInformation.VerticalScrollBarArrowHeight);
		}
		
		bool adjustScrollBarsOnNextUpdate;
		Point scrollToPosOnNextUpdate;
		
		void AdjustScrollBarsOnDocumentChange(object sender, DocumentEventArgs e)
		{
			if (motherTextEditorControl.IsInUpdate == false) {
				AdjustScrollBarsClearCache();
				AdjustScrollBars();
			} else {
				adjustScrollBarsOnNextUpdate = true;
			}
		}
		
		void DocumentUpdateCommitted(object sender, EventArgs e)
		{
			if (motherTextEditorControl.IsInUpdate == false) {
				Caret.ValidateCaretPos();
				
				// AdjustScrollBarsOnCommittedUpdate
				if (!scrollToPosOnNextUpdate.IsEmpty) {
					ScrollTo(scrollToPosOnNextUpdate.Y, scrollToPosOnNextUpdate.X);
				}
				if (adjustScrollBarsOnNextUpdate) {
					AdjustScrollBarsClearCache();
					AdjustScrollBars();
				}
			}
		}
		
		int[] lineLengthCache;
		const int LineLengthCacheAdditionalSize = 100;
		
		void AdjustScrollBarsClearCache()
		{
			if (lineLengthCache != null) {
				if (lineLengthCache.Length < this.Document.TotalNumberOfLines + 2 * LineLengthCacheAdditionalSize) {
					lineLengthCache = null;
				} else {
					Array.Clear(lineLengthCache, 0, lineLengthCache.Length);
				}
			}
		}
		
		public void AdjustScrollBars()
		{
			adjustScrollBarsOnNextUpdate = false;
			vScrollBar.Minimum = 0;
			// number of visible lines in document (folding!)
			vScrollBar.Maximum = textArea.MaxVScrollValue;
			int max = 0;
			
			int firstLine = textArea.TextView.FirstVisibleLine;
			int lastLine = this.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + textArea.TextView.VisibleLineCount);
			if (lastLine >= this.Document.TotalNumberOfLines)
				lastLine = this.Document.TotalNumberOfLines - 1;
			
			if (lineLengthCache == null || lineLengthCache.Length <= lastLine) {
				lineLengthCache = new int[lastLine + LineLengthCacheAdditionalSize];
			}
			
			for (int lineNumber = firstLine; lineNumber <= lastLine; lineNumber++) {
				LineSegment lineSegment = this.Document.GetLineSegment(lineNumber);
				if (Document.FoldingManager.IsLineVisible(lineNumber)) {
					if (lineLengthCache[lineNumber] > 0) {
						max = Math.Max(max, lineLengthCache[lineNumber]);
					} else {
						int visualLength = textArea.TextView.GetVisualColumnFast(lineSegment, lineSegment.Length);
						lineLengthCache[lineNumber] = Math.Max(1, visualLength);
						max = Math.Max(max, visualLength);
					}
				}
			}
			hScrollBar.Minimum = 0;
			hScrollBar.Maximum = (Math.Max(max + 20, textArea.TextView.VisibleColumnCount - 1));
			
			vScrollBar.LargeChange = Math.Max(0, textArea.TextView.DrawingPosition.Height);
			vScrollBar.SmallChange = Math.Max(0, textArea.TextView.FontHeight);
			
			hScrollBar.LargeChange = Math.Max(0, textArea.TextView.VisibleColumnCount - 1);
			hScrollBar.SmallChange = Math.Max(0, (int)textArea.TextView.SpaceWidth);
		}
		
		public void OptionsChanged()
		{
			textArea.OptionsChanged();
			
			if (textArea.TextEditorProperties.ShowHorizontalRuler) {
				if (hRuler == null) {
					hRuler = new HRuler(textArea);
					Controls.Add(hRuler);
					ResizeTextArea();
				} else {
					hRuler.Invalidate();
				}
			} else {
				if (hRuler != null) {
					Controls.Remove(hRuler);
					hRuler.Dispose();
					hRuler = null;
					ResizeTextArea();
				}
			}
			
			AdjustScrollBars();
		}
		
		void VScrollBarValueChanged(object sender, EventArgs e)
		{
			textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value);
			textArea.Invalidate();
			AdjustScrollBars();
		}
		
		void HScrollBarValueChanged(object sender, EventArgs e)
		{
			textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y);
			textArea.Invalidate();
		}
		
		Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler();
		
		public void HandleMouseWheel(MouseEventArgs e)
		{
			int scrollDistance = mouseWheelHandler.GetScrollAmount(e);
			if (scrollDistance == 0)
				return;
			if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom) {
				if (scrollDistance > 0) {
					motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
					                                        motherTextEditorControl.Font.Size + 1);
				} else {
					motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name,
					                                        Math.Max(6, motherTextEditorControl.Font.Size - 1));
				}
			} else {
				if (TextEditorProperties.MouseWheelScrollDown)
					scrollDistance = -scrollDistance;
				int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance;
				vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue));
			}
		}
		
		protected override void OnMouseWheel(MouseEventArgs e)
		{
			base.OnMouseWheel(e);
			if (DoHandleMousewheel) {
				HandleMouseWheel(e);
			}
		}
		
		public void ScrollToCaret()
		{
			ScrollTo(textArea.Caret.Line, textArea.Caret.Column);
		}
		
		public void ScrollTo(int line, int column)
		{
			if (motherTextEditorControl.IsInUpdate) {
				scrollToPosOnNextUpdate = new Point(column, line);
				return;
			} else {
				scrollToPosOnNextUpdate = Point.Empty;
			}
			
			ScrollTo(line);
			
			int curCharMin  = (int)(this.hScrollBar.Value - this.hScrollBar.Minimum);
			int curCharMax  = curCharMin + textArea.TextView.VisibleColumnCount;
			
			int pos = textArea.TextView.GetVisualColumn(line, column);
			
			if (textArea.TextView.VisibleColumnCount < 0) {
				hScrollBar.Value = 0;
			} else {
				if (pos < curCharMin) {
					hScrollBar.Value = (int)(Math.Max(0, pos - scrollMarginHeight));
				} else {
					if (pos > curCharMax) {
						hScrollBar.Value = (int)Math.Max(0, Math.Min(hScrollBar.Maximum, (pos - textArea.TextView.VisibleColumnCount + scrollMarginHeight)));
					}
				}
			}
		}
		
		int scrollMarginHeight  = 3;
		
		/// <summary>
		/// Ensure that <paramref name="line"/> is visible.
		/// </summary>
		public void ScrollTo(int line)
		{
			line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
			line = Document.GetVisibleLine(line);
			int curLineMin = textArea.TextView.FirstPhysicalLine;
			if (textArea.TextView.LineHeightRemainder > 0) {
				curLineMin ++;
			}
			
			if (line - scrollMarginHeight + 3 < curLineMin) {
				this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
				VScrollBarValueChanged(this, EventArgs.Empty);
			} else {
				int curLineMax = curLineMin + this.textArea.TextView.VisibleLineCount;
				if (line + scrollMarginHeight - 1 > curLineMax) {
					if (this.textArea.TextView.VisibleLineCount == 1) {
						this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight - 1) * textArea.TextView.FontHeight)) ;
					} else {
						this.vScrollBar.Value = Math.Min(this.vScrollBar.Maximum,
						                                 (line - this.textArea.TextView.VisibleLineCount + scrollMarginHeight - 1)* textArea.TextView.FontHeight) ;
					}
					VScrollBarValueChanged(this, EventArgs.Empty);
				}
			}
		}
		
		/// <summary>
		/// Scroll so that the specified line is centered.
		/// </summary>
		/// <param name="line">Line to center view on</param>
		/// <param name="treshold">If this action would cause scrolling by less than or equal to
		/// <paramref name="treshold"/> lines in any direction, don't scroll.
		/// Use -1 to always center the view.</param>
		public void CenterViewOn(int line, int treshold)
		{
			line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line));
			// convert line to visible line:
			line = Document.GetVisibleLine(line);
			// subtract half the visible line count
			line -= textArea.TextView.VisibleLineCount / 2;
			
			int curLineMin = textArea.TextView.FirstPhysicalLine;
			if (textArea.TextView.LineHeightRemainder > 0) {
				curLineMin ++;
			}
			if (Math.Abs(curLineMin - line) > treshold) {
				// scroll:
				this.vScrollBar.Value =  Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ;
				VScrollBarValueChanged(this, EventArgs.Empty);
			}
		}
		
		public void JumpTo(int line)
		{
			line = Math.Min(line, Document.TotalNumberOfLines - 1);
			string text = Document.GetText(Document.GetLineSegment(line));
			JumpTo(line, text.Length - text.TrimStart().Length);
		}
		
		public void JumpTo(int line, int column)
		{
			textArea.Focus();
			textArea.SelectionManager.ClearSelection();
			textArea.Caret.Position = new TextLocation(column, line);
			textArea.SetDesiredColumn();
			ScrollToCaret();
		}
		
		public event MouseEventHandler ShowContextMenu;
		
		protected override void WndProc(ref Message m)
		{
			if (m.Msg == 0x007B) { // handle WM_CONTEXTMENU
				if (ShowContextMenu != null) {
					long lParam = m.LParam.ToInt64();
					int x = unchecked((short)(lParam & 0xffff));
					int y = unchecked((short)((lParam & 0xffff0000) >> 16));
					if (x == -1 && y == -1) {
						Point pos = Caret.ScreenPosition;
						ShowContextMenu(this, new MouseEventArgs(MouseButtons.None, 0, pos.X, pos.Y + textArea.TextView.FontHeight, 0));
					} else {
						Point pos = PointToClient(new Point(x, y));
						ShowContextMenu(this, new MouseEventArgs(MouseButtons.Right, 1, pos.X, pos.Y, 0));
					}
				}
			}
			base.WndProc(ref m);
		}
		
		protected override void OnEnter(EventArgs e)
		{
			// SD2-1072 - Make sure the caret line is valid if anyone
			// has handlers for the Enter event.
			Caret.ValidateCaretPos();
			base.OnEnter(e);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions