Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EditorViewAdapter.cs
Go to the documentation of this file.
1 #region License
2 /* **********************************************************************************
3  * Copyright (c) Roman Ivantsov
4  * This source code is subject to terms and conditions of the MIT License
5  * for Irony. A copy of the license can be found in the License.txt file
6  * at the root of this distribution.
7  * By using this source code in any fashion, you are agreeing to be bound by the terms of the
8  * MIT License.
9  * You must not remove this notice from this software.
10  * **********************************************************************************/
11 #endregion
12 
13 using System;
14 using System.Collections.Generic;
15 using System.Linq;
16 using System.Text;
17 using System.Threading;
18 
19 using Irony.Parsing;
20 
21 namespace Irony.GrammarExplorer {
22  public delegate void ColorizeMethod();
23  public interface IUIThreadInvoker {
24  void InvokeOnUIThread(ColorizeMethod colorize);
25  }
26 
27  public class ColorizeEventArgs : EventArgs {
28  public readonly TokenList Tokens;
29  public ColorizeEventArgs(TokenList tokens) {
30  Tokens = tokens;
31  }
32  }
33 
34  //Container for two numbers representing visible range of the source text (min...max)
35  // we use it to allow replacing two numbers in atomic operation
36  public class ViewRange {
37  public readonly int Min, Max;
38  public ViewRange(int min, int max) {
39  Min = min;
40  Max = max;
41  }
42  public bool Equals(ViewRange other) {
43  return other.Min == Min && other.Max == Max;
44  }
45  }
46 
47  public class ViewData {
48  // ColoredTokens + NotColoredTokens == Source.Tokens
49  public readonly TokenList ColoredTokens = new TokenList();
50  public readonly TokenList NotColoredTokens = new TokenList(); //tokens not colored yet
51  public ParseTree Tree;
52  public ViewData(ParseTree tree) {
53  this.Tree = tree;
54  if (tree == null) return;
55  NotColoredTokens.AddRange(tree.Tokens);
56  }
57  }
58 
59  //Two scenarios:
60  // 1. Colorizing in current view range. We colorize only those tokens in current view range that were not colorized yet.
61  // For this we keep two lists (colorized and not colorized) tokens, and move tokens from one list to another when
62  // we actually colorize them.
63  // 2. Typing/Editing - new editor content is being pushed from EditorAdapter. We try to avoid recoloring all visible tokens, when
64  // user just typed a single char. What we do is try to identify "already-colored" tokens in new token list by matching
65  // old viewData.ColoredTokens to newly scanned token list - initially in new-viewData.NonColoredTokens. If we find a "match",
66  // we move the token from NonColored to Colored in new viewData. This all happens on background thread.
67 
68  public class EditorViewAdapterList : List<EditorViewAdapter> { }
69 
70  public class EditorViewAdapter {
71  public readonly EditorAdapter Adapter;
72  private IUIThreadInvoker _invoker;
73  //public readonly Control Control;
74  ViewData _data;
75  ViewRange _range;
76  bool _wantsColorize;
77  int _colorizing;
78  public event EventHandler<ColorizeEventArgs> ColorizeTokens;
79 
81  Adapter = adapter;
82  _invoker = invoker;
83  Adapter.AddView(this);
84  _range = new ViewRange(-1, -1);
85  }
86 
87  //SetViewRange and SetNewText are called by text box's event handlers to notify adapter that user did something edit box
88  public void SetViewRange(int min, int max) {
89  _range = new ViewRange(min, max);
90  _wantsColorize = true;
91  }
92  //The new text is passed directly to EditorAdapter instance (possibly shared by several view adapters).
93  // EditorAdapter parses the text on a separate background thread, and notifies back this and other
94  // view adapters and provides them with newly parsed source through UpdateParsedSource method (see below)
95  public void SetNewText(string newText) {
96  //TODO: fix this
97  //hack, temp solution for more general problem
98  //When we load/replace/clear entire text, clear out colored tokens to force recoloring from scratch
99  if (string.IsNullOrEmpty(newText))
100  _data = null;
101  Adapter.SetNewText(newText);
102  }
103 
104  //Called by EditorAdapter to provide the latest parsed source
105  public void UpdateParsedSource(ParseTree newTree) {
106  lock (this) {
107  var oldData = _data;
108  _data = new ViewData(newTree);
109  //Now try to figure out tokens that match old Colored tokens
110  if (oldData != null && oldData.Tree != null) {
111  DetectAlreadyColoredTokens(oldData.ColoredTokens, _data.Tree.SourceText.Length - oldData.Tree.SourceText.Length);
112  }
113  _wantsColorize = true;
114  }//lock
115  }
116 
117 
118  #region Colorizing
119  public bool WantsColorize {
120  get { return _wantsColorize; }
121  }
122 
123  public void TryInvokeColorize() {
124  if (!_wantsColorize) return;
125  int colorizing = Interlocked.Exchange(ref _colorizing, 1);
126  if (colorizing != 0) return;
127  _invoker.InvokeOnUIThread(Colorize);
128  }
129  private void Colorize() {
130  var range = _range;
131  var data = _data;
132  if (data != null) {
133  TokenList tokensToColor;
134  lock (this) {
135  tokensToColor = ExtractTokensInRange(data.NotColoredTokens, range.Min, range.Max);
136  }
137  if (ColorizeTokens != null && tokensToColor != null && tokensToColor.Count > 0) {
138  data.ColoredTokens.AddRange(tokensToColor);
139  ColorizeEventArgs args = new ColorizeEventArgs(tokensToColor);
140  ColorizeTokens(this, args);
141  }
142  }//if data != null ...
143  _wantsColorize = false;
144  _colorizing = 0;
145  }
146 
147  private void DetectAlreadyColoredTokens(TokenList oldColoredTokens, int shift) {
148  foreach (Token oldColored in oldColoredTokens) {
149  int index;
150  Token newColored;
151  if (FindMatchingToken(_data.NotColoredTokens, oldColored, 0, out index, out newColored) ||
152  FindMatchingToken(_data.NotColoredTokens, oldColored, shift, out index, out newColored)) {
153  _data.NotColoredTokens.RemoveAt(index);
154  _data.ColoredTokens.Add(newColored);
155  }
156  }//foreach
157  }
158 
159  #endregion
160 
161  #region token utilities
162  private bool FindMatchingToken(TokenList inTokens, Token token, int shift, out int index, out Token result) {
163  index = LocateToken(inTokens, token.Location.Position + shift);
164  if (index >= 0) {
165  result = inTokens[index];
166  if (TokensMatch(token, result, shift)) return true;
167  }
168  index = -1;
169  result = null;
170  return false;
171  }
172  public bool TokensMatch(Token x, Token y, int shift) {
173  if (x.Location.Position + shift != y.Location.Position) return false;
174  if (x.Terminal != y.Terminal) return false;
175  if (x.Text != y.Text) return false;
176  //Note: be careful comparing x.Value and y.Value - if value is "ValueType", it is boxed and erroneously reports non-equal
177  //if (x.ValueString != y.ValueString) return false;
178  return true;
179  }
180  public TokenList ExtractTokensInRange(TokenList tokens, int from, int until) {
181  TokenList result = new TokenList();
182  for (int i = tokens.Count - 1; i >= 0; i--) {
183  var tkn = tokens[i];
184  if (tkn.Location.Position > until || (tkn.Location.Position + tkn.Length < from)) continue;
185  result.Add(tkn);
186  tokens.RemoveAt(i);
187  }
188  return result;
189  }
190 
191  public TokenList GetTokensInRange(int from, int until) {
192  ViewData data = _data;
193  if (data == null) return null;
194  return GetTokensInRange(data.Tree.Tokens, from, until);
195  }
196  public TokenList GetTokensInRange(TokenList tokens, int from, int until) {
197  TokenList result = new TokenList();
198  int fromIndex = LocateToken(tokens, from);
199  int untilIndex = LocateToken(tokens, until);
200  if (fromIndex < 0) fromIndex = 0;
201  if (untilIndex >= tokens.Count) untilIndex = tokens.Count - 1;
202  for (int i = fromIndex; i <= untilIndex; i++) {
203  result.Add(tokens[i]);
204  }
205  return result;
206  }
207 
208  //TODO: find better place for these methods
209  public int LocateToken(TokenList tokens, int position) {
210  if (tokens == null || tokens.Count == 0) return -1;
211  var lastToken = tokens[tokens.Count - 1];
212  var lastTokenEnd = lastToken.Location.Position + lastToken.Length;
213  if (position < tokens[0].Location.Position || position > lastTokenEnd) return -1;
214  return LocateTokenExt(tokens, position, 0, tokens.Count - 1);
215  }
216  private int LocateTokenExt(TokenList tokens, int position, int fromIndex, int untilIndex) {
217  if (fromIndex + 1 >= untilIndex) return fromIndex;
218  int midIndex = (fromIndex + untilIndex) / 2;
219  Token middleToken = tokens[midIndex];
220  if (middleToken.Location.Position <= position)
221  return LocateTokenExt(tokens, position, midIndex, untilIndex);
222  else
223  return LocateTokenExt(tokens, position, fromIndex, midIndex);
224  }
225  #endregion
226 
227 
228  }//EditorViewAdapter class
229 
230 }//namespace
EventHandler< ColorizeEventArgs > ColorizeTokens
int LocateToken(TokenList tokens, int position)
TokenList ExtractTokensInRange(TokenList tokens, int from, int until)
delegate void ColorizeMethod()
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t y
Definition: DirectXTexP.h:191
EditorViewAdapter(EditorAdapter adapter, IUIThreadInvoker invoker)
string Text
Gets the text associated with this token.
Definition: Token.cs:145
TokenList GetTokensInRange(TokenList tokens, int from, int until)
readonly SourceLocation Location
Location in the source code.
Definition: Token.cs:116
A List of tokens.
Definition: Token.cs:60
Terminal Terminal
Gets the terminal.
Definition: Token.cs:121
TokenList GetTokensInRange(int from, int until)
bool TokensMatch(Token x, Token y, int shift)
Tokens are produced by scanner and fed to parser, optionally passing through Token filters in between...
Definition: Token.cs:74
Tokens
Summary Canonical example of MPLEX automaton
readonly TokenList Tokens
Definition: ParseTree.cs:125