new file creation

This commit is contained in:
Vadim Lopatin 2015-12-08 15:57:37 +03:00
parent 795be1b3e7
commit e9bde6e562
7 changed files with 478 additions and 7 deletions

View File

@ -446,6 +446,7 @@
<File path="src\dlangide\ui\dsourceedit.d" />
<File path="src\dlangide\ui\frame.d" />
<File path="src\dlangide\ui\homescreen.d" />
<File path="src\dlangide\ui\newfile.d" />
<File path="src\dlangide\ui\newproject.d" />
<File path="src\dlangide\ui\outputpanel.d" />
<File path="src\dlangide\ui\searchPanel.d" />

View File

@ -37,7 +37,7 @@ enum IDEActions : int {
WindowCloseAllDocuments,
CreateNewWorkspace,
AddToCurrentWorkspace,
ProjectFolderAddItem,
//ProjectFolderAddItem,
ProjectFolderRemoveItem,
ProjectFolderOpenItem,
ProjectFolderRenameItem,
@ -53,7 +53,7 @@ __gshared static this() {
}
const Action ACTION_PROJECT_FOLDER_ADD_ITEM = new Action(IDEActions.ProjectFolderAddItem, "MENU_PROJECT_FOLDER_ADD_ITEM"c);
//const Action ACTION_PROJECT_FOLDER_ADD_ITEM = new Action(IDEActions.ProjectFolderAddItem, "MENU_PROJECT_FOLDER_ADD_ITEM"c);
const Action ACTION_PROJECT_FOLDER_OPEN_ITEM = new Action(IDEActions.ProjectFolderOpenItem, "MENU_PROJECT_FOLDER_OPEN_ITEM"c);
const Action ACTION_PROJECT_FOLDER_REMOVE_ITEM = new Action(IDEActions.ProjectFolderRemoveItem, "MENU_PROJECT_FOLDER_REMOVE_ITEM"c);
const Action ACTION_PROJECT_FOLDER_RENAME_ITEM = new Action(IDEActions.ProjectFolderRenameItem, "MENU_PROJECT_FOLDER_RENAME_ITEM"c);

View File

@ -20,6 +20,7 @@ import dlangui.core.files;
import dlangide.ui.commands;
import dlangide.ui.wspanel;
import dlangide.ui.outputpanel;
import dlangide.ui.newfile;
import dlangide.ui.newproject;
import dlangide.ui.dsourceedit;
import dlangide.ui.homescreen;
@ -332,6 +333,7 @@ class IDEFrame : AppFrame {
// Create workspace docked panel
_wsPanel = new WorkspacePanel("workspace");
_wsPanel.sourceFileSelectionListener = &onSourceFileSelected;
_wsPanel.workspaceActionListener = &handleAction;
_wsPanel.dockAlignment = DockAlignment.Left;
_dockHost.addDockedWindow(_wsPanel);
@ -633,6 +635,9 @@ class IDEFrame : AppFrame {
case IDEActions.FileNewProject:
createNewProject(false);
return true;
case IDEActions.FileNew:
addProjectItem(a.objectParam);
return true;
default:
return super.handleAction(a);
}
@ -640,6 +645,54 @@ class IDEFrame : AppFrame {
return false;
}
@property ProjectSourceFile currentEditorSourceFile() {
TabItem tab = _tabs.selectedTab;
if (tab) {
return cast(ProjectSourceFile)tab.objectParam;
}
return null;
}
void addProjectItem(const Object obj) {
if (currentWorkspace is null)
return;
Project project;
ProjectFolder folder;
if (cast(Project)obj) {
project = cast(Project)obj;
} else if (cast(ProjectFolder)obj) {
folder = cast(ProjectFolder)obj;
project = folder.project;
} else if (cast(ProjectSourceFile)obj) {
ProjectSourceFile srcfile = cast(ProjectSourceFile)obj;
folder = cast(ProjectFolder)srcfile.parent;
project = srcfile.project;
} else {
ProjectSourceFile srcfile = currentEditorSourceFile;
if (srcfile) {
folder = cast(ProjectFolder)srcfile.parent;
project = srcfile.project;
}
}
if (project && folder && project.workspace is currentWorkspace) {
NewFileDlg dlg = new NewFileDlg(this, project, folder);
dlg.dialogResult = delegate(Dialog dlg, const Action result) {
if (result.id == ACTION_FILE_NEW_SOURCE_FILE.id) {
FileCreationResult res = cast(FileCreationResult)result.objectParam;
if (res) {
//res.project.reload();
res.project.refresh();
refreshWorkspace();
if (isSupportedSourceTextFileFormat(res.filename)) {
openSourceFile(res.filename, null, true);
}
}
}
};
dlg.show();
}
}
void createNewProject(bool newWorkspace) {
if (currentWorkspace is null)
newWorkspace = true;

313
src/dlangide/ui/newfile.d Normal file
View File

@ -0,0 +1,313 @@
module dlangide.ui.newfile;
import dlangui.core.types;
import dlangui.core.i18n;
import dlangui.platforms.common.platform;
import dlangui.dialogs.dialog;
import dlangui.dialogs.filedlg;
import dlangui.widgets.widget;
import dlangui.widgets.layouts;
import dlangui.widgets.editors;
import dlangui.widgets.controls;
import dlangui.widgets.lists;
import dlangui.dml.parser;
import dlangui.core.stdaction;
import dlangui.core.files;
import dlangide.workspace.project;
import dlangide.workspace.workspace;
import dlangide.ui.commands;
import dlangide.ui.frame;
import std.path;
import std.file;
import std.array : empty;
import std.algorithm : startsWith, endsWith;
class FileCreationResult {
Project project;
string filename;
this(Project project, string filename) {
this.project = project;
this.filename = filename;
}
}
class NewFileDlg : Dialog {
IDEFrame _ide;
Project _project;
ProjectFolder _folder;
string[] _sourcePaths;
this(IDEFrame parent, Project currentProject, ProjectFolder folder) {
super(UIString("New source file"d), parent.window,
DialogFlag.Modal | DialogFlag.Resizable | DialogFlag.Popup, 500, 400);
_ide = parent;
_icon = "dlangui-logo1";
this._project = currentProject;
this._folder = folder;
_location = folder ? folder.filename : currentProject.dir;
_sourcePaths = currentProject.sourcePaths;
if (_sourcePaths.length)
_location = _sourcePaths[0];
if (folder)
_location = folder.filename;
}
/// override to implement creation of dialog controls
override void init() {
super.init();
initTemplates();
Widget content;
try {
content = parseML(q{
VerticalLayout {
id: vlayout
padding: Rect { 5, 5, 5, 5 }
layoutWidth: fill; layoutHeight: fill
HorizontalLayout {
layoutWidth: fill; layoutHeight: fill
VerticalLayout {
margins: 5
layoutWidth: wrap; layoutHeight: fill
TextWidget { text: "Project template" }
StringListWidget {
id: projectTemplateList
layoutWidth: wrap; layoutHeight: fill
}
}
VerticalLayout {
margins: 5
layoutWidth: fill; layoutHeight: fill
TextWidget { text: "Template description" }
EditBox {
id: templateDescription; readOnly: true
layoutWidth: fill; layoutHeight: fill
}
}
}
TableLayout {
margins: 5
colCount: 2
layoutWidth: fill; layoutHeight: wrap
TextWidget { text: "Name" }
EditLine { id: edName; text: "newfile"; layoutWidth: fill }
TextWidget { text: "Location" }
DirEditLine { id: edLocation; layoutWidth: fill }
TextWidget { text: "Module name" }
EditLine { id: edModuleName; text: ""; layoutWidth: fill; readOnly: true }
TextWidget { text: "File path" }
EditLine { id: edFilePath; text: ""; layoutWidth: fill; readOnly: true }
}
TextWidget { id: statusText; text: ""; layoutWidth: fill; textColor: #FF0000 }
}
});
} catch (Exception e) {
Log.e("Exceptin while parsing DML", e);
throw e;
}
_projectTemplateList = content.childById!StringListWidget("projectTemplateList");
_templateDescription = content.childById!EditBox("templateDescription");
_edFileName = content.childById!EditLine("edName");
_edFilePath = content.childById!EditLine("edFilePath");
_edModuleName = content.childById!EditLine("edModuleName");
_edLocation = content.childById!DirEditLine("edLocation");
_edLocation.text = toUTF32(_location);
_statusText = content.childById!TextWidget("statusText");
_edLocation.filetypeIcons[".d"] = "text-d";
_edLocation.filetypeIcons["dub.json"] = "project-d";
_edLocation.filetypeIcons["package.json"] = "project-d";
_edLocation.filetypeIcons[".dlangidews"] = "project-development";
_edLocation.addFilter(FileFilterEntry(UIString("DlangIDE files"d), "*.dlangidews;*.d;*.dd;*.di;*.ddoc;*.dh;*.json;*.xml;*.ini"));
_edLocation.caption = "Select directory"d;
// fill templates
dstring[] names;
foreach(t; _templates)
names ~= t.name;
_projectTemplateList.items = names;
_projectTemplateList.selectedItemIndex = 0;
templateSelected(0);
// listeners
_edLocation.contentChange = delegate (EditableContent source) {
_location = toUTF8(source.text);
validate();
};
_edFileName.contentChange = delegate (EditableContent source) {
_fileName = toUTF8(source.text);
validate();
};
_projectTemplateList.itemSelected = delegate (Widget source, int itemIndex) {
templateSelected(itemIndex);
return true;
};
_projectTemplateList.itemClick = delegate (Widget source, int itemIndex) {
templateSelected(itemIndex);
return true;
};
addChild(content);
addChild(createButtonsPanel([ACTION_FILE_NEW_SOURCE_FILE, ACTION_CANCEL], 0, 0));
}
StringListWidget _projectTemplateList;
EditBox _templateDescription;
DirEditLine _edLocation;
EditLine _edFileName;
EditLine _edModuleName;
EditLine _edFilePath;
TextWidget _statusText;
string _fileName = "newfile";
string _location;
string _moduleName;
string _packageName;
string _fullPathName;
int _currentTemplateIndex = -1;
ProjectTemplate _currentTemplate;
ProjectTemplate[] _templates;
static bool isSubdirOf(string path, string basePath) {
if (path.equal(basePath))
return true;
if (path.length > basePath.length + 1 && path.startsWith(basePath)) {
char ch = path[basePath.length];
return ch == '/' || ch == '\\';
}
return false;
}
bool findSource(string path, ref string sourceFolderPath, ref string relativePath) {
foreach(dir; _sourcePaths) {
if (isSubdirOf(path, dir)) {
sourceFolderPath = dir;
relativePath = path[sourceFolderPath.length .. $];
if (relativePath.length > 0 && (relativePath[0] == '\\' || relativePath[0] == '/'))
relativePath = relativePath[1 .. $];
return true;
}
}
return false;
}
bool setError(dstring msg) {
_statusText.text = msg;
return msg.empty;
}
bool validate() {
string filename = _fileName;
string fullFileName = filename;
if (!_currentTemplate.fileExtension.empty && filename.endsWith(_currentTemplate.fileExtension))
filename = filename[0 .. $ - _currentTemplate.fileExtension.length];
else
fullFileName = fullFileName ~ _currentTemplate.fileExtension;
_fullPathName = buildNormalizedPath(_location, fullFileName);
_edFilePath.text = toUTF32(_fullPathName);
if (!isValidFileName(filename))
return setError("Invalid file name");
if (!exists(_location) || !isDir(_location))
return setError("Location directory does not exist");
if (_currentTemplate.isModule) {
string sourcePath, relativePath;
if (!findSource(_location, sourcePath, relativePath))
return setError("Location is outside of source path");
if (!isValidModuleName(filename))
return setError("Invalid file name");
_moduleName = filename;
char[] buf;
foreach(ch; relativePath) {
if (ch == '/' || ch == '\\')
buf ~= '.';
else
buf ~= ch;
}
_packageName = buf.dup;
string m = !_packageName.empty ? _packageName ~ '.' ~ _moduleName : _moduleName;
_edModuleName.text = toUTF32(m);
} else {
string projectPath = _project.dir;
if (!isSubdirOf(_location, projectPath))
return setError("Location is outside of project path");
_edModuleName.text = "";
_moduleName = "";
_packageName = "";
}
return true;
}
private FileCreationResult _result;
bool createItem() {
try {
if (_currentTemplate.isModule) {
string txt = "module " ~ _packageName ~ ";\n\n" ~ _currentTemplate.srccode;
write(_fullPathName, txt);
} else {
write(_fullPathName, _currentTemplate.srccode);
}
} catch (Exception e) {
Log.e("Cannot create file", e);
return setError("Cannot create file");
}
_result = new FileCreationResult(_project, _fullPathName);
return true;
}
override void close(const Action action) {
Action newaction = action.clone();
if (action.id == IDEActions.FileNew) {
if (!validate()) {
window.showMessageBox(UIString("Error"d), UIString("Invalid parameters"));
return;
}
if (!createItem()) {
window.showMessageBox(UIString("Error"d), UIString("Failed to create project item"));
return;
}
newaction.objectParam = _result;
}
super.close(newaction);
}
protected void templateSelected(int index) {
if (_currentTemplateIndex == index)
return;
_currentTemplateIndex = index;
_currentTemplate = _templates[index];
_templateDescription.text = _currentTemplate.description;
//updateDirLayout();
validate();
}
void initTemplates() {
_templates ~= new ProjectTemplate("Empty module"d, "Empty D module file."d, ".d",
"\n", true);
_templates ~= new ProjectTemplate("Text file"d, "Empty text file."d, ".txt",
"\n", true);
_templates ~= new ProjectTemplate("JSON file"d, "Empty json file."d, ".json",
"{\n}\n", true);
}
}
class ProjectTemplate {
dstring name;
dstring description;
string fileExtension;
string srccode;
bool isModule;
this(dstring name, dstring description, string fileExtension, string srccode, bool isModule) {
this.name = name;
this.description = description;
this.fileExtension = fileExtension;
this.srccode = srccode;
this.isModule = isModule;
}
}

View File

@ -284,6 +284,10 @@ class NewProjectDlg : Dialog {
if (!exists(_location) || !isDir(_location)) {
return setError("Invalid location");
}
if (!isValidProjectName(_projectName))
return setError("Invalid project name");
if (!isValidProjectName(_workspaceName))
return setError("Invalid workspace name");
return setError("");
}

View File

@ -17,12 +17,17 @@ interface SourceFileSelectionHandler {
bool onSourceFileSelected(ProjectSourceFile file, bool activate);
}
interface WorkspaceActionHandler {
bool onWorkspaceAction(const Action a);
}
class WorkspacePanel : DockWindow {
protected Workspace _workspace;
protected TreeWidget _tree;
/// handle source file selection change
Signal!SourceFileSelectionHandler sourceFileSelectionListener;
Signal!WorkspaceActionHandler workspaceActionListener;
this(string id) {
super(id);
@ -70,17 +75,17 @@ class WorkspacePanel : DockWindow {
_tree.popupMenu = &onTreeItemPopupMenu;
_workspacePopupMenu = new MenuItem();
_workspacePopupMenu.add(ACTION_PROJECT_FOLDER_ADD_ITEM);
_workspacePopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE.clone());
_projectPopupMenu = new MenuItem();
_projectPopupMenu.add(ACTION_PROJECT_FOLDER_ADD_ITEM, ACTION_PROJECT_FOLDER_OPEN_ITEM,
_projectPopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_PROJECT_FOLDER_OPEN_ITEM,
ACTION_PROJECT_FOLDER_REMOVE_ITEM);
_folderPopupMenu = new MenuItem();
_folderPopupMenu.add(ACTION_PROJECT_FOLDER_ADD_ITEM, ACTION_PROJECT_FOLDER_OPEN_ITEM,
_folderPopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_PROJECT_FOLDER_OPEN_ITEM,
ACTION_PROJECT_FOLDER_REMOVE_ITEM, ACTION_PROJECT_FOLDER_RENAME_ITEM);
_filePopupMenu = new MenuItem();
_filePopupMenu.add(ACTION_PROJECT_FOLDER_ADD_ITEM, ACTION_PROJECT_FOLDER_OPEN_ITEM,
_filePopupMenu.add(ACTION_FILE_NEW_SOURCE_FILE, ACTION_PROJECT_FOLDER_OPEN_ITEM,
ACTION_PROJECT_FOLDER_REMOVE_ITEM, ACTION_PROJECT_FOLDER_RENAME_ITEM);
return _tree;
}
@ -108,7 +113,15 @@ class WorkspacePanel : DockWindow {
menu = _workspacePopupMenu;
}
if (menu && menu.subitemCount) {
menu.onMenuItem = &onPopupMenuItem;
for (int i = 0; i < menu.subitemCount; i++) {
Action a = menu.subitem(i).action.clone();
a.objectParam = selectedItem.objectParam;
menu.subitem(i).action = a;
//menu.subitem(i).menuItemAction = &handleAction;
}
//menu.onMenuItem = &onPopupMenuItem;
//menu.menuItemClick = &onPopupMenuItem;
menu.menuItemAction = &handleAction;
menu.updateActionState(this);
return menu;
}
@ -161,4 +174,11 @@ class WorkspacePanel : DockWindow {
_workspace = w;
reloadItems();
}
/// override to handle specific actions
override bool handleAction(const Action a) {
if (workspaceActionListener.assigned)
return workspaceActionListener(a);
return false;
}
}

View File

@ -10,6 +10,7 @@ import std.json;
import std.utf;
import std.algorithm;
import std.process;
import std.array;
/// return true if filename matches rules for workspace file names
bool isProjectFile(string filename) {
@ -74,6 +75,9 @@ class ProjectItem {
ProjectItem child(int index) {
return null;
}
void refresh() {
}
}
/// Project folder
@ -99,9 +103,30 @@ class ProjectFolder : ProjectItem {
item._parent = this;
item._project = _project;
}
ProjectItem childByPathName(string path) {
for (int i = 0; i < _children.count; i++) {
if (_children[i].filename.equal(path))
return _children[i];
}
return null;
}
ProjectItem childByName(dstring s) {
for (int i = 0; i < _children.count; i++) {
if (_children[i].name.equal(s))
return _children[i];
}
return null;
}
bool loadDir(string path) {
string src = relativeToAbsolutePath(path);
if (exists(src) && isDir(src)) {
ProjectFolder existing = cast(ProjectFolder)childByPathName(src);
if (existing) {
if (existing.isFolder)
existing.loadItems();
return true;
}
ProjectFolder dir = new ProjectFolder(src);
addChild(dir);
Log.d(" added project folder ", src);
@ -110,9 +135,13 @@ class ProjectFolder : ProjectItem {
}
return false;
}
bool loadFile(string path) {
string src = relativeToAbsolutePath(path);
if (exists(src) && isFile(src)) {
ProjectItem existing = childByPathName(src);
if (existing)
return true;
ProjectSourceFile f = new ProjectSourceFile(src);
addChild(f);
Log.d(" added project file ", src);
@ -120,21 +149,36 @@ class ProjectFolder : ProjectItem {
}
return false;
}
void loadItems() {
bool[string] loaded;
foreach(e; dirEntries(_filename, SpanMode.shallow)) {
string fn = baseName(e.name);
if (e.isDir) {
loadDir(fn);
loaded[fn] = true;
} else if (e.isFile) {
loadFile(fn);
loaded[fn] = true;
}
}
// removing non-reloaded items
for (int i = _children.count - 1; i >= 0; i--) {
if (!(toUTF8(_children[i].name) in loaded)) {
_children.remove(i);
}
}
}
string relativeToAbsolutePath(string path) {
if (isAbsolute(path))
return path;
return buildNormalizedPath(_filename, path);
}
override void refresh() {
loadItems();
}
}
/// Project source file
@ -408,6 +452,10 @@ class Project : WorkspaceItem {
return folder;
}
void refresh() {
_items.refresh();
}
void findMainSourceFile() {
string n = toUTF8(name);
string[] mainnames = ["app.d", "main.d", n ~ ".d"];
@ -575,3 +623,35 @@ class DubPackageFinder {
}
}
bool isValidProjectName(string s) {
if (s.empty)
return false;
for (int i = 0; i < s.length; i++) {
char ch = s[i];
if (ch != '_' && ch != '-' && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))
return false;
}
return true;
}
bool isValidModuleName(string s) {
if (s.empty)
return false;
for (int i = 0; i < s.length; i++) {
char ch = s[i];
if (ch != '_' && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))
return false;
}
return true;
}
bool isValidFileName(string s) {
if (s.empty)
return false;
for (int i = 0; i < s.length; i++) {
char ch = s[i];
if (ch != '_' && ch != '.' && ch != '-' && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'))
return false;
}
return true;
}