Skip to content

Commit

Permalink
CHE-137. Improve full text searching
Browse files Browse the repository at this point in the history
Signed-off-by: Roman Nikitenko <rnikitenko@codenvy.com>
  • Loading branch information
RomanNikitenko committed May 13, 2016
1 parent 510470a commit 688d306
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ public interface CoreLocalizationConstant extends Messages {

String search();

@Key("text.search.scope.label")
String textSearchScopeLabel();

@Key("text.search.fileFilter.label")
String textSearchFileFilterLabel();

@Key("text.search.content.label")
String textSearchContentLabel();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
* Presenter for full text search.
*
* @author Valeriy Svydenko
* @author Roman Nikitenko
*/
@Singleton
public class FullTextSearchPresenter implements FullTextSearchView.ActionDelegate {
private static final String URL_ENCODED_BACKSLASH = "%5C";
private static final String AND_OPERATOR = "AND";

private final FullTextSearchView view;
private final FindResultPresenter findResultPresenter;
Expand Down Expand Up @@ -69,7 +72,7 @@ public void showDialog() {
@Override
public void search(final String text) {
QueryExpression queryExpression = new QueryExpression();
queryExpression.setText(text + '*');
queryExpression.setText(prepareQuery(text));
if (!view.getFileMask().isEmpty()) {
queryExpression.setName(view.getFileMask());
}
Expand All @@ -91,6 +94,51 @@ public void apply(PromiseError arg) throws OperationException {
});
}

private String prepareQuery(String text) {
StringBuilder sb = new StringBuilder();
for (char character : text.toCharArray()) {
// Escape those characters that QueryParser expects to be escaped
if (character == '\\' ||
character == '+' ||
character == '-' ||
character == '!' ||
character == '(' ||
character == ')' ||
character == ':' ||
character == '^' ||
character == '[' ||
character == ']' ||
character == '\"' ||
character == '{' ||
character == '}' ||
character == '~' ||
character == '*' ||
character == '?' ||
character == '|' ||
character == '&' ||
character == '/') {
sb.append(URL_ENCODED_BACKSLASH);
}
sb.append(character);
}
String escapedText = sb.toString();

String[] items = escapedText.trim().split("\\s+");
int numberItem = items.length;
if (numberItem == 1) {
return items[0] + '*';
}

String lastItem = items[numberItem - 1];
sb = new StringBuilder();
sb.append('"');
sb.append(escapedText.substring(0, escapedText.lastIndexOf(lastItem)));
sb.append("\" " + AND_OPERATOR + " ");
sb.append(lastItem);
sb.append('*');
return sb.toString();
}

@Override
public void setPathDirectory(String path) {
view.setPathDirectory(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,80 +23,88 @@
margin: 6px;
}

.spacingContainer {
margin-top: 5px;
margin-bottom: 8px;
.checkBox {
float: left;
margin-top: 4px;
}

.labelMargin {
margin: 5px 5px;
.label {
float: left;
margin-bottom: 0;
}

.containerPanel {
width: 100%;
height: 27px;
.checkBoxLabel {
margin-left: 3px;
}

.checkBox {
float: left;
margin-left: 6px;
margin-top: 6px;
.promtLabel {
float: right;
margin-right: 28px;
margin-bottom: 0;
}

.selectDirectoryButton {
min-width: 25px;
margin-left: 2px;
border-radius: 5px;
padding: 0 7px;
margin-left: 3px;
padding: 2px 7px;
}

.selectDirectoryButton:hover,
.selectDirectoryButton:focus {
float: left;
box-shadow: 0 0 0 1px buttonHoverBorderColor, 0 0 0 2px buttonHoverBackground;
border-color: transparent;
}

.floating {
.section {
float: left;
line-height: 22px;
margin-left: 4px;
}

.inputTextBox {
margin-bottom: 2px;
.sectionContent {
margin-left: 15px;
margin-bottom: 5px;
}

.box {
.inputTextBox {
float: left;
margin-bottom: 2px;
margin-left: 6px;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
</ui:style>
<g:FlowPanel width="400px" height="135px" addStyleNames="{style.emptyBorder}" debugId="text-search-mainPanel">
<g:FlowPanel addStyleNames="{style.emptyBorder} {style.spacingContainer} {style.containerPanel} {style.floating}">
<g:Label addStyleNames="{style.labelMargin} {style.floating}" text="{locale.textSearchContentLabel}"/>
<g:TextBox ui:field="text" width="310px" addStyleNames="{style.box} {style.floating} {style.inputTextBox}"
<g:FlowPanel width="400px" addStyleNames="{style.emptyBorder}" debugId="text-search-mainPanel">
<g:FlowPanel addStyleNames="{style.section}">
<g:Label addStyleNames="{style.label}" text="{locale.textSearchContentLabel}"/>
<g:TextBox ui:field="text" width="322px" addStyleNames="{style.inputTextBox}"
debugId="text-search-text"/>
</g:FlowPanel>

<g:FlowPanel addStyleNames="{style.emptyBorder} {style.spacingContainer} {style.containerPanel} {style.floating}">
<g:CheckBox ui:field="isUseDirectory" addStyleNames="{style.checkBox}" value="false"/>
<g:Label addStyleNames="{style.labelMargin} {style.floating}" text="{locale.textSearchDirectory}"/>
<g:TextBox enabled="false" ui:field="directory" width="257px" addStyleNames="{style.box} {style.floating}"
<g:FlowPanel addStyleNames="{style.section}">
<g:Label text="{locale.textSearchScopeLabel}" width="100%" addStyleNames="{style.label}"/>
<g:CheckBox ui:field="isUseDirectory" addStyleNames="{style.checkBox} {style.sectionContent}" value="false"/>
<g:Label addStyleNames="{style.label} {style.checkBoxLabel}" text="{locale.textSearchDirectory}"/>
<g:TextBox enabled="false" ui:field="directory" width="257px" addStyleNames="{style.inputTextBox}"
debugId="text-search-directory"/>
<g:Button enabled="false" ui:field="selectPathButton" text="..." addStyleNames="{style.floating} {style.selectDirectoryButton}"
<g:Button enabled="false" ui:field="selectPathButton" text="..." addStyleNames="{style.selectDirectoryButton}"
debugId="text-search-directory-button"/>
</g:FlowPanel>

<g:FlowPanel addStyleNames="{style.emptyBorder} {style.spacingContainer} {style.containerPanel} {style.floating}">
<g:CheckBox ui:field="isUseFileMask" addStyleNames="{style.checkBox}" value="false"/>
<g:Label addStyleNames="{style.labelMargin} {style.floating}" text="{locale.textSearchFileMask}"/>
<g:TextBox enabled="false" ui:field="filesMask" width="296px" addStyleNames="{style.box} {style.floating}"
<g:FlowPanel addStyleNames="{style.section}">
<g:Label text="{locale.textSearchFileFilterLabel}" width="100%" addStyleNames="{style.label}"/>
<g:CheckBox ui:field="isUseFileMask" value="false" addStyleNames="{style.checkBox} {style.sectionContent}"/>
<g:Label text="{locale.textSearchFileMask}" width="62px" addStyleNames="{style.label} {style.checkBoxLabel}"/>
<g:TextBox enabled="false" ui:field="filesMask" width="284px" addStyleNames="{style.inputTextBox}"
debugId="text-search-files"/>
<g:Label width="100%" addStyleNames="{style.labelMargin} {res.coreCss.greyFontColor}"
<g:Label addStyleNames="{res.coreCss.greyFontColor} {style.promtLabel}"
text="{locale.navigateToFileViewFileFieldPrompt}"/>
</g:FlowPanel>

<g:SimplePanel addStyleNames="{style.emptyBorder} {style.spacingContainer} {style.containerPanel} {style.floating}">
<g:Label ui:field="errLabel" width="100%" addStyleNames="{style.labelMargin} {res.coreCss.errorFont}"/>
<g:SimplePanel addStyleNames="{style.section}">
<g:Label ui:field="errLabel" width="100%" direction="LTR" addStyleNames="{res.coreCss.errorFont}"/>
</g:SimplePanel>
</g:FlowPanel>
</ui:UiBinder>
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ error.configuration.content=Do you want configure project as BLANK? You can re-c
############### full-text-search###################
action.full.text.search=Find
action.full.text.search.description=Find Text In Path
text.search.scope.label=Scope
text.search.fileFilter.label=File name filter
text.search.content.label=Text to find:
text.search.title=Find Text
text.search.file.mask=File mask:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
import com.google.gwtmockito.GwtMockitoTestRunner;

import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
import org.eclipse.che.ide.api.machine.DevMachine;
import org.eclipse.che.ide.api.project.ProjectServiceClient;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.api.project.shared.dto.ItemReference;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.api.promises.client.PromiseError;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.machine.DevMachine;
import org.eclipse.che.ide.api.project.ProjectServiceClient;
import org.eclipse.che.ide.api.project.QueryExpression;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.rest.DtoUnmarshallerFactory;
import org.eclipse.che.ide.search.presentation.FindResultPresenter;
Expand All @@ -37,6 +37,8 @@
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
Expand All @@ -49,6 +51,7 @@
* Tests for {@link FullTextSearchPresenter}.
*
* @author Valeriy Svydenko
* @author Roman Nikitenko
*/
@RunWith(GwtMockitoTestRunner.class)
public class FullTextSearchPresenterTest {
Expand Down Expand Up @@ -78,6 +81,8 @@ public class FullTextSearchPresenterTest {
private ArgumentCaptor<Operation<PromiseError>> operationErrorCapture;
@Captor
private ArgumentCaptor<Operation<List<ItemReference>>> operationSuccessCapture;
@Captor
private ArgumentCaptor<QueryExpression> queryExpressionArgumentCaptor;

@Before
public void setUp() throws Exception {
Expand Down Expand Up @@ -113,6 +118,38 @@ public void pathOfDirectoryToSearchShouldBeSet() {
verify(view).setPathDirectory(anyString());
}

@Test
public void testPrepareOneWordToSearch() throws Exception {
String textToSearch = "someText";
fullTextSearchPresenter.search(textToSearch);

verify(projectServiceClient).search((DevMachine)any(), queryExpressionArgumentCaptor.capture());
String actual = queryExpressionArgumentCaptor.getValue().getText();
String expected = textToSearch + '*';
assertEquals(expected, actual);
}

@Test
public void testPreparePhraseToSearch() throws Exception {
fullTextSearchPresenter.search(SEARCHED_TEXT);

verify(projectServiceClient).search((DevMachine)any(), queryExpressionArgumentCaptor.capture());
String actual = queryExpressionArgumentCaptor.getValue().getText();
String expected = "\"to be or not to \" AND be*";
assertEquals(expected, actual);
}

@Test
public void testPrepareTextToSearchWhenSomeCharactersShouldBeEscaped() throws Exception {
String searchText = "http://localhost:8080/ide/dev6?action=createProject:projectName=test";
fullTextSearchPresenter.search(searchText);

verify(projectServiceClient).search((DevMachine)any(), queryExpressionArgumentCaptor.capture());
String actual = queryExpressionArgumentCaptor.getValue().getText();
String expected = "http%5C:%5C/%5C/localhost%5C:8080%5C/ide%5C/dev6%5C?action=createProject%5C:projectName=test" + '*';
assertEquals(expected, actual);
}

@Test
public void searchShouldBeSuccessfullyFinished() throws Exception {
List<ItemReference> result = new ArrayList<>();
Expand Down
Loading

0 comments on commit 688d306

Please sign in to comment.