1 module workspaced.com.dlangui; 2 3 import core.thread; 4 import std.algorithm; 5 import std.json; 6 import std.process; 7 import std.string; 8 import std.uni; 9 10 import workspaced.api; 11 import workspaced.completion.dml; 12 13 @component("dlangui") 14 class DlanguiComponent : ComponentWrapper 15 { 16 mixin DefaultComponentWrapper; 17 18 /// Queries for code completion at position `pos` in DML code 19 /// Returns: `[{type: CompletionType, value: string, documentation: string, enumName: string}]` 20 /// Where type is an integer 21 Future!(CompletionItem[]) complete(scope const(char)[] code, int pos) 22 { 23 auto ret = new typeof(return); 24 gthreads.create({ 25 mixin(traceTask); 26 try 27 { 28 LocationInfo info = getLocationInfo(code, pos); 29 CompletionItem[] suggestions; 30 string name = info.itemScope[$ - 1]; 31 string[] stack; 32 if (info.itemScope.length > 1) 33 stack = info.itemScope[0 .. $ - 1]; 34 string[][] curScope = stack.getProvidedScope(); 35 if (info.type == LocationType.RootMember) 36 { 37 foreach (CompletionLookup item; dmlCompletions) 38 { 39 if (item.item.type == CompletionType.Class) 40 { 41 if (name.length == 0 || item.item.value.canFind(name)) 42 { 43 suggestions ~= item.item; 44 } 45 } 46 } 47 } 48 else if (info.type == LocationType.Member) 49 { 50 foreach (CompletionLookup item; dmlCompletions) 51 { 52 if (item.item.type == CompletionType.Class) 53 { 54 if (name.length == 0 || item.item.value.canFind(name)) 55 { 56 suggestions ~= item.item; 57 } 58 } 59 else if (item.item.type != CompletionType.EnumDefinition) 60 { 61 if (curScope.canFind(item.requiredScope)) 62 { 63 if (name.length == 0 || item.item.value.canFind(name)) 64 { 65 suggestions ~= item.item; 66 } 67 } 68 } 69 } 70 } 71 else if (info.type == LocationType.PropertyValue) 72 { 73 foreach (CompletionLookup item; dmlCompletions) 74 { 75 if (item.item.type == CompletionType.EnumValue) 76 { 77 if (curScope.canFind(item.requiredScope)) 78 { 79 if (item.item.value == name) 80 { 81 foreach (CompletionLookup enumdef; dmlCompletions) 82 { 83 if (enumdef.item.type == CompletionType.EnumDefinition) 84 { 85 if (enumdef.item.enumName == item.item.enumName) 86 suggestions ~= enumdef.item; 87 } 88 } 89 break; 90 } 91 } 92 } 93 else if (item.item.type == CompletionType.Boolean) 94 { 95 if (curScope.canFind(item.requiredScope)) 96 { 97 if (item.item.value == name) 98 { 99 suggestions ~= CompletionItem(CompletionType.Keyword, "true"); 100 suggestions ~= CompletionItem(CompletionType.Keyword, "false"); 101 break; 102 } 103 } 104 } 105 } 106 } 107 ret.finish(suggestions); 108 } 109 catch (Throwable e) 110 { 111 ret.error(e); 112 } 113 }); 114 return ret; 115 } 116 } 117 118 /// 119 enum CompletionType : ubyte 120 { 121 /// 122 Undefined = 0, 123 /// 124 Class = 1, 125 /// 126 String = 2, 127 /// 128 Number = 3, 129 /// 130 Color = 4, 131 /// 132 EnumDefinition = 5, 133 /// 134 EnumValue = 6, 135 /// 136 Rectangle = 7, 137 /// 138 Boolean = 8, 139 /// 140 Keyword = 9, 141 } 142 143 /// Returned by list-completion 144 struct CompletionItem 145 { 146 /// 147 CompletionType type; 148 /// 149 string value; 150 /// 151 string documentation = ""; 152 /// 153 string enumName = ""; 154 } 155 156 struct CompletionLookup 157 { 158 CompletionItem item; 159 string[][] providedScope = []; 160 string[] requiredScope = []; 161 } 162 163 private: 164 165 string[][] getProvidedScope(string[] stack) 166 { 167 if (stack.length == 0) 168 return []; 169 string[][] providedScope; 170 foreach (CompletionLookup item; dmlCompletions) 171 { 172 if (item.item.type == CompletionType.Class) 173 { 174 if (item.item.value == stack[$ - 1]) 175 { 176 providedScope ~= item.providedScope; 177 break; 178 } 179 } 180 } 181 return providedScope; 182 } 183 184 enum LocationType : ubyte 185 { 186 RootMember, 187 Member, 188 PropertyValue, 189 None 190 } 191 192 struct LocationInfo 193 { 194 LocationType type; 195 string[] itemScope; 196 string propertyName; 197 } 198 199 LocationInfo getLocationInfo(scope const(char)[] code, int pos) 200 { 201 LocationInfo current; 202 current.type = LocationType.RootMember; 203 current.itemScope = []; 204 current.propertyName = ""; 205 string member = ""; 206 bool inString = false; 207 bool escapeChar = false; 208 foreach (i, c; code) 209 { 210 if (i == pos) 211 break; 212 if (inString) 213 { 214 if (escapeChar) 215 escapeChar = false; 216 else 217 { 218 if (c == '\\') 219 { 220 escapeChar = true; 221 } 222 else if (c == '"') 223 { 224 inString = false; 225 current.type = LocationType.None; 226 member = ""; 227 escapeChar = false; 228 } 229 } 230 continue; 231 } 232 else 233 { 234 if (c == '{') 235 { 236 current.itemScope ~= member; 237 current.propertyName = ""; 238 member = ""; 239 current.type = LocationType.Member; 240 } 241 else if (c == '\n' || c == '\r' || c == ';') 242 { 243 current.propertyName = ""; 244 member = ""; 245 current.type = LocationType.Member; 246 } 247 else if (c == ':') 248 { 249 current.propertyName = member; 250 member = ""; 251 current.type = LocationType.PropertyValue; 252 } 253 else if (c == '"') 254 { 255 inString = true; 256 } 257 else if (c == '}') 258 { 259 if (current.itemScope.length > 0) 260 current.itemScope.length--; 261 current.type = LocationType.None; 262 current.propertyName = ""; 263 member = ""; 264 } 265 else if (c.isWhite) 266 { 267 if (current.type == LocationType.None) 268 current.type = LocationType.Member; 269 if (current.itemScope.length == 0) 270 current.type = LocationType.RootMember; 271 } 272 else 273 { 274 if (current.type == LocationType.Member || current.type == LocationType.RootMember) 275 member ~= c; 276 } 277 } 278 } 279 if (member.length) 280 current.propertyName = member; 281 current.itemScope ~= current.propertyName; 282 return current; 283 } 284 285 unittest 286 { 287 auto info = getLocationInfo(" ", 0); 288 assert(info.type == LocationType.RootMember); 289 info = getLocationInfo(`TableLayout { mar }`, 17); 290 assert(info.itemScope == ["TableLayout", "mar"]); 291 assert(info.type == LocationType.Member); 292 info = getLocationInfo(`TableLayout { margins: 20; paddin }`, 33); 293 assert(info.itemScope == ["TableLayout", "paddin"]); 294 assert(info.type == LocationType.Member); 295 info = getLocationInfo( 296 "TableLayout { margins: 20; padding : 10\n\t\tTextWidget { text: \"} foo } }", 70); 297 assert(info.itemScope == ["TableLayout", "TextWidget", "text"]); 298 assert(info.type == LocationType.PropertyValue); 299 info = getLocationInfo(`TableLayout { margins: 2 }`, 24); 300 assert(info.itemScope == ["TableLayout", "margins"]); 301 assert(info.type == LocationType.PropertyValue); 302 info = getLocationInfo( 303 "TableLayout { margins: 20; padding : 10\n\t\tTextWidget { text: \"} foobar\" } } ", int.max); 304 assert(info.itemScope == [""]); 305 assert(info.type == LocationType.RootMember); 306 info = getLocationInfo( 307 "TableLayout { margins: 20; padding : 10\n\t\tTextWidget { text: \"} foobar\"; } }", 69); 308 assert(info.itemScope == ["TableLayout", "TextWidget", "text"]); 309 assert(info.type == LocationType.PropertyValue); 310 info = getLocationInfo("TableLayout {\n\t", int.max); 311 assert(info.itemScope == ["TableLayout", ""]); 312 assert(info.type == LocationType.Member); 313 info = getLocationInfo(`TableLayout { 314 colCount: 2 315 margins: 20; padding: 10 316 backgroundColor: "#FFFFE0" 317 TextWidget { 318 t`, int.max); 319 assert(info.itemScope == ["TableLayout", "TextWidget", "t"]); 320 assert(info.type == LocationType.Member); 321 }