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 protected BuildConfiguration _buildConfiguration; 60 protected ProjectConfiguration _projectConfiguration = ProjectConfiguration.DEFAULT; 61 62 this(IDEFrame frame, string fname = WORKSPACE_EXTENSION) { 63 super(fname); 64 _workspaceFile = new SettingsFile(fname); 65 _settings = new WorkspaceSettings(fname ? fname ~ WORKSPACE_SETTINGS_EXTENSION : null); 66 _frame = frame; 67 } 68 69 ProjectSourceFile findSourceFile(string projectFileName, string fullFileName) { 70 foreach(p; _projects) { 71 ProjectSourceFile res = p.findSourceFile(projectFileName, fullFileName); 72 if (res) 73 return res; 74 } 75 return null; 76 } 77 78 @property Setting includePath(){ 79 Setting res = _workspaceFile.objectByPath("includePath", true); 80 return res; 81 } 82 83 @property Project[] projects() { return _projects; } 84 85 @property BuildConfiguration buildConfiguration() { return _buildConfiguration; } 86 @property void buildConfiguration(BuildConfiguration config) { _buildConfiguration = config; } 87 88 @property ProjectConfiguration projectConfiguration() { return _projectConfiguration; } 89 @property void projectConfiguration(ProjectConfiguration config) { _projectConfiguration = config; } 90 91 protected Project _startupProject; 92 93 @property Project startupProject() { return _startupProject; } 94 @property void startupProject(Project project) { 95 _startupProject = project; 96 _frame.setProjectConfigurations(project.configurations.keys.map!(k => k.to!dstring).array); 97 _settings.startupProjectName = toUTF8(project.name); 98 } 99 100 /// setups currrent project configuration by name 101 void setStartupProjectConfiguration(string conf) 102 { 103 if(_startupProject && conf in _startupProject.configurations) { 104 _projectConfiguration = _startupProject.configurations[conf]; 105 } 106 } 107 108 private void updateBreakpointFiles(Breakpoint[] breakpoints) { 109 foreach(bp; breakpoints) { 110 Project project = findProjectByName(bp.projectName); 111 if (project) 112 bp.fullFilePath = project.relativeToAbsolutePath(bp.projectFilePath); 113 } 114 } 115 116 private void updateBookmarkFiles(EditorBookmark[] bookmarks) { 117 foreach(bp; bookmarks) { 118 Project project = findProjectByName(bp.projectName); 119 if (project) 120 bp.fullFilePath = project.relativeToAbsolutePath(bp.projectFilePath); 121 } 122 } 123 124 Breakpoint[] getSourceFileBreakpoints(ProjectSourceFile file) { 125 Breakpoint[] res = _settings.getProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath); 126 updateBreakpointFiles(res); 127 return res; 128 } 129 130 void setSourceFileBreakpoints(ProjectSourceFile file, Breakpoint[] breakpoints) { 131 _settings.setProjectBreakpoints(toUTF8(file.project.name), file.projectFilePath, breakpoints); 132 } 133 134 EditorBookmark[] getSourceFileBookmarks(ProjectSourceFile file) { 135 EditorBookmark[] res = _settings.getProjectBookmarks(toUTF8(file.project.name), file.projectFilePath); 136 updateBookmarkFiles(res); 137 return res; 138 } 139 140 void setSourceFileBookmarks(ProjectSourceFile file, EditorBookmark[] bookmarks) { 141 _settings.setProjectBookmarks(toUTF8(file.project.name), file.projectFilePath, bookmarks); 142 } 143 144 /// returns all workspace breakpoints 145 Breakpoint[] getBreakpoints() { 146 Breakpoint[] res = _settings.getBreakpoints(); 147 updateBreakpointFiles(res); 148 return res; 149 } 150 151 protected void fillStartupProject() { 152 string s = _settings.startupProjectName; 153 if ((!_startupProject || !_startupProject.name.toUTF8.equal(s)) && _projects.length) { 154 if (!s.empty) { 155 foreach(p; _projects) { 156 if (p.name.toUTF8.equal(s)) { 157 _startupProject = p; 158 } 159 } 160 } 161 if (!_startupProject) { 162 startupProject = _projects[0]; 163 } 164 } 165 } 166 167 /// tries to find source file in one of projects, returns found project source file item, or null if not found 168 ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true) { 169 foreach (Project p; _projects) { 170 ProjectSourceFile res = p.findSourceFileItem(filename, fullFileName); 171 if (res) 172 return res; 173 } 174 return null; 175 } 176 177 /// find project in workspace by filename 178 Project findProject(string filename) { 179 foreach (Project p; _projects) { 180 if (p.filename.equal(filename)) 181 return p; 182 } 183 return null; 184 } 185 186 /// find project in workspace by filename 187 Project findProjectByName(string name) { 188 foreach (Project p; _projects) { 189 if (p.name.toUTF8.equal(name)) 190 return p; 191 } 192 return null; 193 } 194 195 void addProject(Project p) { 196 _projects ~= p; 197 p.workspace = this; 198 fillStartupProject(); 199 } 200 201 Project removeProject(int index) { 202 if (index < 0 || index > _projects.length) 203 return null; 204 Project res = _projects[index]; 205 for (int j = index; j + 1 < _projects.length; j++) 206 _projects[j] = _projects[j + 1]; 207 return res; 208 } 209 210 bool isDependencyProjectUsed(string filename) { 211 foreach(p; _projects) 212 if (!p.isDependency && p.findDependencyProject(filename)) 213 return true; 214 return false; 215 } 216 217 void cleanupUnusedDependencies() { 218 for (int i = cast(int)_projects.length - 1; i >= 0; i--) { 219 if (_projects[i].isDependency) { 220 if (!isDependencyProjectUsed(_projects[i].filename)) 221 removeProject(i); 222 } 223 } 224 } 225 226 bool addDependencyProject(Project p) { 227 for (int i = 0; i < _projects.length; i++) { 228 if (_projects[i].filename.equal(p.filename)) { 229 _projects[i] = p; 230 return false; 231 } 232 } 233 addProject(p); 234 return true; 235 } 236 237 string absoluteToRelativePath(string path) { 238 return toForwardSlashSeparator(relativePath(path, _dir)); 239 } 240 241 override bool save(string fname = null) { 242 if (fname.length > 0) 243 filename = fname; 244 if (_filename.empty) // no file name specified 245 return false; 246 _settings.save(_filename ~ WORKSPACE_SETTINGS_EXTENSION); 247 // If name is null, then compose it from projects 248 // If description is null, then compose it from project's descriptions 249 immutable auto nf = _name.empty; 250 immutable auto df = _description.empty; 251 if (nf || df) 252 { 253 _name = nf ? "" : _name; 254 _description = df ? "" : _description; 255 foreach (Project p; _projects) { 256 if (p.isDependency) 257 continue; // don't add dependency 258 if (nf) 259 _name ~= p.name ~ ","; 260 if (df) 261 _description ~= p.description ~ " / "; 262 } 263 if (!_name.empty) // cut off last comma 264 _name = _name[ 0 .. $ - 1 ]; 265 if (!_description.empty) // cut off last delimiter 266 _description = _description[ 0 .. $ - 3 ]; 267 } 268 _workspaceFile.setString("name", toUTF8(_name)); 269 _workspaceFile.setString("description", toUTF8(_description)); 270 Log.d("workspace name: ", _name); 271 Log.d("workspace description: ", _description); 272 Setting projects = _workspaceFile.objectByPath("projects", true); 273 projects.clear(SettingType.OBJECT); 274 foreach (Project p; _projects) { 275 if (p.isDependency) 276 continue; // don't save dependency 277 string pname = toUTF8(p.name); 278 string ppath = absoluteToRelativePath(p.filename); 279 projects[pname] = ppath; 280 } 281 if (!_workspaceFile.save(_filename, true)) { 282 Log.e("Cannot save workspace file"); 283 return false; 284 } 285 return true; 286 } 287 288 override bool load(string fname = null) { 289 if (fname.length > 0) 290 filename = fname; 291 if (!exists(_filename) || !isFile(_filename)) { 292 return false; 293 } 294 Log.d("Reading workspace from file ", _filename); 295 if (!_workspaceFile.load(_filename)) { 296 Log.e("Cannot read workspace file"); 297 return false; 298 } 299 _settings.load(filename ~ WORKSPACE_SETTINGS_EXTENSION); 300 _name = toUTF32(_workspaceFile["name"].str); 301 _description = toUTF32(_workspaceFile["description"].str); 302 Log.d("workspace name: ", _name); 303 Log.d("workspace description: ", _description); 304 if (_name.empty()) { 305 Log.e("empty workspace name"); 306 return false; 307 } 308 auto originalStartupProjectName = _settings.startupProjectName; 309 Setting projects = _workspaceFile.objectByPath("projects", true); 310 foreach(string key, Setting value; projects) { 311 string path = value.str; 312 Log.d("project: ", key, " path:", path); 313 if (!isAbsolute(path)) 314 path = buildNormalizedPath(_dir, path); //, "dub.json" 315 Project project = new Project(this, path); 316 _projects ~= project; 317 project.load(); 318 } 319 _settings.startupProjectName = originalStartupProjectName; 320 fillStartupProject(); 321 return true; 322 } 323 void close() { 324 } 325 326 void refresh() { 327 foreach (Project p; _projects) { 328 p.refresh(); 329 } 330 } 331 } 332 333 /// global workspace 334 __gshared Workspace currentWorkspace;