詞法分析器生成器終于做好了,因此我又畫了一個狀態(tài)機然后生成了一個詞法分析器,因此開始研究IDE的智能提示的技術(shù)了。智能提示的技術(shù)有幾個要點,第一個是無論怎么慢都不能妨礙你打字,第二個是崩潰了也不能讓IDE關(guān)掉,要重啟分析器。因此我做了一個小實驗。首先我將NativeX語言的著色器跟詞法分析器都做好了,因此我要做的事情就是在你打字的時候,用另外一個線程進(jìn)行詞法分析,得到結(jié)果之后用一個框框來展示出光標(biāo)所在的那個token:
注意光標(biāo)所在的位置是在方框的內(nèi)部。為了實現(xiàn)這個過程,首先我們要一個安全的多線程機制。我們把詞法分析作為一個請求來看,在打字的過程中我們就不斷把請求發(fā)送到詞法分析器頭上,然后詞法分析器分析完了就會發(fā)個消息給窗口然后傳送結(jié)果。我們注意到如果打字太快導(dǎo)致來不及處理的話,可能會積累若干個請求,不過那些請求實際上只有最后一個需要被處理,之前的那些來不及處理的舊請求都會被忽略。反正我們的字已經(jīng)打完了,詞法分析器就不需要分析那些我們還沒打完的字了。因此我們把它抽象一下:
我們有一個請求的類,這個類需要的數(shù)據(jù)有輸入類型、輸出類型、計算方法以及傳送方法。為什么要傳送方法呢?因為計算是在另外一個線程完成的,因此在調(diào)用傳送方法的時候也是在另外一個線程完成的,所以我們得提供一個函數(shù)來接受分析后的結(jié)果,然后用一種安全的方法傳送到我們想要的地方去。因此我們就把這個過程分成了兩個類,一個處理多線程的問題的抽象類,和一個用來提供計算方法和傳送方法的子類:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6
7 namespace CodeBoxControl
8 {
9 public abstract class CalculationNotifier<I, O> : IDisposable
10 {
11 private Thread workingThread = null;
12 private I analyzingInput = default(I);
13 private bool analyzingInputAvailable = false;
14 private Semaphore codeSemaphore = null;
15 private object locker = null;
16
17 public CalculationNotifier()
18 {
19 this.locker = new object();
20 this.codeSemaphore = new Semaphore(0, 1);
21 this.workingThread = new Thread(Run);
22 this.workingThread.Start();
23 }
24
25 public void Analyze(I input)
26 {
27 bool needRelease = false;
28 lock (this.locker)
29 {
30 if (!this.analyzingInputAvailable)
31 {
32 needRelease = true;
33 }
34 this.analyzingInputAvailable = true;
35 this.analyzingInput = input;
36 }
37 if (needRelease)
38 {
39 this.codeSemaphore.Release(1);
40 }
41 }
42
43 public void Dispose()
44 {
45 this.workingThread.Abort();
46 this.codeSemaphore.Dispose();
47 }
48
49 protected abstract O Calculate(I input);
50 protected abstract void Receive(O output);
51
52 private void Run()
53 {
54 while (true)
55 {
56 this.codeSemaphore.WaitOne();
57 I input = default(I);
58 lock (this.locker)
59 {
60 input = this.analyzingInput;
61 this.analyzingInputAvailable = false;
62 }
63 O output = Calculate(input);
64 Receive(output);
65 }
66 }
67 }
68 }
69
怎么使用它呢?我們只需要不斷地把數(shù)據(jù)發(fā)送給Analyze函數(shù),那個函數(shù)就會自動替我們處理好跟同步相關(guān)的所有問題了,然后在另外一個線程調(diào)用分析函數(shù)和返回結(jié)果:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using CodeBoxControl;
6 using CodeBoxControl.CodeProvider;
7
8 namespace CodeForm
9 {
10 class NativeXAnalyzingResult
11 {
12 public List<CodeToken> Tokens { get; set; }
13 }
14
15 interface INativeXAnalyzingResultReceiver
16 {
17 void Receive(NativeXAnalyzingResult result);
18 }
19
20 class NativeXCodeAnalyzer : CalculationNotifier<string, NativeXAnalyzingResult>
21 {
22 private NativeXTokenizer tokenizer = new NativeXTokenizer();
23 private INativeXAnalyzingResultReceiver receiver = null;
24
25 public NativeXCodeAnalyzer(INativeXAnalyzingResultReceiver receiver)
26 {
27 this.receiver = receiver;
28 }
29
30 protected override NativeXAnalyzingResult Calculate(string input)
31 {
32 NativeXAnalyzingResult result = new NativeXAnalyzingResult();
33 result.Tokens = this.tokenizer.Tokenize(input.ToCharArray());
34 return result;
35 }
36
37 protected override void Receive(NativeXAnalyzingResult output)
38 {
39 this.receiver.Receive(output);
40 }
41 }
42 }
43
這個類就是用來負(fù)責(zé)做詞法分析的了。當(dāng)然我們注意到此時接受數(shù)據(jù)的過程還是被抽象掉了,因為這個過程實際上應(yīng)該讓插件去處理,因為插件才知道要怎么畫框。目前的控件支持兩個插件,一個是著色器,另一個是監(jiān)視器。監(jiān)視器負(fù)責(zé)監(jiān)聽字符串的改變和插手繪圖的過程,我們可以看出這個插件是如何把詞法分析結(jié)果和畫框框聯(lián)系在一起的:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using CodeBoxControl;
6 using System.Windows.Forms;
7 using CodeBoxControl.Core;
8 using CodeBoxControl.CodeProvider;
9 using System.Drawing;
10
11 namespace CodeForm
12 {
13 class NativeXControlPanel
14 : ITextEditorControlPanel
15 , INativeXAnalyzingResultReceiver
16 , IDisposable
17 {
18 private ITextEditorControlPanelCallBack callback = null;
19 private NativeXAnalyzingResult analyzingResult = null;
20 private NativeXCodeAnalyzer analyzer = null;
21
22 private TextPosition grayStart = new TextPosition(0, 0);
23 private TextPosition grayEnd = new TextPosition(0, 0);
24
25 public int Width
26 {
27 get
28 {
29 return 0;
30 }
31 }
32
33 public void InstallCallBack(ITextEditorControlPanelCallBack callback)
34 {
35 this.analyzer = new NativeXCodeAnalyzer(this);
36 this.callback = callback;
37 this.callback.TextEditorBox.SelectionChanged += new EventHandler(TextEditorBox_SelectionChanged);
38 }
39
40 public void OnEdit(CodeBoxControl.Core.TextPosition start, CodeBoxControl.Core.TextPosition oldEnd, CodeBoxControl.Core.TextPosition newEnd)
41 {
42 this.analyzer.Analyze(this.callback.TextEditorBox.Text);
43 }
44
45 public bool NeedColorLineForDisplay(int lineIndex)
46 {
47 return this.grayStart != this.grayEnd && this.grayStart.row <= lineIndex && lineIndex <= this.grayEnd.row;
48 }
49
50 public void ColorLineForDisplay(int lineIndex, int[] colors)
51 {
52 TextLine<TextEditorBox.LineInfo> line = this.callback.TextEditorBox.TextProvider[lineIndex];
53 int length = line.CharCount;
54
55 int start = grayStart.row == lineIndex ? Math.Min(grayStart.col, length) : 0;
56 int end = grayEnd.row == lineIndex ? Math.Min(grayEnd.col, length) : line.CharCount;
57 for (int i = start; i < end; i++)
58 {
59 colors[i] = NativeXColorizer.BlockPointColorId;
60 }
61 }
62
63 public void DrawLineBackground(Graphics g, int lineIndex, Rectangle backgroundArea)
64 {
65 }
66
67 public void DrawLineForeground(Graphics g, int lineIndex, Rectangle backgroundArea)
68 {
69 if (NeedColorLineForDisplay(lineIndex))
70 {
71 TextLine<TextEditorBox.LineInfo> line = this.callback.TextEditorBox.TextProvider[lineIndex];
72 int length = line.CharCount;
73 int start = grayStart.row == lineIndex ? Math.Min(grayStart.col, length) : 0;
74 int end = grayEnd.row == lineIndex ? Math.Min(grayEnd.col, length) : line.CharCount;
75
76 int x1 = this.callback.TextEditorBox.TextPositionToViewPoint(new TextPosition(lineIndex, start)).X;
77 int x2 = this.callback.TextEditorBox.TextPositionToViewPoint(new TextPosition(lineIndex, end)).X;
78 g.DrawRectangle(Pens.Gray, x1, backgroundArea.Top, x2 - x1, backgroundArea.Height);
79 }
80 }
81
82 public void DrawControlPanel(Graphics g, int lineIndex, Rectangle controlPanelArea)
83 {
84 }
85
86 public void DrawControlPanelBackground(Graphics g, Rectangle backgroundArea)
87 {
88 }
89
90 public void OnMouseDown(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
91 {
92 }
93
94 public void OnMouseMove(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
95 {
96 }
97
98 public void OnMouseUp(int lineIndex, Rectangle controlPanelArea, Point relativePosition, System.Windows.Forms.MouseButtons buttons)
99 {
100 }
101
102 public void Receive(NativeXAnalyzingResult result)
103 {
104 this.callback.TextEditorBox.Invoke(new MethodInvoker(() =>
105 {
106 this.analyzingResult = result;
107 UpdateBlock();
108 }));
109 }
110
111 public void Dispose()
112 {
113 this.analyzer.Dispose();
114 }
115
116 private void UpdateBlock()
117 {
118 NativeXAnalyzingResult result = this.analyzingResult;
119 TextPosition pos = this.callback.TextEditorBox.SelectionCaret;
120 if (result != null)
121 {
122 foreach (CodeToken token in result.Tokens)
123 {
124 if (token.Start < pos && pos < token.End)
125 {
126 this.grayStart = token.Start;
127 this.grayEnd = token.End;
128 return;
129 }
130 }
131 this.grayStart = new TextPosition(0, 0);
132 this.grayEnd = new TextPosition(0, 0);
133 }
134 }
135
136 private void TextEditorBox_SelectionChanged(object sender, EventArgs e)
137 {
138 UpdateBlock();
139 }
140 }
141 }
142
這里我們借助了System.Windows.Forms.Control.Invoke來將一整個閉包壓到了消息循環(huán)里面,然后消息循環(huán)來執(zhí)行這個閉包,因此接收數(shù)據(jù)的過程就在UI線程上完成了。Invoke跟SendMessage其實是差不多的。我們還能看出這個插件的接口叫ControlPanel,所以是用來干預(yù)一切變化的。這里有Width屬性但是設(shè)置成0了,如果不是0的話就可以模仿VisualStudio左邊那個放斷點的控制欄了,這個在之前已經(jīng)演示過了。
到這里如何在非UI線程處理代碼的技術(shù)就介紹完了,現(xiàn)在讓我們來看著色器和詞法分析器的樣子哈。首先上著色器的狀態(tài)機:
然后是詞法分析器的狀態(tài)機:
至于他們生成的代碼,可以去
Vczh Library++ 3.0這里看哈。
posted on 2010-10-14 08:23
陳梓瀚(vczh) 閱讀(5861)
評論(6) 編輯 收藏 引用 所屬分類:
開發(fā)自己的IDE