1 // =================================
  2 // Copyright (c) 2021 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 nothrow SourceSpan() : line(0)scol(0)ecol(0)
 15         {
 16         }
 17         public nothrow SourceSpan(int line_short scol_short ecol_) : line(line_)scol(scol_)ecol(ecol_)
 18         {
 19         }
 20         public inline nothrow 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 nothrow 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 nothrow ulong GetHashCode(SourceCodeTokenKind tokenKind)
 40     {
 41         return cast<ulong>(cast<int>(tokenKind));
 42     }
 43 
 44     public class SourceCodeTextStyle
 45     {
 46         public nothrow SourceCodeTextStyle(Font* font_Brush* brush_) : font(font_)brush(brush_)
 47         {
 48         }
 49         public Font* font;
 50         public Brush* brush;
 51     }
 52 
 53     public inline nothrow 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 nothrow 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 nothrow bool operator==(const SourceCodeTokenStyle& leftconst SourceCodeTokenStyle& right)
 68     {
 69         return left.color == right.color && left.fontStyle == right.fontStyle;
 70     }
 71 
 72     public nothrow 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 nothrow 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 nothrow 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 nothrow SourceCodeViewCreateParams(TextViewCreateParams& textViewCreateParams_) : textViewCreateParams(textViewCreateParams_)
 96         {
 97         }
 98         public nothrow 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.0fColor.White()Color.Black()locationsizedockanchors)
