1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.Lex;
  9 
 10 namespace System.Windows
 11 {
 12     public class SourceSpan
 13     {
 14         public SourceSpan() : line(0)scol(0)ecol(0)
 15         {
 16         }
 17         public SourceSpan(int line_short scol_short ecol_) : line(line_)scol(scol_)ecol(ecol_)
 18         {
 19         }
 20         public inline bool IsEmpty() const
 21         {
 22             return line == 0 && scol == 0 && ecol == 0;
 23         }
 24         public int line;
 25         public short scol;
 26         public short ecol;
 27     }
 28 
 29     public inline bool operator==(const SourceSpan& leftconst SourceSpan& right)
 30     {
 31         return left.line == right.line && left.scol == right.scol && left.ecol == right.ecol;
 32     }
 33 
 34     public enum SourceCodeTokenKind : int
 35     {
 36         plainspacekeywordidentifierstringcharacternumbercommentlineNumberbeginBlockendBlock
 37     }
 38 
 39     public ulong GetHashCode(SourceCodeTokenKind tokenKind)
 40     {
 41         return cast<ulong>(cast<int>(tokenKind));
 42     }
 43 
 44     public class SourceCodeTextStyle
 45     {
 46         public SourceCodeTextStyle(Font* font_Brush* brush_) : font(font_)brush(brush_)
 47         {
 48         }
 49         public Font* font;
 50         public Brush* brush;
 51     }
 52 
 53     public inline bool operator==(const SourceCodeTextStyle& leftconst SourceCodeTextStyle& right)
 54     {
 55         return left.font == right.font && left.brush == right.brush;
 56     }
 57 
 58     public class SourceCodeTokenStyle
 59     {
 60         public SourceCodeTokenStyle(const Color& color_FontStyle fontStyle_) : color(color_)fontStyle(fontStyle_)
 61         {
 62         }
 63         public Color color;
 64         public FontStyle fontStyle;
 65     }
 66 
 67     public inline bool operator==(const SourceCodeTokenStyle& leftconst SourceCodeTokenStyle& right)
 68     {
 69         return left.color == right.color && left.fontStyle == right.fontStyle;
 70     }
 71 
 72     public ulong GetHashCode(const SourceCodeTokenStyle& tokenStyle)
 73     {
 74         ulong code = GetHashCode(tokenStyle.color);
 75         code = code + 14695981039346656037u * cast<ulong>(cast<int>(tokenStyle.fontStyle));
 76         return code;
 77     }
 78 
 79     public ulong GetHashCode(const SourceCodeTextStyle& textStyle)
 80     {
 81         ulong code = GetHashCode(textStyle.font);
 82         code = code + 14695981039346656037u * GetHashCode(textStyle.brush);
 83         return code;
 84     }
 85 
 86     public ControlCreateParams& SourceCodeViewControlCreateParams(ControlCreateParams& controlCreateParams)
 87     {
 88         return controlCreateParams.SetWindowClassName("System.Windows.SourceCodeView").SetWindowClassStyle(DoubleClickWindowClassStyle()).
 89             SetWindowStyle(cast<WindowStyle>(DefaultChildWindowStyle() | WindowStyle.WS_TABSTOP)).
 90             SetWindowClassBackgroundColor(SystemColor.COLOR_WINDOW).SetBackgroundColor(Color.White());
 91     }
 92 
 93     public class SourceCodeViewCreateParams
 94     {
 95         public SourceCodeViewCreateParams(TextViewCreateParams& textViewCreateParams_) : textViewCreateParams(textViewCreateParams_)
 96         {
 97         }
 98         public SourceCodeViewCreateParams& Defaults()
 99         {
100             return *this;
101         }
102         public TextViewCreateParams& textViewCreateParams;
103     }
104 
105     public class SourceCodeView : TextView
106     {
107         public SourceCodeView(const FontFamily& fontFamilyfloat fontSizeconst Color& backgroundColorconst Color& textColorconst Point& location
108             const Size& sizeDock dockAnchors anchors) : 
109             base(fontFamilyfontSizebackgroundColortextColorlocationsizedockanchors)numLineNumberDigits(0)
110         {
111             BuildDefaultStyles();
112         }
113         public SourceCodeView(const Point& locationconst Size& sizeDock dockAnchors anchors) : 
114             this(FontFamily("Consolas")10.000000fColor.White()Color.Black()locationsizedockanchors)
115         {
116         }
117         public SourceCodeView(SourceCodeViewCreateParams& createParams) : base(createParams.textViewCreateParams)numLineNumberDigits(0)
118         {
119             BuildDefaultStyles();
120         }
121         [nodiscard]
122         protected override Result<bool> OnFontChanged()
123         {
124             fontStyleFontMap.Clear();
125             textStyleMap.Clear();
126             tokenStyleTextStyleMap.Clear();
127             for (UniquePtr<SourceCodeTokenStyle>& tokenStyle : sourceCodeTokenStyles)
128             {
129                 Brush* brush = GetOrInsertBrush(tokenStyle->color);
130                 Font* font = GetOrInsertFont(tokenStyle->fontStyle);
131                 SourceCodeTextStyle* textStyle = GetOrInsertTextStyle(brushfont);
132                 tokenStyleTextStyleMap[tokenStyle.Get()] = textStyle;
133             }
134             return Result<bool>(true);
135         }
136         public void SetSourceCodeTokenStyle(SourceCodeTokenKind kindSourceCodeTokenStyle style)
137         {
138             SourceCodeTokenStyle* tokenStyle = GetOrInsertTokenStyle(style.colorstyle.fontStyle);
139             tokenKindMap[kind] = tokenStyle;
140         }
141         protected override int LineNumberFieldLength() const
142         {
143             return numLineNumberDigits + 1;
144         }
145         protected override void SetLineNumberFieldLength(int lineCount)
146         {
147             numLineNumberDigits = Log10(lineCount + 1);
148         }
149         protected override void OnLinesChanged()
150         {
151             base->OnLinesChanged();
152             tokenLines.Clear();
153             int state = 0;
154             int n = cast<int>(Lines().Count());
155             numLineNumberDigits = Log10(n + 1);
156             for (int i = 0; i < n; ++i;)
157             {
158                 const ustring& line = Lines()[i];
159                 TokenLine tokenLine = TokenizeLine(linei + 1state);
160                 state = tokenLine.endState;
161                 tokenLines.Add(Rvalue(tokenLine));
162             }
163         }
164         [nodiscard]
165         protected override Result<bool> OnLineChanged(LineEventArgs& args)
166         {
167             auto result = base->OnLineChanged(args);
168             if (result.Error()) return result;
169             int lineIndex = args.lineIndex;
170             const ustring& line = Lines()[lineIndex];
171             int state = 0;
172             if (lineIndex > 0 && !tokenLines.IsEmpty())
173             {
174                 state = tokenLines[lineIndex - 1].endState;
175             }
176             while (lineIndex >= tokenLines.Count())
177             {
178                 tokenLines.Add(TokenLine());
179             }
180             tokenLines[lineIndex] = TokenizeLine(linelineIndex + 1state);
181             return Result<bool>(true);
182         }
183         [nodiscard]
184         protected override Result<bool> OnLineDeleted(LineEventArgs& args)
185         {
186             auto result = base->OnLineDeleted(args);
187             if (result.Error()) return result;
188             int lineIndex = args.lineIndex;
189             tokenLines.Remove(tokenLines.Begin() + lineIndex);
190             return Result<bool>(true);
191         }
192         private bool IsBeginBlockLine(int lineIndex) const
193         {
194             if (lineIndex >= 0 && lineIndex < tokenLines.Count())
195             {
196                 const TokenLine& tokenLine = tokenLines[lineIndex];
197                 for (const Token& token : tokenLine.tokens)
198                 {
199                     if (GetTokenKind(token) == SourceCodeTokenKind.beginBlock)
200                     {
201                         return true;
202                     }
203                 }
204             }
205             return false;
206         }
207         private bool IsEndBlockLine(int lineIndex) const
208         {
209             if (lineIndex >= 0 && lineIndex < tokenLines.Count())
210             {
211                 const TokenLine& tokenLine = tokenLines[lineIndex];
212                 for (const Token& token : tokenLine.tokens)
213                 {
214                     if (GetTokenKind(token) == SourceCodeTokenKind.endBlock)
215                     {
216                         return true;
217                     }
218                 }
219             }
220             return false;
221         }
222         protected override int RemoveIndent(int lineIndex) const
223         {
224             if (IsEndBlockLine(lineIndex))
225             {
226                 return IndentSize();
227             }
228             else
229             {
230                 return 0;
231             }
232         }
233         protected override int GetIndent(const ustring& lineint lineIndex)
234         {
235             for (int i = 0; i < line.Length(); ++i;)
236             {
237                 if (line[i] != ' ')
238                 {
239                     if (IsBeginBlockLine(lineIndex))
240                     {
241                         return i + IndentSize();
242                     }
243                     else
244                     {
245                         return i;
246                     }
247                 }
248             }
249             return 0;
250         }
251         [nodiscard]
252         protected override Result<bool> OnLineInserted(LineEventArgs& args)
253         {
254             auto result = base->OnLineInserted(args);
255             if (result.Error()) return result;
256             int lineIndex = args.lineIndex;
257             ustring& line = Lines()[lineIndex];
258             int state = 0;
259             while (lineIndex >= tokenLines.Count())
260             {
261                 tokenLines.Add(TokenLine());
262             }
263             if (lineIndex > 0)
264             {
265                 state = tokenLines[lineIndex - 1].endState;
266             }
267             tokenLines.Insert(tokenLines.Begin() + lineIndexTokenizeLine(linelineIndex + 1state));
268             return Invalidate();
269         }
270         public ustring GetText(const SourceSpan& span) const
271         {
272             if (span.line >= 1 && span.line <= Lines().Count())
273             {
274                 const ustring& line = Lines()[span.line - 1];
275                 if (span.scol >= 1 && span.scol <= line.Length())
276                 {
277                     int n = span.ecol - span.scol;
278                     if (n > 0)
279                     {
280                         return line.Substring(span.scol - 1n);
281                     }
282                 }
283             }
284             return ustring();
285         }
286         public ustring GetTokenText(int lineNumbershort columnNumber) const
287         {
288             if (lineNumber >= 1 && lineNumber <= tokenLines.Count())
289             {
290                 const TokenLine& tokenLine = tokenLines[lineNumber - 1];
291                 int tokenIndex = tokenLine.TokenIndex(columnNumber);
292                 if (tokenIndex != -1)
293                 {
294                     const Token& token = tokenLine.tokens[tokenIndex];
295                     if (GetTokenKind(token) == SourceCodeTokenKind.identifier)
296                     {
297                         return token.match.ToString();
298                     }
299                 }
300             }
301             return ustring();
302         }
303         protected virtual TokenLine TokenizeLine(const ustring& lineint lineNumberint startState)
304         {
305             return System.Windows.DefaultTokenizeLine(linelineNumberstartState);
306         }
307         protected virtual SourceCodeTokenKind GetTokenKind(const Token& token) const
308         {
309             return SourceCodeTokenKind.plain;
310         }
311         protected override Result<bool> DrawLine(Graphics& graphicsint lineIndexconst PointF& origin)
312         {
313             int lineNumber = lineIndex + 1;
314             string lineNumberStr = System.ToString(lineNumber);
315             string lineNumberText = Format(lineNumberStrnumLineNumberDigitsFormatJustify.right);
316             PointF pt(origin);
317             Result<SourceCodeTextStyle*> lineNumberTextStyleResult = GetTextStyle(SourceCodeTokenKind.lineNumber);
318             if (lineNumberTextStyleResult.Error())
319             {
320                 return Result<bool>(ErrorId(lineNumberTextStyleResult.GetErrorId()));
321             }
322             SourceCodeTextStyle* lineNumberTextStyle = lineNumberTextStyleResult.Value();
323             auto result = graphics.DrawString(lineNumberText*lineNumberTextStyle->fontpt*lineNumberTextStyle->brush);
324             if (result.Error())
325             {
326                 return Result<bool>(ErrorId(result.GetErrorId()));
327             }
328             pt.x = pt.x + CharWidth() * (numLineNumberDigits + 1);
329             const TokenLine& tokenLine = tokenLines[lineIndex];
330             int startState = tokenLine.startState;
331             for (const Token& token : tokenLine.tokens)
332             {
333                 SourceCodeTokenKind tokenKind = GetTokenKind(token);
334                 Result<SourceCodeTextStyle*> tokenTextStyleResult = GetTextStyle(tokenKind);
335                 if (tokenTextStyleResult.Error())
336                 {
337                     return Result<bool>(ErrorId(tokenTextStyleResult.GetErrorId()));
338                 }
339                 SourceCodeTextStyle* tokenTextStyle = tokenTextStyleResult.Value();
340                 ustring tokenStr = token.match.ToString();
341                 auto utf8Result = ToUtf8(tokenStr);
342                 if (utf8Result.Error())
343                 {
344                     return Result<bool>(ErrorId(utf8Result.GetErrorId()));
345                 }
346                 string s(Rvalue(utf8Result.Value()));
347                 auto result = graphics.DrawString(s*tokenTextStyle->fontpt*tokenTextStyle->brush);
348                 if (result.Error())
349                 {
350                     return Result<bool>(ErrorId(result.GetErrorId()));
351                 }
352                 pt.x = pt.x + CharWidth() * tokenStr.Length();
353                 startState = -1;
354             }
355             PointF hiliteOrigin(origin);
356             hiliteOrigin.x = hiliteOrigin.x + CharWidth() * LineNumberFieldLength();
357             result = DrawHilites(graphicslineIndexhiliteOrigin);
358             if (result.Error())
359             {
360                 return Result<bool>(ErrorId(result.GetErrorId()));
361             }
362             return Result<bool>(true);
363         }
364         protected virtual Result<bool> DrawHilites(Graphics& graphicsint lineIndexconst PointF& origin)
365         {
366             return Result<bool>(true);
367         }
368         private Result<SourceCodeTextStyle*> GetTextStyle(SourceCodeTokenKind tokenKind)
369         {
370             SourceCodeTextStyle* textStyle = null;
371             SourceCodeTokenStyle* tokenStyle = null;
372             auto it = tokenKindMap.Find(tokenKind);
373             if (it != tokenKindMap.End())
374             {
375                 tokenStyle = it->second;
376             }
377             else
378             {
379                 int errorId = AllocateError("source code token style not found");
380                 return Result<SourceCodeTextStyle*>(ErrorId(errorId));
381             }
382             auto  it2 = tokenStyleTextStyleMap.Find(tokenStyle);
383             if (it2 != tokenStyleTextStyleMap.End())
384             {
385                 textStyle = it2->second;
386             }
387             else
388             {
389                 int errorId = AllocateError("source code text style not found");
390                 return Result<SourceCodeTextStyle*>(ErrorId(errorId));
391             }
392             return Result<SourceCodeTextStyle*>(textStyle);
393         }
394         private void BuildDefaultStyles()
395         {
396             SourceCodeTokenStyle* plainStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
397             tokenKindMap[SourceCodeTokenKind.plain] = plainStyle;
398             tokenKindMap[SourceCodeTokenKind.beginBlock] = plainStyle;
399             tokenKindMap[SourceCodeTokenKind.endBlock] = plainStyle;
400             SourceCodeTokenStyle* spaceStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
401             tokenKindMap[SourceCodeTokenKind.space] = spaceStyle;
402             SourceCodeTokenStyle* keywordStyle = GetOrInsertTokenStyle(Color.Blue()FontStyle.regular);
403             tokenKindMap[SourceCodeTokenKind.keyword] = keywordStyle;
404             SourceCodeTokenStyle* identifierStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
405             tokenKindMap[SourceCodeTokenKind.identifier] = identifierStyle;
406             SourceCodeTokenStyle* stringStyle = GetOrInsertTokenStyle(Color(163u21u21u)FontStyle.regular);
407             tokenKindMap[SourceCodeTokenKind.string] = stringStyle;
408             SourceCodeTokenStyle* characterStyle = GetOrInsertTokenStyle(Color(163u21u21u)FontStyle.regular);
409             tokenKindMap[SourceCodeTokenKind.character] = characterStyle;
410             SourceCodeTokenStyle* numberStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
411             tokenKindMap[SourceCodeTokenKind.number] = numberStyle;
412             SourceCodeTokenStyle* commentStyle = GetOrInsertTokenStyle(Color(0u128u0u)FontStyle.regular);
413             tokenKindMap[SourceCodeTokenKind.comment] = commentStyle;
414             SourceCodeTokenStyle* lineNumberStyle = GetOrInsertTokenStyle(Color(43u145u175u)FontStyle.regular);
415             tokenKindMap[SourceCodeTokenKind.lineNumber] = lineNumberStyle;
416         }
417         private SourceCodeTokenStyle* GetOrInsertTokenStyle(const Color& colorFontStyle fontStyle)
418         {
419             SourceCodeTokenStyle style(colorfontStyle);
420             HashMap<SourceCodeTokenStyleSourceCodeTokenStyle*>.ConstIterator it = tokenStyleMap.CFind(style);
421             if (it != tokenStyleMap.CEnd())
422             {
423                 return it->second;
424             }
425             SourceCodeTokenStyle* sourceCodeTokenStyle = new SourceCodeTokenStyle(colorfontStyle);
426             sourceCodeTokenStyles.Add(UniquePtr<SourceCodeTokenStyle>(sourceCodeTokenStyle));
427             tokenStyleMap[style] = sourceCodeTokenStyle;
428             Brush* brush = GetOrInsertBrush(color);
429             Font* font = GetOrInsertFont(fontStyle);
430             SourceCodeTextStyle* textStyle = GetOrInsertTextStyle(brushfont);
431             tokenStyleTextStyleMap[sourceCodeTokenStyle] = textStyle;
432             return sourceCodeTokenStyle;
433         }
434         public Font* GetOrInsertFont(FontStyle fontStyle)
435         {
436             Map<FontStyleFont*>.ConstIterator it = fontStyleFontMap.CFind(fontStyle);
437             if (it != fontStyleFontMap.CEnd())
438             {
439                 return it->second;
440             }
441             Font* font = new Font(GetFontFamily()FontSize()fontStyleUnit.point);
442             Fonts().Add(UniquePtr<Font>(font));
443             fontStyleFontMap[fontStyle] = font;
444             return font;
445         }
446         public SourceCodeTextStyle* GetOrInsertTextStyle(Brush* brushFont* font)
447         {
448             SourceCodeTextStyle style(fontbrush);
449             HashMap<SourceCodeTextStyleSourceCodeTextStyle*>.ConstIterator it = textStyleMap.CFind(style);
450             if (it != textStyleMap.CEnd())
451             {
452                 return it->second;
453             }
454             SourceCodeTextStyle* textStyle = new SourceCodeTextStyle(fontbrush);
455             textStyles.Add(UniquePtr<SourceCodeTextStyle>(textStyle));
456             textStyleMap[style] = textStyle;
457             return textStyle;
458         }
459         private List<TokenLine> tokenLines;
460         private int numLineNumberDigits;
461         private List<UniquePtr<SourceCodeTextStyle>> textStyles;
462         private List<UniquePtr<SourceCodeTokenStyle>> sourceCodeTokenStyles;
463         private Map<FontStyleFont*> fontStyleFontMap;
464         private HashMap<SourceCodeTokenStyleSourceCodeTokenStyle*> tokenStyleMap;
465         private HashMap<SourceCodeTokenStyle*SourceCodeTextStyle*> tokenStyleTextStyleMap;
466         private HashMap<SourceCodeTextStyleSourceCodeTextStyle*> textStyleMap;
467         private HashMap<SourceCodeTokenKindSourceCodeTokenStyle*> tokenKindMap;
468     }
469 
470     public TokenLine DefaultTokenizeLine(const ustring& lineint lineNumberint startState)
471     {
472         Token token;
473         TokenLine tokenLine;
474         uchar* begin = line.Chars();
475         uchar* end = line.Chars() + line.Length();
476         token.match.begin = begin;
477         token.match.end = end;
478         token.line = lineNumber;
479         tokenLine.tokens.Add(token);
480         tokenLine.endState = 0;
481         return tokenLine;
482     }