127 lines
3.8 KiB
C#
127 lines
3.8 KiB
C#
|
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));
|
|||
|
}
|
|||
|
}
|
|||
|
|