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