115         {
116         }
117         public SourceCodeView(SourceCodeViewCreateParams& createParams) : base(createParams.textViewCreateParams)numLineNumberDigits(0)
118         {
119             BuildDefaultStyles();
120         }
121         protected override void OnFontChanged()
122         {
123             fontStyleFontMap.Clear();
124             textStyleMap.Clear();
125             tokenStyleTextStyleMap.Clear();
126             for (UniquePtr<SourceCodeTokenStyle>& tokenStyle : sourceCodeTokenStyles)
127             {
128                 Brush* brush = GetOrInsertBrush(tokenStyle->color);
129                 Font* font = GetOrInsertFont(tokenStyle->fontStyle);
130                 SourceCodeTextStyle* textStyle = GetOrInsertTextStyle(brushfont);
131                 tokenStyleTextStyleMap[tokenStyle.Get()] = textStyle;
132             }
133         }
134         public void SetSourceCodeTokenStyle(SourceCodeTokenKind kindSourceCodeTokenStyle style)
135         {
136             SourceCodeTokenStyle* tokenStyle = GetOrInsertTokenStyle(style.colorstyle.fontStyle);
137             tokenKindMap[kind] = tokenStyle;
138         }
139         protected override nothrow int LineNumberFieldLength() const
140         {
141             return numLineNumberDigits + 1;
142         }
143         protected override nothrow void SetLineNumberFieldLength(int lineCount)
144         {
145             numLineNumberDigits = Log10(lineCount + 1);
146         }
147         protected override void OnLinesChanged()
148         {
149             base->OnLinesChanged();
150             tokenLines.Clear();
151             int state = 0;
152             int n = cast<int>(Lines().Count());
153             numLineNumberDigits = Log10(n + 1);
154             for (int i = 0; i < n; ++i;)
155             {
156                 const ustring& line = Lines()[i];
157                 TokenLine tokenLine = TokenizeLine(linei + 1state);
158                 state = tokenLine.endState;
159                 tokenLines.Add(Rvalue(tokenLine));
160             }
161         }
162         protected override void OnLineChanged(LineEventArgs& args)
163         {
164             base->OnLineChanged(args);
165             int lineIndex = args.lineIndex;
166             const ustring& line = Lines()[lineIndex];
167             int state = 0;
168             if (lineIndex > 0 && !tokenLines.IsEmpty())
169             {
170                 state = tokenLines[lineIndex - 1].endState;
171             }
172             while (lineIndex >= tokenLines.Count())
173             {
174                 tokenLines.Add(TokenLine());
175             }
176             tokenLines[lineIndex] = TokenizeLine(linelineIndex + 1state);
177         }
178         protected override void OnLineDeleted(LineEventArgs& args)
179         {
180             base->OnLineDeleted(args);
181             int lineIndex = args.lineIndex;
182             tokenLines.Remove(tokenLines.Begin() + lineIndex);
183         }
184         private nothrow bool IsBeginBlockLine(int lineIndex) const
185         {
186             if (lineIndex >= 0 && lineIndex < tokenLines.Count())
187             {
188                 const TokenLine& tokenLine = tokenLines[lineIndex];
189                 for (const Token& token : tokenLine.tokens)
190                 {
191                     if (GetTokenKind(token) == SourceCodeTokenKind.beginBlock)
192                     {
193                         return true;
194                     }
195                 }
196             }
197             return false;
198         }
199         private nothrow bool IsEndBlockLine(int lineIndex) const
200         {
201             if (lineIndex >= 0 && lineIndex < tokenLines.Count())
202             {
203                 const TokenLine& tokenLine = tokenLines[lineIndex];
204                 for (const Token& token : tokenLine.tokens)
205                 {
206                     if (GetTokenKind(token) == SourceCodeTokenKind.endBlock)
207                     {
208                         return true;
209                     }
210                 }
211             }
212             return false;
213         }
214         protected override nothrow int RemoveIndent(int lineIndex) const
215         {
216             if (IsEndBlockLine(lineIndex))
217             {
218                 return IndentSize();
219             }
220             else
221             {
222                 return 0;
223             }
224         }
225         protected override nothrow int GetIndent(const ustring& lineint lineIndex)
226         {
227             for (int i = 0; i < line.Length(); ++i;)
228             {
229                 if (line[i] != ' ')
230                 {
231                     if (IsBeginBlockLine(lineIndex))
232                     {
233                         return i + IndentSize();
234                     }
235                     else
236                     {
237                         return i;
238                     }
239                 }
240             }
241             return 0;
242         }
243         protected override void OnLineInserted(LineEventArgs& args)
244         {
245             base->OnLineInserted(args);
246             int lineIndex = args.lineIndex;
247             ustring& line = Lines()[lineIndex];
248             int state = 0;
249             while (lineIndex >= tokenLines.Count())
250             {
251                 tokenLines.Add(TokenLine());
252             }
253             if (lineIndex > 0)
254             {
255                 state = tokenLines[lineIndex - 1].endState;
256             }
257             tokenLines.Insert(tokenLines.Begin() + lineIndexTokenizeLine(linelineIndex + 1state));
258             Invalidate();
259         }
260         public nothrow ustring GetText(const SourceSpan& span) const
261         {
262             if (span.line >= 1 && span.line <= Lines().Count())
263             {
264                 const ustring& line = Lines()[span.line - 1];
265                 if (span.scol >= 1 && span.scol <= line.Length())
266                 {
267                     int n = span.ecol - span.scol;
268                     if (n > 0)
269                     {
270                         return line.Substring(span.scol - 1n);
271                     }
272                 }
273             }
274             return ustring();
275         }
276         public nothrow ustring GetTokenText(int lineNumbershort columnNumber) const
277         {
278             if (lineNumber >= 1 && lineNumber <= tokenLines.Count())
279             {
280                 const TokenLine& tokenLine = tokenLines[lineNumber - 1];
281                 int tokenIndex = tokenLine.TokenIndex(columnNumber);
282                 if (tokenIndex != -1)
283                 {
284                     const Token& token = tokenLine.tokens[tokenIndex];
285                     if (GetTokenKind(token) == SourceCodeTokenKind.identifier)
286                     {
287                         return token.match.ToString();
288                     }
289                 }
290             }
291             return ustring();
292         }
293         protected virtual TokenLine TokenizeLine(const ustring& lineint lineNumberint startState)
294         {
295             return System.Windows.DefaultTokenizeLine(linelineNumberstartState);
296         }
297         protected virtual nothrow SourceCodeTokenKind GetTokenKind(const Token& token) const
298         {
299             return SourceCodeTokenKind.plain;
300         }
301         protected override void DrawLine(Graphics& graphicsint lineIndexconst PointF& origin)
302         {
303             int lineNumber = lineIndex + 1;
304             string lineNumberStr = System.ToString(lineNumber);
305             string lineNumberText = Format(lineNumberStrnumLineNumberDigitsFormatJustify.right);
306             PointF pt(origin);
307             SourceCodeTextStyle* lineNumberTextStyle = GetTextStyle(SourceCodeTokenKind.lineNumber);
308             graphics.DrawStringChecked(lineNumberText*lineNumberTextStyle->fontpt*lineNumberTextStyle->brush);
309             pt.x = pt.x + CharWidth() * (numLineNumberDigits + 1);
310             const TokenLine& tokenLine = tokenLines[lineIndex];
311             int startState = tokenLine.startState;
312             for (const Token& token : tokenLine.tokens)
313             {
314                 SourceCodeTokenKind tokenKind = GetTokenKind(token);
315                 SourceCodeTextStyle* tokenTextStyle = GetTextStyle(tokenKind);
316                 ustring tokenStr = token.match.ToString();
317                 string s(ToUtf8(tokenStr));
318                 graphics.DrawStringChecked(s*tokenTextStyle->fontpt*tokenTextStyle->brush);
319                 pt.x = pt.x + CharWidth() * tokenStr.Length();
320                 startState = -1;
321             }
322             PointF hiliteOrigin(origin);
323             hiliteOrigin.x = hiliteOrigin.x + CharWidth() * LineNumberFieldLength();
324             DrawHilites(graphicslineIndexhiliteOrigin);
325         }
326         protected virtual void DrawHilites(Graphics& graphicsint lineIndexconst PointF& origin)
327         {
328         }
329         private SourceCodeTextStyle* GetTextStyle(SourceCodeTokenKind tokenKind)
330         {
331             SourceCodeTextStyle* textStyle = null;
332             SourceCodeTokenStyle* tokenStyle = null;
333             HashMap<SourceCodeTokenKindSourceCodeTokenStyle*>.ConstIterator it = tokenKindMap.CFind(tokenKind);
334             if (it != tokenKindMap.CEnd())
335             {
336                 tokenStyle = it->second;
337             }
338             else
339             {
340                 throw Exception("source code token style not found");
341             }
342             HashMap<SourceCodeTokenStyle*SourceCodeTextStyle*>.ConstIterator it2 = tokenStyleTextStyleMap.CFind(tokenStyle);
343             if (it2 != tokenStyleTextStyleMap.CEnd())
344             {
345                 textStyle = it2->second;
346             }
347             else
348             {
349                 throw Exception("source code text style not found");
350             }
351             return textStyle;
352         }
353         private void BuildDefaultStyles()
354         {
355             SourceCodeTokenStyle* plainStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
356             tokenKindMap[SourceCodeTokenKind.plain] = plainStyle;
357             tokenKindMap[SourceCodeTokenKind.beginBlock] = plainStyle;
358             tokenKindMap[SourceCodeTokenKind.endBlock] = plainStyle;
359             SourceCodeTokenStyle* spaceStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
360             tokenKindMap[SourceCodeTokenKind.space] = spaceStyle;
361             SourceCodeTokenStyle* keywordStyle = GetOrInsertTokenStyle(Color.Blue()FontStyle.regular);
362             tokenKindMap[SourceCodeTokenKind.keyword] = keywordStyle;
363             SourceCodeTokenStyle* identifierStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
364             tokenKindMap[SourceCodeTokenKind.identifier] = identifierStyle;
365             SourceCodeTokenStyle* stringStyle = GetOrInsertTokenStyle(Color(163u21u21u)FontStyle.regular);
366             tokenKindMap[SourceCodeTokenKind.string] = stringStyle;
367             SourceCodeTokenStyle* characterStyle = GetOrInsertTokenStyle(Color(163u21u21u)FontStyle.regular);
368             tokenKindMap[SourceCodeTokenKind.character] = characterStyle;
369             SourceCodeTokenStyle* numberStyle = GetOrInsertTokenStyle(Color.Black()FontStyle.regular);
370             tokenKindMap[SourceCodeTokenKind.number] = numberStyle;
371             SourceCodeTokenStyle* commentStyle = GetOrInsertTokenStyle(Color(0u128u0u)FontStyle.regular);
372             tokenKindMap[SourceCodeTokenKind.comment] = commentStyle;
373             SourceCodeTokenStyle* lineNumberStyle = GetOrInsertTokenStyle(Color(43u145u175u)FontStyle.regular);
374             tokenKindMap[SourceCodeTokenKind.lineNumber] = lineNumberStyle;
375         }
376         private SourceCodeTokenStyle* GetOrInsertTokenStyle(const Color& colorFontStyle fontStyle)
377         {
378             SourceCodeTokenStyle style(colorfontStyle);
379             HashMap<SourceCodeTokenStyleSourceCodeTokenStyle*>.ConstIterator it = tokenStyleMap.CFind(style);
380             if (it != tokenStyleMap.CEnd())
381             {
382                 return it->second;
383             }
384             SourceCodeTokenStyle* sourceCodeTokenStyle = new SourceCodeTokenStyle(colorfontStyle);
385             sourceCodeTokenStyles.Add(UniquePtr<SourceCodeTokenStyle>(sourceCodeTokenStyle));
386             tokenStyleMap[style] = sourceCodeTokenStyle;
387             Brush* brush = GetOrInsertBrush(color);
388             Font* font = GetOrInsertFont(fontStyle);
389             SourceCodeTextStyle* textStyle = GetOrInsertTextStyle(brushfont);
390             tokenStyleTextStyleMap[sourceCodeTokenStyle] = textStyle;
391             return sourceCodeTokenStyle;
392         }
393         public Font* GetOrInsertFont(FontStyle fontStyle)
394         {
395             Map<FontStyleFont*>.ConstIterator it = fontStyleFontMap.CFind(fontStyle);
396             if (it != fontStyleFontMap.CEnd())
397             {
398                 return it->second;
399             }
400             Font* font = new Font(GetFontFamily()FontSize()fontStyleUnit.point);
401             Fonts().Add(UniquePtr<Font>(font));
402             fontStyleFontMap[fontStyle] = font;
403             return font;
404         }
405         public SourceCodeTextStyle* GetOrInsertTextStyle(Brush* brushFont* font)
406         {
407             SourceCodeTextStyle style(fontbrush);
408             HashMap<SourceCodeTextStyleSourceCodeTextStyle*>.ConstIterator it = textStyleMap.CFind(style);
409             if (it != textStyleMap.CEnd())
410             {
411                 return it->second;
412             }
413             SourceCodeTextStyle* textStyle = new SourceCodeTextStyle(fontbrush);
414             textStyles.Add(UniquePtr<SourceCodeTextStyle>(textStyle));
415             textStyleMap[style] = textStyle;
416             return textStyle;
417         }
418         private List<TokenLine> tokenLines;
419         private int numLineNumberDigits;
420         private List<UniquePtr<SourceCodeTextStyle>> textStyles;
421         private List<UniquePtr<SourceCodeTokenStyle>> sourceCodeTokenStyles;
422         private Map<FontStyleFont*> fontStyleFontMap;
423         private HashMap<SourceCodeTokenStyleSourceCodeTokenStyle*> tokenStyleMap;
424         private HashMap<SourceCodeTokenStyle*SourceCodeTextStyle*> tokenStyleTextStyleMap;
425         private HashMap<SourceCodeTextStyleSourceCodeTextStyle*> textStyleMap;
426         private HashMap<SourceCodeTokenKindSourceCodeTokenStyle*> tokenKindMap;
427     }
428 
429     public TokenLine DefaultTokenizeLine(const ustring& lineint lineNumberint startState)
430     {
431         Token token;
432         TokenLine tokenLine;
433         uchar* begin = line.Chars();
434         uchar* end = line.Chars() + line.Length();
435         token.match.begin = begin;
436         token.match.end = end;
437         token.line = lineNumber;
438         tokenLine.tokens.Add(token);
439         tokenLine.endState = 0;
440         return tokenLine;
441     }
442 }