diff --git a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/CoreLocalizationConstant.java b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/CoreLocalizationConstant.java index 99e6ac67291..5f2e3345130 100644 --- a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/CoreLocalizationConstant.java +++ b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/CoreLocalizationConstant.java @@ -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(); diff --git a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchPresenter.java b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchPresenter.java index fa7bf0e0949..d95aef570ee 100644 --- a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchPresenter.java +++ b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchPresenter.java @@ -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; @@ -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()); } @@ -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); diff --git a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchViewImpl.ui.xml b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchViewImpl.ui.xml index 935892bda93..5eb37069695 100644 --- a/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchViewImpl.ui.xml +++ b/core/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/search/FullTextSearchViewImpl.ui.xml @@ -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; } - - - - + + + - - - - + + + + - - - - - + + + + - - - + + diff --git a/core/ide/che-core-ide-app/src/main/resources/org/eclipse/che/ide/CoreLocalizationConstant.properties b/core/ide/che-core-ide-app/src/main/resources/org/eclipse/che/ide/CoreLocalizationConstant.properties index 4378465fbd6..34a0b182355 100644 --- a/core/ide/che-core-ide-app/src/main/resources/org/eclipse/che/ide/CoreLocalizationConstant.properties +++ b/core/ide/che-core-ide-app/src/main/resources/org/eclipse/che/ide/CoreLocalizationConstant.properties @@ -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: diff --git a/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/search/FullTextSearchPresenterTest.java b/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/search/FullTextSearchPresenterTest.java index 9d558906b5b..96391a8a910 100644 --- a/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/search/FullTextSearchPresenterTest.java +++ b/core/ide/che-core-ide-app/src/test/java/org/eclipse/che/ide/search/FullTextSearchPresenterTest.java @@ -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; @@ -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; @@ -49,6 +51,7 @@ * Tests for {@link FullTextSearchPresenter}. * * @author Valeriy Svydenko + * @author Roman Nikitenko */ @RunWith(GwtMockitoTestRunner.class) public class FullTextSearchPresenterTest { @@ -78,6 +81,8 @@ public class FullTextSearchPresenterTest { private ArgumentCaptor> operationErrorCapture; @Captor private ArgumentCaptor>> operationSuccessCapture; + @Captor + private ArgumentCaptor queryExpressionArgumentCaptor; @Before public void setUp() throws Exception { @@ -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 result = new ArrayList<>(); diff --git a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java index 7adabf9e549..1a1292cacc1 100644 --- a/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java +++ b/wsagent/che-core-api-project/src/test/java/org/eclipse/che/api/project/server/ProjectServiceTest.java @@ -131,6 +131,14 @@ public class ProjectServiceTest { protected final static String FS_PATH = "target/fss"; protected final static String INDEX_PATH = "target/fss_index"; + private static final String URL_ENCODED_QUOTES = "%22"; + private static final String URL_ENCODED_SPACE = "%20"; + private static final String URL_ENCODED_BACKSLASH = "%5C"; + private static final String URL_ENCODED_ASTERISK = "%2A"; + + private static final String AND_OPERATOR = "AND"; + private static final String NOT_OPERATOR = "NOT"; + private ProjectManager pm; private ResourceLauncher launcher; private ProjectHandlerRegistry phRegistry; @@ -142,7 +150,7 @@ public class ProjectServiceTest { @Mock private UserDao userDao; @Mock - private WorkspaceDto usersWorkspaceMock; + private WorkspaceDto usersWorkspaceMock; @Mock private WorkspaceConfigDto workspaceConfigMock; @Mock @@ -1892,6 +1900,137 @@ public void testSearchByText() throws Exception { Assert.assertTrue(paths.contains("/my_project/x/y/__test.txt")); } + @SuppressWarnings("unchecked") + @Test + public void testSearchParticularSequenceWords() throws Exception { + String queryToSearch = "?text=" + URL_ENCODED_QUOTES + + "To" + URL_ENCODED_SPACE + + "be" + URL_ENCODED_SPACE + + "or" + URL_ENCODED_SPACE + + "not" + URL_ENCODED_SPACE + + "to" + URL_ENCODED_SPACE + + "be" + URL_ENCODED_QUOTES; + RegisteredProject myProject = pm.getProject("my_project"); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("a/b").createFile("test.txt", "Pay attention! To be or to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("c").createFile("_test", "Pay attention! To be or to not be that is the question".getBytes()); + + ContainerResponse response = + launcher.service(GET, String.format("http://localhost:8080/api/project/%s/search/my_project", workspace) + queryToSearch, + "http://localhost:8080/api", null, null, null); + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + List result = (List)response.getEntity(); + assertEquals(result.size(), 1); + Set paths = new LinkedHashSet<>(1); + paths.addAll(result.stream().map(ItemReference::getPath).collect(Collectors.toList())); + Assert.assertTrue(paths.contains("/my_project/x/y/containsSearchText.txt")); + } + + @SuppressWarnings("unchecked") + @Test + public void testSearchParticularSequenceWordsWithAnyEnding() throws Exception { + String queryToSearch = "?text=" + URL_ENCODED_QUOTES + + "that" + URL_ENCODED_SPACE + + "is" + URL_ENCODED_SPACE + + "the" + URL_ENCODED_QUOTES + URL_ENCODED_SPACE + AND_OPERATOR + URL_ENCODED_SPACE + + "question" + URL_ENCODED_ASTERISK; + RegisteredProject myProject = pm.getProject("my_project"); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("a/b") + .createFile("containsSearchTextAlso.txt", "Pay attention! To be or not to be that is the questionS".getBytes()); + myProject.getBaseFolder().createFolder("c") + .createFile("notContainsSearchText", "Pay attention! To be or to not be that is the questEon".getBytes()); + + ContainerResponse response = + launcher.service(GET, String.format("http://localhost:8080/api/project/%s/search/my_project", workspace) + queryToSearch, + "http://localhost:8080/api", null, null, null); + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + List result = (List)response.getEntity(); + assertEquals(result.size(), 2); + Set paths = new LinkedHashSet<>(2); + paths.addAll(result.stream().map(ItemReference::getPath).collect(Collectors.toList())); + Assert.assertTrue(paths.contains("/my_project/x/y/containsSearchText.txt")); + Assert.assertTrue(paths.contains("/my_project/a/b/containsSearchTextAlso.txt")); + Assert.assertFalse(paths.contains("/my_project/c/notContainsSearchText.txt")); + } + + @SuppressWarnings("unchecked") + @Test + public void testSearchWordWithAnyEnding() throws Exception { + String queryToSearch = "?text=" + + "question" + URL_ENCODED_ASTERISK; + RegisteredProject myProject = pm.getProject("my_project"); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("a/b") + .createFile("containsSearchTextAlso.txt", "Pay attention! To be or not to be that is the questionS".getBytes()); + myProject.getBaseFolder().createFolder("c") + .createFile("notContainsSearchText", "Pay attention! To be or to not be that is the questEon".getBytes()); + + ContainerResponse response = + launcher.service(GET, String.format("http://localhost:8080/api/project/%s/search/my_project", workspace) + queryToSearch, + "http://localhost:8080/api", null, null, null); + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + List result = (List)response.getEntity(); + assertEquals(result.size(), 2); + Set paths = new LinkedHashSet<>(2); + paths.addAll(result.stream().map(ItemReference::getPath).collect(Collectors.toList())); + Assert.assertTrue(paths.contains("/my_project/x/y/containsSearchText.txt")); + Assert.assertTrue(paths.contains("/my_project/a/b/containsSearchTextAlso.txt")); + Assert.assertFalse(paths.contains("/my_project/c/notContainsSearchText.txt")); + } + + @SuppressWarnings("unchecked") + @Test + public void testSearchTextWhenExcludeSomeText() throws Exception { + String queryToSearch = "?text=" + + "question" + URL_ENCODED_SPACE + NOT_OPERATOR + URL_ENCODED_SPACE + URL_ENCODED_QUOTES + + "attention!" + URL_ENCODED_QUOTES; + RegisteredProject myProject = pm.getProject("my_project"); + myProject.getBaseFolder().createFolder("x/y").createFile("containsSearchText.txt", "To be or not to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("b") + .createFile("notContainsSearchText", "Pay attention! To be or not to be that is the question".getBytes()); + myProject.getBaseFolder().createFolder("c").createFile("alsoNotContainsSearchText", "To be or to not be that is the ...".getBytes()); + + ContainerResponse response = + launcher.service(GET, String.format("http://localhost:8080/api/project/%s/search/my_project", workspace) + queryToSearch, + "http://localhost:8080/api", null, null, null); + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + List result = (List)response.getEntity(); + assertEquals(result.size(), 1); + Set paths = new LinkedHashSet<>(1); + paths.addAll(result.stream().map(ItemReference::getPath).collect(Collectors.toList())); + Assert.assertTrue(paths.contains("/my_project/x/y/containsSearchText.txt")); + Assert.assertFalse(paths.contains("/my_project/b/notContainsSearchText.txt")); + Assert.assertFalse(paths.contains("/my_project/c/alsoContainsSearchText")); + } + + @SuppressWarnings("unchecked") + @Test + public void testSearchTextWithEscapedCharachters() throws Exception { + String queryToSearch = "?text=http" + + URL_ENCODED_BACKSLASH + ':' + + URL_ENCODED_BACKSLASH + '/' + + URL_ENCODED_BACKSLASH + '/' + "localhost" + + URL_ENCODED_BACKSLASH + ':' + "8080" + + URL_ENCODED_BACKSLASH + '/' + "ide" + + URL_ENCODED_BACKSLASH + '/' + "dev6" + + URL_ENCODED_BACKSLASH + '?' + "action=createProject" + + URL_ENCODED_BACKSLASH + ':' + "projectName=test"; + RegisteredProject myProject = pm.getProject("my_project"); + myProject.getBaseFolder().createFolder("x/y") + .createFile("test.txt", "http://localhost:8080/ide/dev6?action=createProject:projectName=test".getBytes()); + + ContainerResponse response = launcher.service(GET, String.format("http://localhost:8080/api/project/%s/search/my_project", + workspace) + queryToSearch, + "http://localhost:8080/api", null, null, null); + assertEquals(response.getStatus(), 200, "Error: " + response.getEntity()); + List result = (List)response.getEntity(); + assertEquals(result.size(), 1); + Set paths = new LinkedHashSet<>(1); + paths.addAll(result.stream().map(ItemReference::getPath).collect(Collectors.toList())); + Assert.assertTrue(paths.contains("/my_project/x/y/test.txt")); + } + @SuppressWarnings("unchecked") @Test public void testSearchByNameAndText() throws Exception {