Merge pull request #42 from Freakazo/find

Add search feature
This commit is contained in:
Vadim Lopatin 2015-03-10 09:25:07 +03:00
commit d9d61c7c46
6 changed files with 299 additions and 10 deletions

View File

@ -44,6 +44,7 @@ enum IDEActions : int {
GoToDefinition,
GetCompletionSuggestions,
InsertCompletion,
FindText,
}
__gshared static this() {
@ -101,3 +102,4 @@ const Action ACTION_ADD_TO_CURRENT_WORKSPACE = new Action(IDEActions.AddToCurren
const Action ACTION_GO_TO_DEFINITION = (new Action(IDEActions.GoToDefinition, "GO_TO_DEFINITION"c, ""c, KeyCode.KEY_G, KeyFlag.Control)).addAccelerator(KeyCode.F12, 0).disableByDefault();
const Action ACTION_GET_COMPLETIONS = (new Action(IDEActions.GetCompletionSuggestions, "SHOW_COMPLETIONS"c, ""c, KeyCode.KEY_G, KeyFlag.Control|KeyFlag.Shift)).addAccelerator(KeyCode.SPACE, KeyFlag.Control).disableByDefault();
const Action ACTION_FIND_TEXT = (new Action(IDEActions.FindText, "FIND_TEXT"c, ""c, KeyCode.KEY_F, KeyFlag.Control));

View File

@ -385,7 +385,7 @@ class IDEFrame : AppFrame {
MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE,
ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO);
ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_FIND_TEXT);
MenuItem editItemAdvanced = new MenuItem(new Action(221, "MENU_EDIT_ADVANCED"));
editItemAdvanced.add(ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
editItem.add(editItemAdvanced);
@ -626,6 +626,17 @@ class IDEFrame : AppFrame {
case IDEActions.EditPreferences:
showPreferences();
return true;
case IDEActions.FindText:
Log.d("Opening Search Field");
import dlangide.ui.searchPanel;
int searchPanelIndex = _logPanel.getTabs.tabIndex("search");
if(searchPanelIndex == -1) {
SearchWidget searchPanel = new SearchWidget("search", this);
_logPanel.getTabs.addTab( searchPanel, "Search"d, null, true);
}
_logPanel.getTabs.selectTab("search");
//TODO: Focus search field
return true;
default:
return super.handleAction(a);
}

View File

@ -3,16 +3,19 @@ module dlangide.ui.outputpanel;
import dlangui;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
import dlangide.ui.frame;
import std.utf;
import std.regex;
import std.algorithm : startsWith;
import std.string;
/// event listener to navigate by error/warning position
interface CompilerLogIssueClickHandler {
bool onCompilerLogIssueClick(dstring filename, int line, int column);
}
/// Log widget with parsing of compiler output
class CompilerLogWidget : LogWidget {
@ -113,20 +116,42 @@ class OutputPanel : DockWindow {
protected CompilerLogWidget _logWidget;
TabWidget _tabs;
@property TabWidget getTabs() { return _tabs;}
this(string id) {
_showCloseButton = false;
dockAlignment = DockAlignment.Bottom;
super(id);
_caption.text = "Output"d;
dockAlignment = DockAlignment.Bottom;
}
}
override protected Widget createBodyWidget() {
_tabs = new TabWidget("OutputPanelTabs");
_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
_logWidget = new CompilerLogWidget("logwidget");
_logWidget.readOnly = true;
_logWidget.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT);
_logWidget.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
_logWidget.compilerLogIssueClickHandler = &onIssueClick;
return _logWidget;
_tabs.addTab(_logWidget, "Compiler Log"d);
_tabs.selectTab("logwidget");
return _tabs;
}
override protected void init() {
styleId = STYLE_DOCK_WINDOW;
_bodyWidget = createBodyWidget();
_bodyWidget.styleId = STYLE_DOCK_WINDOW_BODY;
addChild(_bodyWidget);
}
//TODO: Refactor OutputPanel to expose CompilerLogWidget
void appendText(string category, dstring msg) {
_logWidget.appendText(msg);
}
@ -159,4 +184,4 @@ class OutputPanel : DockWindow {
return true;
}
}
}

View File

