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