diff --git a/README.md b/README.md
index dc2e729..93b6598 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,18 @@ Currently supported features:
* Build and run project with DUB
* Build log highlight and navigation to place of error or warning by clicking on log line
* DUB dependencies update
+* DUB package configuration selection (implemented by NCrashed)
+* Dependency projects are shown in workspace tree
Source editor features:
* D language source code syntax highlight (basic)
* Indent / unindent text with Tab and Shift+Tab or Ctrl+\[ and Ctrl+\]
* Toggle line or block comments by Ctrl+/ and Ctrl+Shift+/
-* Select word by mouse double click
* D source code autocompletion by Ctrl+Space or Ctrl+Shift+G (using DCD)
* D source code Go To Definition by Ctrl+G or F12 (using DCD)
+* D source code Smart Indents
+* Select word by mouse double click

@@ -28,6 +31,8 @@ GitHub page: [https://github.com/buggins/dlangide](https://github.com/buggins/dl
DlangUI project GitHub page: [https://github.com/buggins/dlangui](https://github.com/buggins/dlangui)
+[](https://gitter.im/buggins/dlangide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/buggins/dlangide) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=H2ADZV8S6TDHQ "Donate once-off to this project using Paypal")
+
DCD integration
===============
diff --git a/dlangide.visualdproj b/dlangide.visualdproj
index 734db22..51867a8 100644
--- a/dlangide.visualdproj
+++ b/dlangide.visualdproj
@@ -72,7 +72,7 @@
0
DebugInfo DCD
0
- Unicode USE_FREETYPE USE_LIBDPARSE USE_MAGO
+ Unicode USE_FREETYPE
0
3
0
diff --git a/src/ddebug/windows/mago.d b/src/ddebug/windows/mago.d
index e699369..4e5aa1b 100644
--- a/src/ddebug/windows/mago.d
+++ b/src/ddebug/windows/mago.d
@@ -79,6 +79,9 @@ void testMago() {
Log.e("OLE 2 failed to initialize");
return;
}
+
+ IDebugCoreServer2 coreServer = null;
+
//hr = CoCreateInstance(&CLSID_MAGO, null, CLSCTX_ALL, &IID_IDebugEngine2, cast(void**)&piUnknown);
hr = CoCreateInstance(&IID_MAGO_NATIVE_ENGINE, //CLSID_MAGO,
null,
@@ -118,10 +121,10 @@ void testMago() {
envblock ~= 0;
opts ~= 0;
-
+ IDebugPort2 port;
hr = debugEngineLaunch.LaunchSuspended (
null,
- null,
+ port,
exe.ptr,//LPCOLESTR
args.ptr,
dir.ptr,
diff --git a/src/dlangide/builders/builder.d b/src/dlangide/builders/builder.d
index 6f07359..0886a87 100644
--- a/src/dlangide/builders/builder.d
+++ b/src/dlangide/builders/builder.d
@@ -16,6 +16,7 @@ class Builder : BackgroundOperationWatcher {
protected ExternalProcess _extprocess;
protected OutputPanel _log;
protected ProtectedTextStorage _box;
+ protected ProjectConfiguration _projectConfig;
protected BuildConfiguration _buildConfig;
protected BuildOperation _buildOp;
protected bool _verbose;
@@ -23,8 +24,9 @@ class Builder : BackgroundOperationWatcher {
@property Project project() { return _project; }
@property void project(Project p) { _project = p; }
- this(AppFrame frame, Project project, OutputPanel log, BuildConfiguration buildConfig, BuildOperation buildOp, bool verbose) {
+ this(AppFrame frame, Project project, OutputPanel log, ProjectConfiguration projectConfig, BuildConfiguration buildConfig, BuildOperation buildOp, bool verbose) {
super(frame);
+ _projectConfig = projectConfig;
_buildConfig = buildConfig;
_buildOp = buildOp;
_verbose = verbose;
@@ -62,7 +64,11 @@ class Builder : BackgroundOperationWatcher {
} else if (_buildOp == BuildOperation.Clean) {
params ~= "clean".dup;
} else if (_buildOp == BuildOperation.Run) {
- params ~= "run".dup;
+ if (_projectConfig.type == ProjectConfiguration.Type.Library) {
+ params ~= "test".dup;
+ } else {
+ params ~= "run".dup;
+ }
} else if (_buildOp == BuildOperation.Upgrade) {
params ~= "upgrade".dup;
params ~= "--force-remove".dup;
@@ -83,6 +89,10 @@ class Builder : BackgroundOperationWatcher {
}
}
+ if(_projectConfig.name != ProjectConfiguration.DEFAULT_NAME) {
+ params ~= "--config=".dup ~ _projectConfig.name;
+ }
+
if (_verbose)
params ~= "-v".dup;
diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d
index d7fd6b6..f1b68ba 100644
--- a/src/dlangide/ui/commands.d
+++ b/src/dlangide/ui/commands.d
@@ -16,6 +16,7 @@ enum IDEActions : int {
FileClose,
FileExit,
EditPreferences,
+ ProjectConfigurations,
BuildConfigurations,
BuildWorkspace,
RebuildWorkspace,
@@ -68,6 +69,7 @@ const Action ACTION_FILE_EXIT = new Action(IDEActions.FileExit, "MENU_FILE_EXIT"
const Action ACTION_WORKSPACE_BUILD = new Action(IDEActions.BuildWorkspace, "MENU_BUILD_WORKSPACE_BUILD"c);
const Action ACTION_WORKSPACE_REBUILD = new Action(IDEActions.RebuildWorkspace, "MENU_BUILD_WORKSPACE_REBUILD"c);
const Action ACTION_WORKSPACE_CLEAN = new Action(IDEActions.CleanWorkspace, "MENU_BUILD_WORKSPACE_CLEAN"c);
+const Action ACTION_PROJECT_CONFIGURATIONS = new Action(IDEActions.ProjectConfigurations, "MENU_PROJECT_CONFIGURATIONS"c);
const Action ACTION_BUILD_CONFIGURATIONS = new Action(IDEActions.BuildConfigurations, "MENU_BUILD_CONFIGURATIONS"c);
const Action ACTION_PROJECT_BUILD = new Action(IDEActions.BuildProject, "MENU_BUILD_PROJECT_BUILD"c, "run-build", KeyCode.F7, 0);
const Action ACTION_PROJECT_REBUILD = new Action(IDEActions.RebuildProject, "MENU_BUILD_PROJECT_REBUILD"c, "run-build-clean", KeyCode.F7, KeyFlag.Control);
diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d
index 9655fc1..2f5e824 100644
--- a/src/dlangide/ui/dsourceedit.d
+++ b/src/dlangide/ui/dsourceedit.d
@@ -83,7 +83,7 @@ class DSourceEdit : SourceEdit {
/// returns project import paths - if file from project is opened in current editor
string[] importPaths() {
if (_projectSourceFile)
- return _projectSourceFile.project.sourcePaths ~ _projectSourceFile.project.builderSourcePaths;
+ return _projectSourceFile.project.importPaths;
return null;
}
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index 0168dd9..bbef2d0 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -57,9 +57,33 @@ class BackgroundOperationWatcherTest : BackgroundOperationWatcher {
}
}
+/// Subclass of toolbars that can update their items
+class UpdateableToolBarComboBox : ToolBarComboBox {
+ this(string id, dstring[] items) {
+ super(id, items);
+ }
+
+ @property items(dstring[] newItems) {
+ _adapter.items = newItems;
+ if(newItems.length > 0) {
+ selectedItemIndex = 0;
+ }
+ requestLayout();
+ }
+
+ @property dstring selectedItem() {
+ size_t index = _selectedItemIndex;
+ if(index < 0 || index >= _adapter.itemCount) return "";
+
+ return _adapter.items.get(index);
+ }
+}
+
/// DIDE app frame
class IDEFrame : AppFrame {
+ private UpdateableToolBarComboBox projectConfigurationCombo;
+
MenuItem mainMenuItems;
WorkspacePanel _wsPanel;
OutputPanel _logPanel;
@@ -439,6 +463,17 @@ class IDEFrame : AppFrame {
tb.addButtons(ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR);
tb.addButtons(ACTION_DEBUG_START);
+
+ projectConfigurationCombo = new UpdateableToolBarComboBox("projectConfig", [ProjectConfiguration.DEFAULT_NAME.to!dstring]);
+ projectConfigurationCombo.onItemClickListener = delegate(Widget source, int index) {
+ if (currentWorkspace) {
+ currentWorkspace.setStartupProjectConfiguration(projectConfigurationCombo.selectedItem.to!string);
+ }
+ return true;
+ };
+ projectConfigurationCombo.action = ACTION_PROJECT_CONFIGURATIONS;
+ tb.addControl(projectConfigurationCombo);
+
ToolBarComboBox cbBuildConfiguration = new ToolBarComboBox("buildConfig", ["Debug"d, "Release"d, "Unittest"d]);
cbBuildConfiguration.onItemClickListener = delegate(Widget source, int index) {
if (currentWorkspace && index < 3) {
@@ -596,9 +631,19 @@ class IDEFrame : AppFrame {
return false;
}
+ private bool loadProject(Project project) {
+ if (!project.load()) {
+ _logPanel.logLine("Cannot read project " ~ project.filename);
+ window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project "d ~ toUTF32(project.filename)));
+ return false;
+ }
+ _logPanel.logLine(toUTF32("Project file " ~ project.filename ~ " is opened ok"));
+ return true;
+ }
+
void openFileOrWorkspace(string filename) {
if (filename.isWorkspaceFile) {
- Workspace ws = new Workspace();
+ Workspace ws = new Workspace(this);
if (ws.load(filename)) {
askForUnsavedEdits(delegate() {
setWorkspace(ws);
@@ -610,13 +655,7 @@ class IDEFrame : AppFrame {
} else if (filename.isProjectFile) {
_logPanel.clear();
_logPanel.logLine("Trying to open project from " ~ filename);
- Project project = new Project();
- if (!project.load(filename)) {
- _logPanel.logLine("Cannot read project file " ~ filename);
- window.showMessageBox(UIString("Cannot open project"d), UIString("Error occured while opening project"d));
- return;
- }
- _logPanel.logLine("Project file is opened ok");
+ Project project = new Project(currentWorkspace, filename);
string defWsFile = project.defWorkspaceFile;
if (currentWorkspace) {
Project existing = currentWorkspace.findProject(project.filename);
@@ -633,6 +672,7 @@ class IDEFrame : AppFrame {
} else if (result.id == IDEActions.AddToCurrentWorkspace) {
// add to current
currentWorkspace.addProject(project);
+ loadProject(project);
currentWorkspace.save();
refreshWorkspace();
}
@@ -657,10 +697,11 @@ class IDEFrame : AppFrame {
string defWsFile = project.defWorkspaceFile;
_logPanel.logLine("Creating new workspace " ~ defWsFile);
// new ws
- Workspace ws = new Workspace();
+ Workspace ws = new Workspace(this);
ws.name = project.name;
ws.description = project.description;
ws.addProject(project);
+ loadProject(project);
ws.save(defWsFile);
setWorkspace(ws);
_logPanel.logLine("Done");
@@ -691,10 +732,15 @@ class IDEFrame : AppFrame {
_logPanel.logLine("No project is opened");
return;
}
- Builder op = new Builder(this, currentWorkspace.startupProject, _logPanel, currentWorkspace.buildConfiguration, buildOp, false);
+ Builder op = new Builder(this, currentWorkspace.startupProject, _logPanel, currentWorkspace.projectConfiguration, currentWorkspace.buildConfiguration, buildOp, false);
setBackgroundOperation(op);
}
-
+
+ /// updates list of available configurations
+ void setProjectConfigurations(dstring[] items) {
+ projectConfigurationCombo.items = items;
+ }
+
/// handle files dropped to application window
void onFilesDropped(string[] filenames) {
//Log.d("onFilesDropped(", filenames, ")");
diff --git a/src/dlangide/ui/wspanel.d b/src/dlangide/ui/wspanel.d
index 934d157..6f9311b 100644
--- a/src/dlangide/ui/wspanel.d
+++ b/src/dlangide/ui/wspanel.d
@@ -147,7 +147,7 @@ class WorkspacePanel : DockWindow {
TreeItem root = _tree.items.newChild(_workspace.filename, _workspace.name, "project-development");
root.intParam = ProjectItemType.Workspace;
foreach(project; _workspace.projects) {
- TreeItem p = root.newChild(project.filename, project.name, "project-d");
+ TreeItem p = root.newChild(project.filename, project.name, project.isDependency ? "project-d-dependency" : "project-d");
p.intParam = ProjectItemType.Project;
addProjectItems(p, project.items);
}
diff --git a/src/dlangide/workspace/project.d b/src/dlangide/workspace/project.d
index 19977c1..5728c43 100644
--- a/src/dlangide/workspace/project.d
+++ b/src/dlangide/workspace/project.d
@@ -9,6 +9,7 @@ import std.file;
import std.json;
import std.utf;
import std.algorithm;
+import std.process;
/// return true if filename matches rules for workspace file names
bool isProjectFile(string filename) {
@@ -223,6 +224,57 @@ string[] dmdSourcePaths() {
return res;
}
+/// Stores info about project configuration
+struct ProjectConfiguration {
+ /// name used to build the project
+ string name;
+ /// type, for libraries one can run tests, for apps - execute them
+ Type type;
+
+ /// How to display default configuration in ui
+ immutable static string DEFAULT_NAME = "default";
+ /// Default project configuration
+ immutable static ProjectConfiguration DEFAULT = ProjectConfiguration(DEFAULT_NAME, Type.Default);
+
+ /// Type of configuration
+ enum Type {
+ Default,
+ Executable,
+ Library
+ }
+
+ private static Type parseType(string s)
+ {
+ switch(s)
+ {
+ case "executable": return Type.Executable;
+ case "library": return Type.Library;
+ case "dynamicLibrary": return Type.Library;
+ case "staticLibrary": return Type.Library;
+ default: return Type.Default;
+ }
+ }
+
+ /// parsing from setting file
+ static ProjectConfiguration[string] load(Setting s)
+ {
+ ProjectConfiguration[string] res = [DEFAULT_NAME: DEFAULT];
+ Setting configs = s.objectByPath("configurations");
+ if(configs is null || configs.type != SettingType.ARRAY)
+ return res;
+
+ foreach(conf; configs) {
+ if(!conf.isObject) continue;
+ Type t = Type.Default;
+ if(auto typeName = conf.getString("targetType"))
+ t = parseType(typeName);
+ if (string confName = conf.getString("name"))
+ res[confName] = ProjectConfiguration(confName, t);
+ }
+ return res;
+ }
+}
+
/// DLANGIDE D project
class Project : WorkspaceItem {
protected Workspace _workspace;
@@ -230,16 +282,30 @@ class Project : WorkspaceItem {
protected ProjectFolder _items;
protected ProjectSourceFile _mainSourceFile;
protected SettingsFile _projectFile;
+ protected bool _isDependency;
+ protected string _dependencyVersion;
protected string[] _sourcePaths;
protected string[] _builderSourcePaths;
+ protected ProjectConfiguration[string] _configurations;
-
- this(string fname = null) {
+ this(Workspace ws, string fname = null, string dependencyVersion = null) {
super(fname);
+ _workspace = ws;
_items = new ProjectFolder(fname);
+ _dependencyVersion = dependencyVersion;
+ _isDependency = _dependencyVersion.length > 0;
}
+ @property bool isDependency() { return _isDependency; }
+ @property string dependencyVersion() { return _dependencyVersion; }
+
+ /// returns project configurations
+ @property const(ProjectConfiguration[string]) configurations() const
+ {
+ return _configurations;
+ }
+
/// returns project's own source paths
@property string[] sourcePaths() { return _sourcePaths; }
/// returns project's own source paths
@@ -250,6 +316,29 @@ class Project : WorkspaceItem {
return _builderSourcePaths;
}
+ private static void addUnique(ref string[] dst, string[] items) {
+ foreach(item; items) {
+ bool found = false;
+ foreach(existing; dst) {
+ if (item.equal(existing)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ dst ~= item;
+ }
+ }
+ @property string[] importPaths() {
+ string[] res;
+ addUnique(res, sourcePaths);
+ addUnique(res, builderSourcePaths);
+ foreach(dep; _dependencies) {
+ addUnique(res, dep.sourcePaths);
+ }
+ return res;
+ }
+
string relativeToAbsolutePath(string path) {
if (isAbsolute(path))
return path;
@@ -346,6 +435,10 @@ class Project : WorkspaceItem {
try {
_name = toUTF32(_projectFile.getString("name"));
+ if (_isDependency) {
+ _name ~= "-"d;
+ _name ~= toUTF32(_dependencyVersion.startsWith("~") ? _dependencyVersion[1..$] : _dependencyVersion);
+ }
_description = toUTF32(_projectFile.getString("description"));
Log.d(" project name: ", _name);
Log.d(" project description: ", _description);
@@ -355,7 +448,12 @@ class Project : WorkspaceItem {
Log.i("Project source paths: ", sourcePaths);
Log.i("Builder source paths: ", builderSourcePaths);
+ if (!_isDependency)
+ loadSelections();
+ _configurations = ProjectConfiguration.load(_projectFile);
+ Log.i("Project configurations: ", _configurations);
+
} catch (JSONException e) {
Log.e("Cannot parse json", e);
return false;
@@ -365,5 +463,82 @@ class Project : WorkspaceItem {
}
return true;
}
+
+ protected Project[] _dependencies;
+ @property Project[] dependencies() { return _dependencies; }
+ protected bool addDependency(Project dep) {
+ if (_workspace)
+ _workspace.addDependencyProject(dep);
+ _dependencies ~= dep;
+ return true;
+ }
+ bool loadSelections() {
+ _dependencies.length = 0;
+ DubPackageFinder finder = new DubPackageFinder();
+ scope(exit) destroy(finder);
+ SettingsFile selectionsFile = new SettingsFile(buildNormalizedPath(_dir, "dub.selections.json"));
+ if (!selectionsFile.load())
+ return false;
+ Setting versions = selectionsFile.objectByPath("versions");
+ if (!versions)
+ return false;
+ string[string] versionMap = versions.strMap;
+ foreach(packageName, packageVersion; versionMap) {
+ string fn = finder.findPackage(packageName, packageVersion);
+ Log.d("dependency ", packageName, " ", packageVersion, " : ", fn ? fn : "NOT FOUND");
+ if (fn) {
+ Project p = new Project(_workspace, fn, packageVersion);
+ if (p.load()) {
+ addDependency(p);
+ } else {
+ Log.e("cannot load dependency package ", packageName, " ", packageVersion, " from file ", fn);
+ destroy(p);
+ }
+ }
+ }
+ return true;
+ }
+}
+
+class DubPackageFinder {
+ string systemDubPath;
+ string userDubPath;
+ string tempPath;
+ this() {
+ version(Windows){
+ systemDubPath = buildNormalizedPath(environment.get("ProgramData"), "dub", "packages");
+ userDubPath = buildNormalizedPath(environment.get("APPDATA"), "dub", "packages");
+ tempPath = buildNormalizedPath(environment.get("TEMP"), "dub", "packages");
+ } else version(Posix){
+ systemDubPath = "/var/lib/dub/packages";
+ userDubPath = buildNormalizedPath(environment.get("HOME"), ".dub", "packages");
+ if(!userDubPath.isAbsolute)
+ userDubPath = buildNormalizedPath(getcwd(), userDubPath);
+ tempPath = "/tmp/packages";
+ }
+ }
+
+ protected string findPackage(string packageDir, string packageName, string packageVersion) {
+ string fullName = packageVersion.startsWith("~") ? packageName ~ "-" ~ packageVersion[1..$] : packageName ~ "-" ~ packageVersion;
+ string pathName = absolutePath(buildNormalizedPath(packageDir, fullName));
+ if (pathName.exists && pathName.isDir) {
+ string fn = buildNormalizedPath(pathName, "dub.json");
+ if (fn.exists && fn.isFile)
+ return fn;
+ fn = buildNormalizedPath(pathName, "package.json");
+ if (fn.exists && fn.isFile)
+ return fn;
+ }
+ return null;
+ }
+
+ string findPackage(string packageName, string packageVersion) {
+ string res = null;
+ res = findPackage(userDubPath, packageName, packageVersion);
+ if (res)
+ return res;
+ res = findPackage(systemDubPath, packageName, packageVersion);
+ return res;
+ }
}
diff --git a/src/dlangide/workspace/workspace.d b/src/dlangide/workspace/workspace.d
index 02244f2..36091a6 100644
--- a/src/dlangide/workspace/workspace.d
+++ b/src/dlangide/workspace/workspace.d
@@ -1,10 +1,13 @@
module dlangide.workspace.workspace;
import dlangide.workspace.project;
+import dlangide.ui.frame;
import dlangui.core.logger;
+import std.conv;
import std.path;
import std.file;
import std.json;
+import std.range;
import std.utf;
import std.algorithm;
@@ -44,11 +47,14 @@ bool isWorkspaceFile(string filename) {
/// DlangIDE workspace
class Workspace : WorkspaceItem {
protected Project[] _projects;
-
+
+ protected IDEFrame _frame;
protected BuildConfiguration _buildConfiguration;
-
- this(string fname = null) {
+ protected ProjectConfiguration _projectConfiguration = ProjectConfiguration.DEFAULT;
+
+ this(IDEFrame frame, string fname = null) {
super(fname);
+ _frame = frame;
}
@property Project[] projects() {
@@ -58,14 +64,28 @@ class Workspace : WorkspaceItem {
@property BuildConfiguration buildConfiguration() { return _buildConfiguration; }
@property void buildConfiguration(BuildConfiguration config) { _buildConfiguration = config; }
+ @property ProjectConfiguration projectConfiguration() { return _projectConfiguration; }
+ @property void projectConfiguration(ProjectConfiguration config) { _projectConfiguration = config; }
+
protected Project _startupProject;
@property Project startupProject() { return _startupProject; }
- @property void startupProject(Project project) { _startupProject = project; }
+ @property void startupProject(Project project) {
+ _startupProject = project;
+ _frame.setProjectConfigurations(project.configurations.keys.map!(k => k.to!dstring).array);
+ }
+ /// setups currrent project configuration by name
+ void setStartupProjectConfiguration(string conf)
+ {
+ if(_startupProject && conf in _startupProject.configurations) {
+ _projectConfiguration = _startupProject.configurations[conf];
+ }
+ }
+
protected void fillStartupProject() {
if (!_startupProject && _projects.length)
- _startupProject = _projects[0];
+ startupProject = _projects[0];
}
/// tries to find source file in one of projects, returns found project source file item, or null if not found
@@ -93,6 +113,17 @@ class Workspace : WorkspaceItem {
fillStartupProject();
}
+ bool addDependencyProject(Project p) {
+ for (int i = 0; i < _projects.length; i++) {
+ if (_projects[i].filename.equal(p.filename)) {
+ _projects[i] = p;
+ return false;
+ }
+ }
+ addProject(p);
+ return true;
+ }
+
string absoluteToRelativePath(string path) {
return toForwardSlashSeparator(relativePath(path, _dir));
}
@@ -108,6 +139,8 @@ class Workspace : WorkspaceItem {
json["description"] = JSONValue(toUTF8(_description));
JSONValue[string] projects;
foreach (Project p; _projects) {
+ if (p.isDependency)
+ continue; // don't save dependency
string pname = toUTF8(p.name);
string ppath = absoluteToRelativePath(p.filename);
projects[pname] = JSONValue(ppath);
@@ -147,7 +180,7 @@ class Workspace : WorkspaceItem {
Log.d("project: ", key, " path:", path);
if (!isAbsolute(path))
path = buildNormalizedPath(_dir, path); //, "dub.json"
- Project project = new Project(path);
+ Project project = new Project(this, path);
_projects ~= project;
project.load();
diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini
index 1939fe4..68922e4 100644
--- a/views/res/i18n/en.ini
+++ b/views/res/i18n/en.ini
@@ -32,6 +32,7 @@ MENU_BUILD_PROJECT_BUILD=Build Project
MENU_BUILD_PROJECT_REBUILD=Rebuild Project
MENU_BUILD_PROJECT_CLEAN=Clean Project
MENU_PROJECT=&PROJECT
+MENU_PROJECT_CONFIGURATIONS=Project configurations
MENU_PROJECT_SET_AS_STARTUP=Set as Startup Project
MENU_PROJECT_SETTINGS=Project Settings
MENU_PROJECT_REFRESH=Refresh Workspace Items
diff --git a/views/res/i18n/ru.ini b/views/res/i18n/ru.ini
index 82f24a2..4598b3b 100644
--- a/views/res/i18n/ru.ini
+++ b/views/res/i18n/ru.ini
@@ -11,6 +11,7 @@ MENU_EDIT_CUT=Вырезать
MENU_EDIT_UNDO=&Отмена
MENU_EDIT_REDO=&Повторить
MENU_EDIT_PREFERENCES=&Настройки
+MENU_PROJECT_CONFIGURATIONS=Конфигурации проекта
MENU_VIEW=&Вид
MENU_VIEW_LANGUAGE=&Язык интерфейса
MENU_VIEW_LANGUAGE_EN=English
diff --git a/views/res/mdpi/project-d-dependency.png b/views/res/mdpi/project-d-dependency.png
new file mode 100644
index 0000000..5e7bfcc
Binary files /dev/null and b/views/res/mdpi/project-d-dependency.png differ
diff --git a/views/resources.list b/views/resources.list
index 5d7ee6b..2d7728f 100644
--- a/views/resources.list
+++ b/views/resources.list
@@ -17,6 +17,7 @@ res/mdpi/edit-redo.png
res/mdpi/edit-undo.png
res/mdpi/edit-unindent.png
res/mdpi/project-d.png
+res/mdpi/project-d-dependency.png
res/mdpi/project-development.png
res/mdpi/project-open.png
res/mdpi/run-build.png