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