Add line numbers
This commit is contained in:
parent
7610ffaf38
commit
ab3fb386d9
|
@ -684,7 +684,7 @@
|
|||
</subviews>
|
||||
</clipView>
|
||||
<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"/>
|
||||
</scroller>
|
||||
<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>
|
||||
private bool _defaultWordsOnlyIfKeywordsEmpty = true;
|
||||
|
||||
private LineNumberRuler LineNumberRuler;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Computed Properties
|
||||
|
@ -245,6 +247,28 @@ public class SourceTextView : NSTextView
|
|||
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
|
||||
|
||||
#region Private Methods
|
||||
|
@ -678,6 +702,7 @@ public class SourceTextView : NSTextView
|
|||
// Console.WriteLine ("Read selection from pasteboard");
|
||||
bool result = base.ReadSelectionFromPasteboard(pboard);
|
||||
Formatter?.Reformat();
|
||||
this.OnTextChanged?.Invoke(this, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -695,6 +720,7 @@ public class SourceTextView : NSTextView
|
|||
// Console.WriteLine ("Read selection from pasteboard also");
|
||||
var result = base.ReadSelectionFromPasteboard(pboard, type);
|
||||
Formatter?.Reformat();
|
||||
this.OnTextChanged?.Invoke(this, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue