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