1 module dlangide.workspace.workspace; 2 3 import dlangide.workspace.project; 4 import dlangide.workspace.workspacesettings; 5 import dlangide.ui.frame; 6 import dlangui.core.logger; 7 import dlangui.core.settings; 8 import std.algorithm : map, equal, endsWith; 9 import std.array : empty; 10 import std.conv; 11 import std.file; 12 import std.path; 13 import std.range : array; 14 import std.utf; 15 16 import ddebug.common.debugger; 17 18 enum BuildOperation { 19 Build, 20 Clean, 21 Rebuild, 22 Run, 23 Upgrade 24 } 25 26 enum BuildConfiguration { 27 Debug, 28 Release, 29 Unittest 30 } 31 32 33 /** 34 Exception thrown on Workspace errors 35 */ 36 class WorkspaceException : Exception 37 { 38 this(string msg, string file = __FILE__, size_t line = __LINE__) 39 { 40 super(msg, file, line); 41 } 42 } 43 44 immutable string WORKSPACE_EXTENSION = ".dlangidews"; 45 immutable string WORKSPACE_SETTINGS_EXTENSION = ".wssettings"; 46 47 /// return true if filename matches rules for workspace file names 48 bool isWorkspaceFile(string filename) { 49 return filename.endsWith(WORKSPACE_EXTENSION); 50 } 51 52 /// DlangIDE workspace 53 class Workspace : WorkspaceItem { 54 protected Project[] _projects; 55 protected SettingsFile _workspaceFile; 56 protected WorkspaceSettings _settings; 57 58 protected IDEFrame _frame; 59 60 this(IDEFrame frame, string fname = WORKSPACE_EXTENSION) { 61 super(fname); 62 _workspaceFile = new SettingsFile(fname); 63 _settings = new WorkspaceSettings(fname ? fname ~ WORKSPACE_SETTINGS_EXTENSION : null); 64 _frame = frame; 65 } 66 67 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 68 foreach(p; _projects) { 69 ProjectSourceFile res = p.findSourceFile(projectFileName, fullFileName); 70 if (res) 71 return res; 72 } 73 return null; 74 } 75 76 @property Setting includePath(){ 77 Setting res = _workspaceFile.objectByPath("includePath", true); 78 return res; 79 } 80 81 @property Project[] projects() { return _projects; } 82 83 @property BuildConfiguration buildConfiguration() { 84 return cast(BuildConfiguration)_settings.buildConfiguration; 85 } 86 87 @property void buildConfiguration(BuildConfiguration config) { 88 _settings.buildConfiguration = cast(int)config; 89 } 90 91 protected Project _startupProject; 92 93 @property Project startupProject() { return _startupProject; } 94 @property void startupProject(Project project) { 95 if (_startupProject is project) 96 return; 97 _startupProject = project; 98 _settings.startupProjectName = toUTF8(project.name); 99 _frame.updateProjectConfigurations(); 100 } 101 102 /// Last opened files in workspace 103 @property WorkspaceFile[] files() { 104 return _settings.files(); 105 } 106 107 /// Last opened files in workspace 108 @property void files(WorkspaceFile[] fs) { 109 _settings.files(fs); 110 } 111 112 /// read list of expanded items from settings 113 @property string[] expandedItems() { 114 return _settings.expandedItems(); 115 } 116 117 /// update list of expanded items in settings 118 @property void expandedItems(string[] items) { 119 _settings.expandedItems(items); 120 } 121 122 /// last selected workspace item in workspace explorer 123 @property string selectedWorkspaceItem() { 124 return _settings.selectedWorkspaceItem; 125 } 126 127 /// update last selected workspace item in workspace explorer 128 @property void selectedWorkspaceItem(string item) { 129 if (_settings.selectedWorkspaceItem != item) 130 _settings.selectedWorkspaceItem = item; 131 } 132 133 /// setups currrent project configuration by name 134 void setStartupProjectConfiguration(string conf) 135 { 136 if(_startupProject) { 137 _startupProject.projectConfiguration = conf; 138 } 139 } 140 141 private void updateBreakpointFiles(Breakpoint[] breakpoints) { 142 foreach(bp; breakpoints) { 143 Project project = findProjectByName(bp.projectName); 144 if (project) 145 bp.fullFilePath = project.relativeToAbsolutePath(bp.projectFilePath); 146 } 147 } 148 149 private void updateBookmarkFiles(EditorBookmark[] bookmarks) { 150 foreach(bp; bookmarks) { 151 Project project = findProjectByName(bp.projectName); 152 if (project) 153 bp.fullFilePath = project.relativeToAbsolutePath(bp.projectFilePath); 154 } 155 } 156 157 Breakpoint[] getSourceFileBreakpoints(ProjectSourceFile file) { 158 Breakpoint[] res = _settings.getProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath); 159 updateBreakpointFiles(res); 160 return res; 161 } 162 163 void setSourceFileBreakpoints(ProjectSourceFile file, Breakpoint[] breakpoints) { 164 _settings.setProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath, breakpoints); 165 } 166 167 EditorBookmark[] getSourceFileBookmarks(ProjectSourceFile file) { 168 EditorBookmark[] res = _settings.getProjectBookmarks(toUTF8(file.project.name), file.projectFilePath); 169 updateBookmarkFiles(res); 170 return res; 171 } 172 173 void setSourceFileBookmarks(ProjectSourceFile file, EditorBookmark[] bookmarks) { 174 _settings.setProjectBookmarks(toUTF8(file.project.name), file.projectFilePath, bookmarks); 175 } 176 177 /// returns all workspace breakpoints 178 Breakpoint[] getBreakpoints() { 179 Breakpoint[] res = _settings.getBreakpoints(); 180 updateBreakpointFiles(res); 181 return res; 182 } 183 184 protected void fillStartupProject() { 185 string s = _settings.startupProjectName; 186 if ((!_startupProject || !_startupProject.name.toUTF8.equal(s)) && _projects.length) { 187 if (!s.empty) { 188 foreach(p; _projects) { 189 if (p.name.toUTF8.equal(s)) { 190 _startupProject = p; 191 } 192 } 193 } 194 if (!_startupProject) { 195 startupProject = _projects[0]; 196 } 197 } 198 } 199 200 /// tries to find source file in one of projects, returns found project source file item, or null if not found 201 ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true, dstring projectName = null) { 202 foreach (Project p; _projects) { 203 if (projectName && p.name != projectName) 204 continue; 205 ProjectSourceFile res = p.findSourceFileItem(filename, fullFileName); 206 if (res) 207 return res; 208 } 209 return null; 210 } 211 212 /// find project in workspace by filename 213 Project findProject(string filename) { 214 foreach (Project p; _projects) { 215 if (p.filename.equal(filename)) 216 return p; 217 } 218 return null; 219 } 220 221 /// find project in workspace by filename 222 Project findProjectByName(string name) { 223 foreach (Project p; _projects) { 224 if (p.name.toUTF8.equal(name)) 225 return p; 226 } 227 return null; 228 } 229 230 Project findProjectInWorkspace(Project p) { 231 foreach(existing; _projects) 232 if (existing is p || existing.filename == p.filename) 233 return existing; 234 return null; 235 } 236 237 Project findProjectInWorkspace(string projectFilename) { 238 foreach(existing; _projects) 239 if (existing.filename == projectFilename) 240 return existing; 241 return null; 242 } 243 244 void addProject(Project p) { 245 if (findProjectInWorkspace(p)) 246 return; 247 Log.d("addProject ", p.filename); 248 _projects ~= p; 249 p.workspace = this; 250 fillStartupProject(); 251 } 252 253 Project removeProject(int index) { 254 if (index < 0 || index > _projects.length) 255 return null; 256 Project res = _projects[index]; 257 for (int j = index; j + 1 < _projects.length; j++) 258 _projects[j] = _projects[j + 1]; 259 _projects.length = _projects.length - 1; 260 return res; 261 } 262 263 bool isDependencyProjectUsed(string filename) { 264 foreach(p; _projects) 265 if (!p.isDependency && p.findDependencyProject(filename)) 266 return true; 267 return false; 268 } 269 270 void cleanupUnusedDependencies() { 271 for (int i = cast(int)_projects.length - 1; i >= 0; i--) { 272 if (_projects[i].isDependency) { 273 if (!isDependencyProjectUsed(_projects[i].filename)) 274 removeProject(i); 275 } 276 } 277 } 278 279 bool addDependencyProject(Project p) { 280 if (findProjectInWorkspace(p)) 281 return false; 282 addProject(p); 283 return true; 284 } 285 286 string absoluteToRelativePath(string path) { 287 return toForwardSlashSeparator(relativePath(path, _dir)); 288 } 289 290 override bool save(string fname = null) { 291 if (fname.length > 0) 292 filename = fname; 293 if (_filename.empty) // no file name specified 294 return false; 295 _settings.save(_filename ~ WORKSPACE_SETTINGS_EXTENSION); 296 // If name is null, then compose it from projects 297 // If description is null, then compose it from project's descriptions 298 immutable auto nf = _name.empty; 299 immutable auto df = _description.empty; 300 if (nf || df) 301 { 302 _name = nf ? "" : _name; 303 _description = df ? "" : _description; 304 foreach (Project p; _projects) { 305 if (p.isDependency) 306 continue; // don't add dependency 307 if (nf) 308 _name ~= p.name ~ ","; 309 if (df) 310 _description ~= p.description ~ " / "; 311 } 312 if (nf && !_name.empty) // cut off last comma 313 _name = _name[ 0 .. $ - 1 ]; 314 if (df && !_description.empty) // cut off last delimiter 315 _description = _description[ 0 .. $ - 3 ]; 316 } 317 _workspaceFile.setString("name", toUTF8(_name)); 318 _workspaceFile.setString("description", toUTF8(_description)); 319 Log.d("workspace name: ", _name); 320 Log.d("workspace description: ", _description); 321 Setting projects = _workspaceFile.objectByPath("projects", true); 322 projects.clear(SettingType.OBJECT); 323 foreach (Project p; _projects) { 324 if (p.isDependency) 325 continue; // don't save dependency 326 string pname = toUTF8(p.name); 327 string ppath = absoluteToRelativePath(p.filename); 328 projects[pname] = ppath; 329 } 330 if (!_workspaceFile.save(_filename, true)) { 331 Log.e("Cannot save workspace file"); 332 return false; 333 } 334 return true; 335 } 336 337 override bool load(string fname = null) { 338 if (fname.length > 0) 339 filename = fname; 340 if (!exists(_filename) || !isFile(_filename)) { 341 return false; 342 } 343 Log.d("Reading workspace from file ", _filename); 344 if (!_workspaceFile.load(_filename)) { 345 Log.e("Cannot read workspace file"); 346 return false; 347 } 348 _settings.load(filename ~ WORKSPACE_SETTINGS_EXTENSION); 349 _name = toUTF32(_workspaceFile["name"].str); 350 _description = toUTF32(_workspaceFile["description"].str); 351 Log.d("workspace name: ", _name); 352 Log.d("workspace description: ", _description); 353 if (_name.empty()) { 354 Log.e("empty workspace name"); 355 return false; 356 } 357 auto originalStartupProjectName = _settings.startupProjectName; 358 Setting projects = _workspaceFile.objectByPath("projects", true); 359 foreach(string key, Setting value; projects) { 360 string path = value.str; 361 Log.d("project: ", key, " path:", path); 362 if (!isAbsolute(path)) 363 path = buildNormalizedPath(_dir, path); //, "dub.json" 364 if (findProjectInWorkspace(path)) 365 continue; 366 Project project = new Project(this, path); 367 _projects ~= project; 368 project.load(); 369 } 370 _settings.startupProjectName = originalStartupProjectName; 371 fillStartupProject(); 372 return true; 373 } 374 void close() { 375 } 376 377 void refresh() { 378 foreach (Project p; _projects) { 379 p.refresh(); 380 } 381 } 382 } 383 384 /// global workspace 385 __gshared Workspace currentWorkspace;