Add line numbers
This commit is contained in:
		
							parent
							
								
									7610ffaf38
								
							
						
					
					
						commit
						ab3fb386d9
					
				| 
						 | 
					@ -684,7 +684,7 @@
 | 
				
			||||||
                            </subviews>
 | 
					                            </subviews>
 | 
				
			||||||
                        </clipView>
 | 
					                        </clipView>
 | 
				
			||||||
                        <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="vhq-Zf-7BV">
 | 
					                        <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="vhq-Zf-7BV">
 | 
				
			||||||
                            <rect key="frame" x="-100" y="-100" width="240" height="16"/>
 | 
					                            <rect key="frame" x="-100" y="-100" width="700" height="16"/>
 | 
				
			||||||
                            <autoresizingMask key="autoresizingMask"/>
 | 
					                            <autoresizingMask key="autoresizingMask"/>
 | 
				
			||||||
                        </scroller>
 | 
					                        </scroller>
 | 
				
			||||||
                        <scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="S8X-st-Qy9">
 | 
					                        <scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="S8X-st-Qy9">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										126
									
								
								Cauldron.Macos/SourceWriter/LineNumberRuler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								Cauldron.Macos/SourceWriter/LineNumberRuler.cs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,126 @@
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using AppKit;
 | 
				
			||||||
 | 
					using CoreGraphics;
 | 
				
			||||||
 | 
					using Foundation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Cauldron.Macos.SourceWriter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LineNumberRuler : NSRulerView
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						private NSColor _foregroundColor = NSColor.DisabledControlText;
 | 
				
			||||||
 | 
						private NSColor _backgroundColor = NSColor.TextBackground;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public float GutterWidth { get; private set; } = 40f;
 | 
				
			||||||
 | 
						public NSColor ForegroundColor
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							get => this._foregroundColor;
 | 
				
			||||||
 | 
							set
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								this._foregroundColor = value;
 | 
				
			||||||
 | 
								this.NeedsDisplay = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						public NSColor BackgroundColor
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							get => this._backgroundColor;
 | 
				
			||||||
 | 
							set
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								this._backgroundColor = value;
 | 
				
			||||||
 | 
								this.NeedsDisplay = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public LineNumberRuler(NSTextView textView)
 | 
				
			||||||
 | 
							: base(textView.EnclosingScrollView, NSRulerOrientation.Vertical)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							this.ClientView = textView;
 | 
				
			||||||
 | 
							this.RuleThickness = this.GutterWidth;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public LineNumberRuler(NSTextView textView, NSColor foregroundColor, NSColor backgroundColor)
 | 
				
			||||||
 | 
							: base(textView.EnclosingScrollView, NSRulerOrientation.Vertical)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							this.ClientView = textView;
 | 
				
			||||||
 | 
							this.ForegroundColor = foregroundColor;
 | 
				
			||||||
 | 
							this.BackgroundColor = backgroundColor;
 | 
				
			||||||
 | 
							this.RuleThickness = this.GutterWidth;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public override void DrawHashMarksAndLabels(CGRect rect)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							this.BackgroundColor.Set();
 | 
				
			||||||
 | 
							NSGraphicsContext.CurrentContext.CGContext.FillRect(rect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (this.ClientView is not NSTextView textView)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NSLayoutManager layoutManager = textView.LayoutManager;
 | 
				
			||||||
 | 
							NSTextContainer textContainer = textView.TextContainer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (layoutManager is null || textContainer is null)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NSString content = new NSString(textView.Value);
 | 
				
			||||||
 | 
							NSRange visibleGlyphsRange = layoutManager
 | 
				
			||||||
 | 
								.GetGlyphRangeForBoundingRect(textView.VisibleRect(), textContainer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							int lineNumber = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NSRegularExpression newlineRegex = new NSRegularExpression(new NSString("\n"),
 | 
				
			||||||
 | 
								new NSRegularExpressionOptions(), out NSError error);
 | 
				
			||||||
 | 
							if (error is not null)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lineNumber += (int)newlineRegex.GetNumberOfMatches(content, new NSMatchingOptions(),
 | 
				
			||||||
 | 
								new NSRange(0, visibleGlyphsRange.Location));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							nint firstGlyphOfLineIndex = visibleGlyphsRange.Location;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							while (firstGlyphOfLineIndex < visibleGlyphsRange.Location + visibleGlyphsRange.Length)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								NSRange charRangeOfLine = content.LineRangeForRange(
 | 
				
			||||||
 | 
									new NSRange((nint)layoutManager.GetCharacterIndex((nuint)firstGlyphOfLineIndex), 0));
 | 
				
			||||||
 | 
								NSRange glyphRangeOfLine = layoutManager.GetGlyphRange(charRangeOfLine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								nint firstGlyphOfRowIndex = firstGlyphOfLineIndex;
 | 
				
			||||||
 | 
								int lineWrapCount = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								while (firstGlyphOfRowIndex < glyphRangeOfLine.Location + glyphRangeOfLine.Length)
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									CGRect lineRect = layoutManager.GetLineFragmentRect((nuint)firstGlyphOfRowIndex,
 | 
				
			||||||
 | 
										out NSRange effectiveRange, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (lineWrapCount == 0)
 | 
				
			||||||
 | 
										this.DrawLineNumber(lineNumber, (float)lineRect.GetMinY()
 | 
				
			||||||
 | 
											+ (float)textView.TextContainerInset.Height);
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Move to next row
 | 
				
			||||||
 | 
									firstGlyphOfRowIndex = effectiveRange.Location + effectiveRange.Length;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								firstGlyphOfLineIndex = glyphRangeOfLine.Location + glyphRangeOfLine.Length;
 | 
				
			||||||
 | 
								lineNumber += 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (layoutManager.ExtraLineFragmentTextContainer != null)
 | 
				
			||||||
 | 
								this.DrawLineNumber(lineNumber, (float)layoutManager.ExtraLineFragmentRect.GetMinY()
 | 
				
			||||||
 | 
									+ (float)textView.TextContainerInset.Height);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void DrawLineNumber(int lineNumber, float yPosition)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (this.ClientView is not NSTextView textView)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							NSDictionary attributes = new(NSStringAttributeKey.Font, textView.Font,
 | 
				
			||||||
 | 
								NSStringAttributeKey.ForegroundColor, this.ForegroundColor);
 | 
				
			||||||
 | 
							NSAttributedString attributedLineNumber = new(lineNumber.ToString(), attributes);
 | 
				
			||||||
 | 
							CGPoint relativePoint = this.ConvertPointFromView(CGPoint.Empty, textView);
 | 
				
			||||||
 | 
							nfloat xPosition = this.GutterWidth - (attributedLineNumber.Size.Width + 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							attributedLineNumber.DrawAtPoint(new CGPoint(xPosition, relativePoint.Y + yPosition));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,8 @@ public class SourceTextView : NSTextView
 | 
				
			||||||
	/// <summary>Should the editor only use default words if the keyword list is empty.</summary>
 | 
						/// <summary>Should the editor only use default words if the keyword list is empty.</summary>
 | 
				
			||||||
	private bool _defaultWordsOnlyIfKeywordsEmpty = true;
 | 
						private bool _defaultWordsOnlyIfKeywordsEmpty = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private LineNumberRuler LineNumberRuler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#endregion
 | 
						#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#region Computed Properties
 | 
						#region Computed Properties
 | 
				
			||||||
| 
						 | 
					@ -245,6 +247,28 @@ public class SourceTextView : NSTextView
 | 
				
			||||||
		this.UsesAdaptiveColorMappingForDarkAppearance = true;
 | 
							this.UsesAdaptiveColorMappingForDarkAppearance = true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public override void AwakeFromNib()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							base.AwakeFromNib();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.LineNumberRuler = new LineNumberRuler(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.EnclosingScrollView.VerticalRulerView = this.LineNumberRuler;
 | 
				
			||||||
 | 
							this.EnclosingScrollView.HasVerticalRuler = true;
 | 
				
			||||||
 | 
							this.EnclosingScrollView.RulersVisible = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.PostsFrameChangedNotifications = true;
 | 
				
			||||||
 | 
							NSView.Notifications.ObserveBoundsChanged((_, _) => this.DrawGutter());
 | 
				
			||||||
 | 
							this.OnTextChanged += (_, _) => this.DrawGutter();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[Export("drawGutter")]
 | 
				
			||||||
 | 
						public void DrawGutter()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (this.LineNumberRuler is not null)
 | 
				
			||||||
 | 
								this.LineNumberRuler.NeedsDisplay = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#endregion
 | 
						#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#region Private Methods
 | 
						#region Private Methods
 | 
				
			||||||
| 
						 | 
					@ -678,6 +702,7 @@ public class SourceTextView : NSTextView
 | 
				
			||||||
		// Console.WriteLine ("Read selection from pasteboard");
 | 
							// Console.WriteLine ("Read selection from pasteboard");
 | 
				
			||||||
		bool result = base.ReadSelectionFromPasteboard(pboard);
 | 
							bool result = base.ReadSelectionFromPasteboard(pboard);
 | 
				
			||||||
		Formatter?.Reformat();
 | 
							Formatter?.Reformat();
 | 
				
			||||||
 | 
							this.OnTextChanged?.Invoke(this, null);
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -695,6 +720,7 @@ public class SourceTextView : NSTextView
 | 
				
			||||||
		// Console.WriteLine ("Read selection from pasteboard also");
 | 
							// Console.WriteLine ("Read selection from pasteboard also");
 | 
				
			||||||
		var result = base.ReadSelectionFromPasteboard(pboard, type);
 | 
							var result = base.ReadSelectionFromPasteboard(pboard, type);
 | 
				
			||||||
		Formatter?.Reformat();
 | 
							Formatter?.Reformat();
 | 
				
			||||||
 | 
							this.OnTextChanged?.Invoke(this, null);
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue