1 module dlangide.ui.newfile;
2 
3 import dlangui.core.types;
4 import dlangui.core.i18n;
5 import dlangui.platforms.common.platform;
6 import dlangui.dialogs.dialog;
7 import dlangui.dialogs.filedlg;
8 import dlangui.widgets.widget;
9 import dlangui.widgets.layouts;
10 import dlangui.widgets.editors;
11 import dlangui.widgets.controls;
12 import dlangui.widgets.lists;
13 import dlangui.dml.parser;
14 import dlangui.core.stdaction;
15 import dlangui.core.files;
16 import dlangide.workspace.project;
17 import dlangide.workspace.workspace;
18 import dlangide.ui.commands;
19 import dlangide.ui.frame;
20 
21 import std.algorithm : startsWith, endsWith, equal;
22 import std.array : empty;
23 import std.utf : toUTF32, toUTF8;
24 import std.file;
25 import std.path;
26 
27 class FileCreationResult {
28     Project project;
29     string filename;
30     this(Project project, string filename) {
31         this.project = project;
32         this.filename = filename;
33     }
34 }
35 
36 class NewFileDlg : Dialog {
37     IDEFrame _ide;
38     Project _project;
39     ProjectFolder _folder;
40     string[] _sourcePaths;
41     this(IDEFrame parent, Project currentProject, ProjectFolder folder) {
42         super(UIString("New source file"d), parent.window, 
43               DialogFlag.Modal | DialogFlag.Resizable | DialogFlag.Popup, 500, 400);
44         _ide = parent;
45         _icon = "dlangui-logo1";
46         this._project = currentProject;
47         this._folder = folder;
48         _location = folder ? folder.filename : currentProject.dir;
49         _sourcePaths = currentProject.sourcePaths;
50         if (_sourcePaths.length)
51             _location = _sourcePaths[0];
52         if (folder)
53             _location = folder.filename;
54     }
55     /// override to implement creation of dialog controls
56     override void initialize() {
57         super.initialize();
58         initTemplates();
59         Widget content;
60         try {
61             content = parseML(q{
62                     VerticalLayout {
63                         id: vlayout
64                         padding: Rect { 5, 5, 5, 5 }
65                         layoutWidth: fill; layoutHeight: fill
66                         HorizontalLayout {
67                             layoutWidth: fill; layoutHeight: fill
68                             VerticalLayout {
69                                 margins: 5
70                                 layoutWidth: 50%; layoutHeight: fill
71                                 TextWidget { text: "Project template" }
72                                 StringListWidget { 
73                                     id: projectTemplateList 
74                                     layoutWidth: wrap; layoutHeight: fill
75                                 }
76                             }
77                             VerticalLayout {
78                                 margins: 5
79                                 layoutWidth: 50%; layoutHeight: fill
80                                 TextWidget { text: "Template description" }
81                                 EditBox { 
82                                     id: templateDescription; readOnly: true 
83                                     layoutWidth: fill; layoutHeight: fill
84                                 }
85                             }
86                         }
87                         TableLayout {
88                             margins: 5
89                             colCount: 2
90                             layoutWidth: fill; layoutHeight: wrap
91                             TextWidget { text: "Name" }
92                             EditLine { id: edName; text: "newfile"; layoutWidth: fill }
93                             TextWidget { text: "Location" }
94                             DirEditLine { id: edLocation; layoutWidth: fill }
95                             TextWidget { text: "Module name" }
96                             EditLine { id: edModuleName; text: ""; layoutWidth: fill; readOnly: true }
97                             TextWidget { text: "File path" }
98                             EditLine { id: edFilePath; text: ""; layoutWidth: fill; readOnly: true }
99                         }
100                         TextWidget { id: statusText; text: ""; layoutWidth: fill; textColor: #FF0000 }
101                     }
102                 });
103         } catch (Exception e) {
104             Log.e("Exceptin while parsing DML", e);
105             throw e;
106         }
107 
108 
109         _projectTemplateList = content.childById!StringListWidget("projectTemplateList");
110         _templateDescription = content.childById!EditBox("templateDescription");
111         _edFileName = content.childById!EditLine("edName");
112         _edFilePath = content.childById!EditLine("edFilePath");
113         _edModuleName = content.childById!EditLine("edModuleName");
114         _edLocation = content.childById!DirEditLine("edLocation");
115         _edLocation.text = toUTF32(_location);
116         _statusText = content.childById!TextWidget("statusText");
117 
118         _edLocation.filetypeIcons[".d"] = "text-d";
119         _edLocation.filetypeIcons["dub.json"] = "project-d";
120         _edLocation.filetypeIcons["package.json"] = "project-d";
121         _edLocation.filetypeIcons[".dlangidews"] = "project-development";
122         _edLocation.addFilter(FileFilterEntry(UIString("DlangIDE files"d), "*.dlangidews;*.d;*.dd;*.di;*.ddoc;*.dh;*.json;*.xml;*.ini"));
123         _edLocation.caption = "Select directory"d;
124 
125         // fill templates
126         dstring[] names;
127         foreach(t; _templates)
128             names ~= t.name;
129         _projectTemplateList.items = names;
130         _projectTemplateList.selectedItemIndex = 0;
131 
132         templateSelected(0);
133 
134         // listeners
135         _edLocation.contentChange = delegate (EditableContent source) {
136             _location = toUTF8(source.text);
137             validate();
138         };
139 
140         _edFileName.contentChange = delegate (EditableContent source) {
141             _fileName = toUTF8(source.text);
142             validate();
143         };
144 
145         _projectTemplateList.itemSelected = delegate (Widget source, int itemIndex) {
146             templateSelected(itemIndex);
147             return true;
148         };
149         _projectTemplateList.itemClick = delegate (Widget source, int itemIndex) {
150             templateSelected(itemIndex);
151             return true;
152         };
153 
154         addChild(content);
155         addChild(createButtonsPanel([ACTION_FILE_NEW_SOURCE_FILE, ACTION_CANCEL], 0, 0));
156 
157     }
158 
159     StringListWidget _projectTemplateList;
160     EditBox _templateDescription;
161     DirEditLine _edLocation;
162     EditLine _edFileName;
163     EditLine _edModuleName;
164     EditLine _edFilePath;
165     TextWidget _statusText;
166 
167     string _fileName = "newfile";
168     string _location;
169     string _moduleName;
170     string _packageName;
171     string _fullPathName;
172 
173     int _currentTemplateIndex = -1;
174     ProjectTemplate _currentTemplate;
175     ProjectTemplate[] _templates;
176 
177     static bool isSubdirOf(string path, string basePath) {
178         if (path.equal(basePath))
179             return true;
180         if (path.length > basePath.length + 1 && path.startsWith(basePath)) {
181             char ch = path[basePath.length];
182             return ch == '/' || ch == '\\';
183         }
184         return false;
185     }
186 
187     bool findSource(string path, ref string sourceFolderPath, ref string relativePath) {
188         foreach(dir; _sourcePaths) {
189             if (isSubdirOf(path, dir)) {
190                 sourceFolderPath = dir;
191                 relativePath = path[sourceFolderPath.length .. $];
192                 if (relativePath.length > 0 && (relativePath[0] == '\\' || relativePath[0] == '/'))
193                     relativePath = relativePath[1 .. $];
194                 return true;
195             }
196         }
197         return false;
198     }
199 
200     bool setError(dstring msg) {
201         _statusText.text = msg;
202         return msg.empty;
203     }
204 
205     bool validate() {
206         string filename = _fileName;
207         string fullFileName = filename;
208         if (!_currentTemplate.fileExtension.empty && filename.endsWith(_currentTemplate.fileExtension))
209             filename = filename[0 .. $ - _currentTemplate.fileExtension.length];
210         else
211             fullFileName = fullFileName ~ _currentTemplate.fileExtension;
212         _fullPathName = buildNormalizedPath(_location, fullFileName);
213         _edFilePath.text = toUTF32(_fullPathName);
214         if (!isValidFileName(filename))
215             return setError("Invalid file name");
216         if (!exists(_location) || !isDir(_location))
217             return setError("Location directory does not exist");
218 
219         if (_currentTemplate.isModule) {
220             string sourcePath, relativePath;
221             if (!findSource(_location, sourcePath, relativePath))
222                 return setError("Location is outside of source path");
223             if (!isValidModuleName(filename))
224                 return setError("Invalid file name");
225             _moduleName = filename;
226             char[] buf;
227             foreach(ch; relativePath) {
228                 if (ch == '/' || ch == '\\')
229                     buf ~= '.';
230                 else
231                     buf ~= ch;
232             }
233             _packageName = buf.dup;
234             string m = !_packageName.empty ? _packageName ~ '.' ~ _moduleName : _moduleName;
235             _edModuleName.text = toUTF32(m);
236             _packageName = m;
237         } else {
238             string projectPath = _project.dir;
239             if (!isSubdirOf(_location, projectPath))
240                 return setError("Location is outside of project path");
241             _edModuleName.text = "";
242             _moduleName = "";
243             _packageName = "";
244         }
245         return true;
246     }
247 
248     private FileCreationResult _result;
249     bool createItem() {
250         try {
251             if (_currentTemplate.isModule) {
252                 string txt = "module " ~ _packageName ~ ";\n\n" ~ _currentTemplate.srccode;
253                 write(_fullPathName, txt);
254             } else {
255                 write(_fullPathName, _currentTemplate.srccode);
256             }
257         } catch (Exception e) {
258             Log.e("Cannot create file", e);
259             return setError("Cannot create file");
260         }
261         _result = new FileCreationResult(_project, _fullPathName);
262         return true;
263     }
264 
265     override void close(const Action action) {
266         Action newaction = action.clone();
267         if (action.id == IDEActions.FileNew) {
268             if (!validate()) {
269                 window.showMessageBox(UIString("Error"d), UIString("Invalid parameters"));
270                 return;
271             }
272             if (!createItem()) {
273                 window.showMessageBox(UIString("Error"d), UIString("Failed to create project item"));
274                 return;
275             }
276             newaction.objectParam = _result;
277         }
278         super.close(newaction);
279     }
280 
281     protected void templateSelected(int index) {
282         if (_currentTemplateIndex == index)
283             return;
284         _currentTemplateIndex = index;
285         _currentTemplate = _templates[index];
286         _templateDescription.text = _currentTemplate.description;
287         //updateDirLayout();
288         validate();
289     }
290 
291     void initTemplates() {
292         _templates ~= new ProjectTemplate("Empty module"d, "Empty D module file."d, ".d",
293                     "\n", true);
294         _templates ~= new ProjectTemplate("Text file"d, "Empty text file."d, ".txt",
295                     "\n", true);
296         _templates ~= new ProjectTemplate("JSON file"d, "Empty json file."d, ".json",
297                     "{\n}\n", true);
298     }
299 }
300 
301 class ProjectTemplate {
302     dstring name;
303     dstring description;
304     string fileExtension;
305     string srccode;
306     bool isModule;
307     this(dstring name, dstring description, string fileExtension, string srccode, bool isModule) {
308         this.name = name;
309         this.description = description;
310         this.fileExtension = fileExtension;
311         this.srccode = srccode;
312         this.isModule = isModule;
313     }
314 }
315