1 module dlangide.workspace.workspace;
2 
3 import dlangide.workspace.project;
4 import dlangide.ui.frame;
5 import dlangui.core.logger;
6 import std.conv;
7 import std.path;
8 import std.file;
9 import std.json;
10 import std.range;
11 import std.utf;
12 import std.algorithm;
13 
14 enum BuildOperation {
15     Build,
16     Clean,
17     Rebuild,
18     Run,
19     Upgrade
20 }
21 
22 enum BuildConfiguration {
23     Debug,
24     Release,
25     Unittest
26 }
27 
28 
29 /**
30     Exception thrown on Workspace errors
31 */
32 class WorkspaceException : Exception
33 {
34     this(string msg, string file = __FILE__, size_t line = __LINE__)
35     {
36         super(msg, file, line);
37     }
38 }
39 
40 immutable string WORKSPACE_EXTENSION = ".dlangidews";
41 
42 /// return true if filename matches rules for workspace file names
43 bool isWorkspaceFile(string filename) {
44     return filename.endsWith(WORKSPACE_EXTENSION);
45 }
46 
47 /// DlangIDE workspace
48 class Workspace : WorkspaceItem {
49     protected Project[] _projects;
50     
51     protected IDEFrame _frame;
52     protected BuildConfiguration _buildConfiguration;
53     protected ProjectConfiguration _projectConfiguration = ProjectConfiguration.DEFAULT;
54     
55     this(IDEFrame frame, string fname = null) {
56         super(fname);
57         _frame = frame;
58     }
59 
60     @property Project[] projects() {
61         return _projects;
62     }
63 
64     @property BuildConfiguration buildConfiguration() { return _buildConfiguration; }
65     @property void buildConfiguration(BuildConfiguration config) { _buildConfiguration = config; }
66 
67     @property ProjectConfiguration projectConfiguration() { return _projectConfiguration; }
68     @property void projectConfiguration(ProjectConfiguration config) { _projectConfiguration = config; }
69      
70     protected Project _startupProject;
71 
72     @property Project startupProject() { return _startupProject; }
73     @property void startupProject(Project project) { 
74         _startupProject = project;
75         _frame.setProjectConfigurations(project.configurations.keys.map!(k => k.to!dstring).array); 
76     }
77 
78     /// setups currrent project configuration by name
79     void setStartupProjectConfiguration(string conf)
80     {
81         if(_startupProject && conf in _startupProject.configurations) {
82             _projectConfiguration = _startupProject.configurations[conf];
83         }
84     }
85     
86     protected void fillStartupProject() {
87         if (!_startupProject && _projects.length)
88             startupProject = _projects[0];
89     }
90 
91     /// tries to find source file in one of projects, returns found project source file item, or null if not found
92 	ProjectSourceFile findSourceFileItem(string filename, bool fullFileName=true) {
93         foreach (Project p; _projects) {
94 			ProjectSourceFile res = p.findSourceFileItem(filename, fullFileName);
95             if (res)
96                 return res;
97         }
98         return null;
99     }
100 
101     /// find project in workspace by filename
102     Project findProject(string filename) {
103         foreach (Project p; _projects) {
104             if (p.filename.equal(filename))
105                 return p;
106         }
107         return null;
108     }
109 
110     void addProject(Project p) {
111         _projects ~= p;
112         p.workspace = this;
113         fillStartupProject();
114     }
115 
116     bool addDependencyProject(Project p) {
117         for (int i = 0; i < _projects.length; i++) {
118             if (_projects[i].filename.equal(p.filename)) {
119                 _projects[i] = p;
120                 return false;
121             }
122         }
123         addProject(p);
124         return true;
125     }
126 
127     string absoluteToRelativePath(string path) {
128         return toForwardSlashSeparator(relativePath(path, _dir));
129     }
130 
131 
132     override bool save(string fname = null) {
133         if (fname.length > 0)
134             filename = fname;
135         try {
136             JSONValue content;
137             JSONValue[string] json;
138             json["name"] = JSONValue(toUTF8(_name));
139             json["description"] = JSONValue(toUTF8(_description));
140             JSONValue[string] projects;
141             foreach (Project p; _projects) {
142                 if (p.isDependency)
143                     continue; // don't save dependency
144                 string pname = toUTF8(p.name);
145                 string ppath = absoluteToRelativePath(p.filename);
146                 projects[pname] = JSONValue(ppath);
147             }
148             json["projects"] = projects;
149             content = json;
150             string js = content.toPrettyString;
151             write(_filename, js);
152         } catch (JSONException e) {
153             Log.e("Cannot parse json", e);
154             return false;
155         } catch (Exception e) {
156             Log.e("Cannot read workspace file", e);
157             return false;
158         }
159         return true;
160     }
161 
162     override bool load(string fname = null) {
163         if (fname.length > 0)
164             filename = fname;
165         if (!exists(_filename) || !isFile(_filename))  {
166             return false;
167         }
168         Log.d("Reading workspace from file ", _filename);
169 
170         try {
171             string jsonSource = readText!string(_filename);
172             JSONValue json = parseJSON(jsonSource);
173             _name = toUTF32(json["name"].str);
174             _description = toUTF32(json["description"].str);
175             Log.d("workspace name: ", _name);
176             Log.d("workspace description: ", _description);
177             JSONValue projects = json["projects"];
178             foreach(string key, ref JSONValue value; projects) {
179                 string path = value.str;
180                 Log.d("project: ", key, " path:", path);
181                 if (!isAbsolute(path))
182                     path = buildNormalizedPath(_dir, path); //, "dub.json"
183                 Project project = new Project(this, path);
184                 _projects ~= project;
185                 project.load();
186 
187             }
188             string js = json.toPrettyString;
189             write(_filename, js);
190         } catch (JSONException e) {
191             Log.e("Cannot parse json", e);
192             return false;
193         } catch (Exception e) {
194             Log.e("Cannot read workspace file", e);
195             return false;
196         }
197         fillStartupProject();
198         return true;
199     }
200     void close() {
201     }
202 
203     void refresh() {
204         foreach (Project p; _projects) {
205             p.refresh();
206         }
207     }
208 }
209 
210 /// global workspace
211 __gshared Workspace currentWorkspace;