@ -0,0 +1,249 @@
module dlangide.ui.searchPanel;
import dlangui;
import dlangide.ui.frame;
import dlangide.ui.wspanel;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
import std.string;
import std.conv;
interface SearchResultClickHandler {
bool onSearchResultClick(int line);
}
//LogWidget with highlighting for search results.
class SearchLogWidget : LogWidget {
//Sends which line was clicked.
Signal!SearchResultClickHandler searchResultClickHandler;
this(string ID){
super(ID);
scrollLock = false;
}
override protected CustomCharProps[] handleCustomLineHighlight(int line, dstring txt, ref CustomCharProps[] buf) {
uint defColor = textColor;
const uint filenameColor = 0x0000C0;
const uint errorColor = 0xFF0000;
const uint warningColor = 0x606000;
const uint deprecationColor = 0x802040;
uint flags = 0;
if (buf.length < txt.length)
buf.length = txt.length;
//Highlights the filename
if(txt.startsWith("Matches in ")) {
CustomCharProps[] colors = buf[0..txt.length];
uint cl = defColor;
flags = 0;
for (int i = 0; i < txt.length; i++) {
dstring rest = txt[i..$];
if(i == 11) {
cl = filenameColor;
flags = TextFlag.Underline;
}
colors[i].color = cl;
colors[i].textFlags = flags;
}
return colors;
}
//Highlight line and collumn
else {
CustomCharProps[] colors = buf[0..txt.length];
uint cl = filenameColor;
flags = TextFlag.Underline;
for (int i = 0; i < txt.length; i++) {
dstring rest = txt[i..$];
if (rest.startsWith(" -->"d)) {
cl = warningColor;
flags = 0;
}
if(i == 4) {
cl = errorColor;
flags = TextFlag.Underline;
}
colors[i].color = cl;
colors[i].textFlags = flags;
//Colors to apply in following iterations of the loop.
if(rest.startsWith("]")) {
cl = defColor;
flags = 0;
}
}
return colors;
}
}
override bool onMouseEvent(MouseEvent event) {
super.onMouseEvent(event);
if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
int line = _caretPos.line;
if (searchResultClickHandler.assigned) {
searchResultClickHandler(line);
return true;
}
}
return false;
}
}
class SearchWidget : TabWidget {
HorizontalLayout _layout;
EditLine _findText;
SearchLogWidget _resultLog;
ComboBox _searchScope;
protected IDEFrame _frame;
protected SearchMatchList[] _matchedList;
struct SearchMatch {
int line;
long col;
dstring lineContent;
}
struct SearchMatchList {
string filename;
SearchMatch[] matches;
}
this(string ID, IDEFrame frame) {
super(ID);
_frame = frame;
layoutHeight(FILL_PARENT);
//Remove title, more button
removeAllChildren();
_layout = new HorizontalLayout();
_layout.addChild(new TextWidget("FindLabel", "Find: "d));
_findText = new EditLine();
_findText.padding(Rect(5,4,50,4));
_findText.layoutWidth(400);
_layout.addChild(_findText);
auto goButton = new ImageButton("findTextButton", "edit-redo");
goButton.addOnClickListener( delegate(Widget) {
findText(_findText.text);
return true;
});
_layout.addChild(goButton);
_searchScope = new ComboBox("searchScope", ["File"d, "Project"d, "Dependencies"d, "Everywhere"d]);
_searchScope.selectedItemIndex = 0;
_layout.addChild(_searchScope);
addChild(_layout);
_resultLog = new SearchLogWidget("SearchLogWidget");
_resultLog.searchResultClickHandler = &onMatchClick;
_resultLog.layoutHeight(FILL_PARENT);
addChild(_resultLog);
}
//Recursively search for text in projectItem
void searchInProject(ProjectItem project, dstring text) {
if (project.isFolder == true) {
ProjectFolder projFolder = cast(ProjectFolder) project;
for (int i = 0; i < projFolder.childCount; i++) {
searchInProject(projFolder.child(i), text);
}
}
else {
Log.d("Searching in: " ~ project.filename);
EditableContent content = new EditableContent(true);
content.load(project.filename);
SearchMatchList match;
match.filename = project.filename;
foreach(int lineIndex, dstring line; content.lines) {
auto colIndex = line.indexOf(text);
if (colIndex != -1) {
match.matches ~= SearchMatch(lineIndex, colIndex, line);
}
}
if(match.matches.length > 0) {
_matchedList ~= match;
}
}
}
bool findText(dstring source) {
Log.d("Finding " ~ source);
_resultLog.text = ""d;
_matchedList = [];
switch (_searchScope.text) {
case "File": //File
auto currentFileItem = _frame._wsPanel.workspace.findSourceFileItem(_frame.currentEditor.filename);
if(currentFileItem !is null)
searchInProject(currentFileItem, source);
break;
case "Project": //Project
foreach(Project project; _frame._wsPanel.workspace.projects) {
if(!project.isDependency)
searchInProject(project.items, source);
}
break;
case "Dependencies": //Dependencies
foreach(Project project; _frame._wsPanel.workspace.projects) {
if(project.isDependency)
searchInProject(project.items, source);
}
break;
case "Everywhere": //Everywhere
foreach(Project project; _frame._wsPanel.workspace.projects) {
searchInProject(project.items, source);
}
break;
default:
assert(0);
}
if (_matchedList.length == 0) {
_resultLog.appendText(to!dstring("No matches found.\n"));
}
else {
foreach(SearchMatchList fileMatchList; _matchedList) {
_resultLog.appendText("Matches in "d ~ to!dstring(fileMatchList.filename) ~ '\n');
foreach(SearchMatch match; fileMatchList.matches) {
_resultLog.appendText(" --> ["d ~ to!dstring(match.line+1) ~ ":"d ~ to!dstring(match.col) ~ "]" ~ match.lineContent ~"\n"d);
}
}
}
return true;
}
//Find the match/matchList that corrosponds to the line in _resultLog
bool onMatchClick(int line) {
line++;
foreach(matchList; _matchedList){
line--;
if (line == 0) {
_frame.openSourceFile(matchList.filename);
_frame.currentEditor.setFocus();
return true;
}
foreach(match; matchList.matches) {
line--;
if (line == 0) {
_frame.openSourceFile(matchList.filename);
_frame.currentEditor.setCaretPos(match.line, to!int(match.col));
_frame.currentEditor.setFocus();
return true;
}
}
}
return false;
}
}

View File

@ -63,7 +63,7 @@ class ProjectItem {
}
/// returns true if item is folder
@property bool isFolder() {
@property const bool isFolder() {
return false;
}
/// returns child object count
@ -84,7 +84,7 @@ class ProjectFolder : ProjectItem {
super(filename);
}
@property override bool isFolder() {
@property override const bool isFolder() {
return true;
}
@property override int childCount() {

View File

@ -56,9 +56,11 @@ MENU_WINDOW_CLOSE_ALL_DOCUMENTS=Close All Documents
MENU_HELP=&HELP
MENU_HELP_VIEW_HELP=&View help
MENU_HELP_ABOUT=&About
MENU_NAVIGATE=NAVIGATE
GO_TO_DEFINITION=Go To Definition
SHOW_COMPLETIONS=Get Autocompletions
MENU_NAVIGATE=NAVIGATE
FIND_TEXT=Find text
TAB_LONG_LIST=Long list