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