1
2
3
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& left, const 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 plain, space, keyword, identifier, string, character, number, comment, lineNumber, beginBlock, endBlock
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& left, const 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& left, const 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& fontFamily, float fontSize, const Color& backgroundColor, const Color& textColor, const Point& location,
108 const Size& size, Dock dock, Anchors anchors) :
109 base(fontFamily, fontSize, backgroundColor, textColor, location, size, dock, anchors), numLineNumberDigits(0)
110 {
111 BuildDefaultStyles();
112 }
113 public SourceCodeView(const Point& location, const Size& size, Dock dock, Anchors anchors) :
114 this(FontFamily("Consolas"), 10.000000f, Color.White(), Color.Black(), location, size, dock, anchors)
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(brush, font);
132 tokenStyleTextStyleMap[tokenStyle.Get()] = textStyle;
133 }
134 return Result<bool>(true);
135 }
136 public void SetSourceCodeTokenStyle(SourceCodeTokenKind kind, SourceCodeTokenStyle style)
137 {
138 SourceCodeTokenStyle* tokenStyle = GetOrInsertTokenStyle(style.color, style.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(line, i + 1, state);
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(line, lineIndex + 1, state);
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& line, int 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() + lineIndex, TokenizeLine(line, lineIndex + 1, state));
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 - 1, n);
281 }
282 }
283 }
284 return ustring();
285 }
286 public ustring GetTokenText(int lineNumber, short 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& line, int lineNumber, int startState)
304 {
305 return System.Windows.DefaultTokenizeLine(line, lineNumber, startState);
306 }
307 protected virtual SourceCodeTokenKind GetTokenKind(const Token& token) const
308 {
309 return SourceCodeTokenKind.plain;
310 }
311 protected override Result<bool> DrawLine(Graphics& graphics, int lineIndex, const PointF& origin)
312 {
313 int lineNumber = lineIndex + 1;
314 string lineNumberStr = System.ToString(lineNumber);
315 string lineNumberText = Format(lineNumberStr, numLineNumberDigits, FormatJustify.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->font, pt, *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->font, pt, *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(graphics, lineIndex, hiliteOrigin);
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& graphics, int lineIndex, const 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(163u, 21u, 21u), FontStyle.regular);
407 tokenKindMap[SourceCodeTokenKind.string] = stringStyle;
408 SourceCodeTokenStyle* characterStyle = GetOrInsertTokenStyle(Color(163u, 21u, 21u), 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(0u, 128u, 0u), FontStyle.regular);
413 tokenKindMap[SourceCodeTokenKind.comment] = commentStyle;
414 SourceCodeTokenStyle* lineNumberStyle = GetOrInsertTokenStyle(Color(43u, 145u, 175u), FontStyle.regular);
415 tokenKindMap[SourceCodeTokenKind.lineNumber] = lineNumberStyle;
416 }
417 private SourceCodeTokenStyle* GetOrInsertTokenStyle(const Color& color, FontStyle fontStyle)
418 {
419 SourceCodeTokenStyle style(color, fontStyle);
420 HashMap<SourceCodeTokenStyle, SourceCodeTokenStyle*>.ConstIterator it = tokenStyleMap.CFind(style);
421 if (it != tokenStyleMap.CEnd())
422 {
423 return it->second;
424 }
425 SourceCodeTokenStyle* sourceCodeTokenStyle = new SourceCodeTokenStyle(color, fontStyle);
426 sourceCodeTokenStyles.Add(UniquePtr<SourceCodeTokenStyle>(sourceCodeTokenStyle));
427 tokenStyleMap[style] = sourceCodeTokenStyle;
428 Brush* brush = GetOrInsertBrush(color);
429 Font* font = GetOrInsertFont(fontStyle);
430 SourceCodeTextStyle* textStyle = GetOrInsertTextStyle(brush, font);
431 tokenStyleTextStyleMap[sourceCodeTokenStyle] = textStyle;
432 return sourceCodeTokenStyle;
433 }
434 public Font* GetOrInsertFont(FontStyle fontStyle)
435 {
436 Map<FontStyle, Font*>.ConstIterator it = fontStyleFontMap.CFind(fontStyle);
437 if (it != fontStyleFontMap.CEnd())
438 {
439 return it->second;
440 }
441 Font* font = new Font(GetFontFamily(), FontSize(), fontStyle, Unit.point);
442 Fonts().Add(UniquePtr<Font>(font));
443 fontStyleFontMap[fontStyle] = font;
444 return font;
445 }
446 public SourceCodeTextStyle* GetOrInsertTextStyle(Brush* brush, Font* font)
447 {
448 SourceCodeTextStyle style(font, brush);
449 HashMap<SourceCodeTextStyle, SourceCodeTextStyle*>.ConstIterator it = textStyleMap.CFind(style);
450 if (it != textStyleMap.CEnd())
451 {
452 return it->second;
453 }
454 SourceCodeTextStyle* textStyle = new SourceCodeTextStyle(font, brush);
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<FontStyle, Font*> fontStyleFontMap;
464 private HashMap<SourceCodeTokenStyle, SourceCodeTokenStyle*> tokenStyleMap;
465 private HashMap<SourceCodeTokenStyle*, SourceCodeTextStyle*> tokenStyleTextStyleMap;
466 private HashMap<SourceCodeTextStyle, SourceCodeTextStyle*> textStyleMap;
467 private HashMap<SourceCodeTokenKind, SourceCodeTokenStyle*> tokenKindMap;
468 }
469
470 public TokenLine DefaultTokenizeLine(const ustring& line, int lineNumber, int 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 }