1 module dlangide.ui.dsourceedit;
2 
3 import dlangui.core.logger;
4 import dlangui.widgets.editors;
5 import dlangui.widgets.srcedit;
6 
7 import ddc.lexer.textsource;
8 import ddc.lexer.exceptions;
9 import ddc.lexer.tokenizer;
10 
11 import dlangide.workspace.workspace;
12 import dlangide.workspace.project;
13 import dlangide.ui.commands;
14 
15 import std.algorithm;
16 
17 
18 /// DIDE source file editor
19 class DSourceEdit : SourceEdit {
20 	this(string ID) {
21 		super(ID);
22 		styleId = null;
23 		backgroundColor = 0xFFFFFF;
24         setTokenHightlightColor(TokenCategory.Comment, 0x008000); // green
25         setTokenHightlightColor(TokenCategory.Keyword, 0x0000FF); // blue
26         setTokenHightlightColor(TokenCategory.String, 0xA31515);  // brown
27         setTokenHightlightColor(TokenCategory.Character, 0xA31515);  // brown
28         setTokenHightlightColor(TokenCategory.Error, 0xFF0000);  // red
29         setTokenHightlightColor(TokenCategory.Comment_Documentation, 0x206000);
30         //setTokenHightlightColor(TokenCategory.Identifier, 0x206000);  // no colors
31 	}
32 	this() {
33 		this("SRCEDIT");
34 	}
35     protected ProjectSourceFile _projectSourceFile;
36     @property ProjectSourceFile projectSourceFile() { return _projectSourceFile; }
37     /// load by filename
38     override bool load(string fn) {
39         _projectSourceFile = null;
40         bool res = super.load(fn);
41         setHighlighter();
42         return res;
43     }
44 
45     void setHighlighter() {
46         if (filename.endsWith(".d") || filename.endsWith(".dd") || filename.endsWith(".dh") || filename.endsWith(".ddoc")) {
47             content.syntaxHighlighter = new SimpleDSyntaxHighlighter(filename);
48         } else {
49             content.syntaxHighlighter = null;
50         }
51     }
52 
53     /// load by project item
54     bool load(ProjectSourceFile f) {
55         if (!load(f.filename)) {
56             _projectSourceFile = null;
57             return false;
58         }
59         _projectSourceFile = f;
60         setHighlighter();
61         return true;
62     }
63 
64     /// save to the same file
65     bool save() {
66         return _content.save();
67     }
68 
69     /// override to handle specific actions
70 	override bool handleAction(const Action a) {
71         if (a) {
72             switch (a.id) {
73                 case IDEActions.FileSave:
74                     save();
75                     return true;
76                 default:
77                     break;
78             }
79         }
80         return super.handleAction(a);
81     }
82 }
83 
84 
85 
86 class SimpleDSyntaxHighlighter : SyntaxHighlighter {
87 
88     SourceFile _file;
89     ArraySourceLines _lines;
90     Tokenizer _tokenizer;
91     this (string filename) {
92         _file = new SourceFile(filename);
93         _lines = new ArraySourceLines();
94         _tokenizer = new Tokenizer(_lines);
95         _tokenizer.errorTolerant = true;
96     }
97 
98     TokenPropString[] _props;
99 
100     /// categorize characters in content by token types
101     void updateHighlight(dstring[] lines, TokenPropString[] props, int changeStartLine, int changeEndLine) {
102         Log.d("updateHighlight");
103         long ms0 = currentTimeMillis();
104         _props = props;
105         changeStartLine = 0;
106         changeEndLine = cast(int)lines.length;
107         _lines.init(lines[changeStartLine..$], _file, changeStartLine);
108         _tokenizer.init(_lines);
109         int tokenPos = 0;
110         int tokenLine = 0;
111         ubyte category = 0;
112         try {
113             for (;;) {
114                 Token token = _tokenizer.nextToken();
115                 if (token is null) {
116                     //Log.d("Null token returned");
117                     break;
118                 }
119                 if (token.type == TokenType.EOF) {
120                     //Log.d("EOF token");
121                     break;
122                 }
123                 uint newPos = token.pos - 1;
124                 uint newLine = token.line - 1;
125 
126                 //Log.d("", token.line, ":", token.pos, "\t", tokenLine + 1, ":", tokenPos + 1, "\t", token.toString);
127 
128                 // fill with category
129                 for (int i = tokenLine; i <= newLine; i++) {
130                     int start = i > tokenLine ? 0 : tokenPos;
131                     int end = i < newLine ? cast(int)lines[i].length : newPos;
132                     for (int j = start; j < end; j++)
133                         _props[i][j] = category;
134                 }
135 
136                 // handle token - convert to category
137                 switch(token.type) {
138                     case TokenType.COMMENT:
139                         category = token.isDocumentationComment ? TokenCategory.Comment_Documentation : TokenCategory.Comment;
140                         break;
141                     case TokenType.KEYWORD:
142                         category = TokenCategory.Keyword;
143                         break;
144                     case TokenType.IDENTIFIER:
145                         category = TokenCategory.Identifier;
146                         break;
147                     case TokenType.STRING:
148                         category = TokenCategory.String;
149                         break;
150                     case TokenType.CHARACTER:
151                         category = TokenCategory.Character;
152                         break;
153                     case TokenType.INTEGER:
154                         category = TokenCategory.Integer;
155                         break;
156                     case TokenType.FLOAT:
157                         category = TokenCategory.Float;
158                         break;
159                     case TokenType.INVALID:
160                         switch (token.invalidTokenType) {
161                             case TokenType.IDENTIFIER:
162                                 category = TokenCategory.Error_InvalidIdentifier;
163                                 break;
164                             case TokenType.STRING:
165                                 category = TokenCategory.Error_InvalidString;
166                                 break;
167                             case TokenType.COMMENT:
168                                 category = TokenCategory.Error_InvalidComment;
169                                 break;
170                             case TokenType.FLOAT:
171                             case TokenType.INTEGER:
172                                 category = TokenCategory.Error_InvalidNumber;
173                                 break;
174                             default:
175                                 category = TokenCategory.Error;
176                                 break;
177                         }
178                         break;
179                     default:
180                         category = 0;
181                         break;
182                 }
183                 tokenPos = newPos;
184                 tokenLine= newLine;
185 
186             }
187         } catch (Exception e) {
188             Log.e("exception while trying to parse D source", e);
189         }
190         _lines.close();
191         _props = null;
192         Log.d("updateHighlight took ", currentTimeMillis() - ms0, "ms");
193     }
194 }
195