From fd4b26b85ef3b322a86af40a1a628bae884848e9 Mon Sep 17 00:00:00 2001 From: xzl Date: Tue, 23 Apr 2024 14:06:30 +0800 Subject: [PATCH] feat: update eclipse-platform-text to 4.26 Eclipse JFace Text Issue: https://github.com/deepin-community/sig-deepin-sysdev-team/issues/547 Log: update repo --- .gitignore | 1 - .mvn/extensions.xml | 11 +- CONTRIBUTING | 71 -- Jenkinsfile | 42 +- NOTICE | 43 -- debian/build.xml | 2 +- debian/changelog | 10 + debian/control | 4 +- debian/copyright | 2 +- debian/watch | 2 +- .../META-INF/MANIFEST.MF | 2 +- .../build.properties | 4 + org.eclipse.core.filebuffers.tests/pom.xml | 28 - .../tests/ConvertLineDelemiterTest.java | 93 +++ .../META-INF/MANIFEST.MF | 2 +- .../ConvertLineDelimitersOperation.java | 41 +- .../internal/filebuffers/DocumentReader.java | 6 + .../filebuffers/FileBuffersMessages.java | 1 + .../FileBuffersMessages.properties | 3 +- .../filebuffers/FileStoreTextFileBuffer.java | 14 +- .../filebuffers/ResourceTextFileBuffer.java | 16 +- .../build.properties | 4 + org.eclipse.jface.text.examples/pom.xml | 28 - .../examples/codemining/CodeMiningDemo.java | 21 +- .../EchoAtEndOfLineCodeMiningProvider.java | 64 ++ .../EmptyLineCodeMiningProvider.java | 49 ++ .../META-INF/MANIFEST.MF | 4 +- org.eclipse.jface.text.tests/build.properties | 6 + org.eclipse.jface.text.tests/pom.xml | 50 -- .../DefaultTextDoubleClickStrategyTest.java | 16 +- ...entAdapterContentProposalProviderTest.java | 60 ++ .../jface/text/tests/JFaceTextTestSuite.java | 6 +- .../jface/text/tests/MultiSelectionTest.java | 193 +++++ .../text/tests/ProjectionViewerTest.java | 78 ++ .../text/tests/TextPresentationTest.java | 6 +- .../jface/text/tests/TextViewerTest.java | 482 ++++++------ .../CodeMiningProjectionViewerTest.java | 91 ++- .../AbstractContentAssistTest.java | 38 +- .../contentassist/AsyncContentAssistTest.java | 79 +- .../ContextInformationPresenterTest.java | 3 +- .../contentassist/ContextInformationTest.java | 15 +- .../DelayedErrorContentAssistProcessor.java | 2 +- .../FilteringAsyncContentAssistTests.java | 1 + .../IncrementalAsyncContentAssistTests.java | 1 + .../reconciler/AbstractReconcilerTest.java | 14 +- .../FastAbstractReconcilerTest.java | 29 + org.eclipse.jface.text/.settings/.api_filters | 24 +- .../.settings/org.eclipse.jdt.ui.prefs | 83 +- org.eclipse.jface.text/META-INF/MANIFEST.MF | 4 +- .../source/projection/ProjectionViewer.java | 4 +- .../IContentAssistSubjectControl.java | 2 - .../text/InformationControlReplacer.java | 48 +- .../internal/text/SelectionProcessor.java | 482 +++++++++--- .../CodeMiningLineHeaderAnnotation.java | 4 + .../text/html/BrowserInformationControl.java | 10 +- .../internal/text/html/HTMLTextPresenter.java | 250 +++--- .../contentassist/LineBreakingReader.java | 156 ---- .../internal/text/source/DiffPainter.java | 9 +- .../text/AbstractInformationControl.java | 2 +- .../text/DefaultTextDoubleClickStrategy.java | 4 +- ...ocumentAdapterContentProposalProvider.java | 14 +- .../jface/text/IBlockTextSelection.java | 2 + .../jface/text/IFindReplaceTarget.java | 30 +- .../text/IFindReplaceTargetExtension.java | 1 + .../text/IFindReplaceTargetExtension4.java | 22 + .../jface/text/IMultiTextSelection.java | 29 + .../jface/text/MultiTextSelection.java | 111 +++ .../org/eclipse/jface/text/TextSelection.java | 9 +- .../org/eclipse/jface/text/TextViewer.java | 230 ++++-- .../jface/text/TextViewerUndoManager.java | 2 +- .../text/codemining/CodeMiningReconciler.java | 3 +- .../text/codemining/LineEndCodeMining.java | 36 + .../AdditionalInfoController.java | 2 +- .../AsyncCompletionProposalPopup.java | 70 +- .../CompletionProposalPopup.java | 103 ++- .../text/contentassist/ContentAssistant.java | 61 +- .../IContentAssistProcessor.java | 10 +- .../ContextBasedFormattingStrategy.java | 1 - .../formatter/IContentFormatterExtension.java | 35 +- .../eclipse/jface/text/link/LinkedModeUI.java | 69 +- .../text/reconciler/AbstractReconciler.java | 81 +- .../InlinedAnnotationDrawingStrategy.java | 26 +- .../inlined/InlinedAnnotationSupport.java | 10 +- .../source/inlined/LineContentAnnotation.java | 19 +- org.eclipse.search.tests/META-INF/MANIFEST.MF | 2 +- org.eclipse.search.tests/build.properties | 6 + org.eclipse.search.tests/pom.xml | 49 -- .../search/core/tests/TestSearchResult.java | 17 - .../tests/filesearch/FileSearchTests.java | 39 + org.eclipse.search/.settings/.api_filters | 16 - org.eclipse.search/META-INF/MANIFEST.MF | 2 +- .../search/core/text/TextSearchRequestor.java | 15 + .../ui/text/AbstractTextSearchResult.java | 166 ++-- .../ui/text/AbstractTextSearchViewPage.java | 94 ++- .../search/ui/text/FileTextSearchScope.java | 14 +- .../internal/ui/text/MarkerHighlighter.java | 123 ++- org.eclipse.search/plugin.xml | 15 - .../core/text/DocumentCharSequence.java | 5 + .../core/text/FileCharSequenceProvider.java | 68 +- .../core/text/PatternConstructor.java | 57 +- .../internal/core/text/TextSearchVisitor.java | 298 ++++---- .../search/internal/ui/GotoMarkerAction.java | 37 - .../internal/ui/RemoveAllResultsAction.java | 34 - .../internal/ui/RemoveAllSearchesAction.java | 34 - .../search/internal/ui/RemoveMatchAction.java | 68 -- .../ui/RemovePotentialMatchesAction.java | 125 --- .../internal/ui/RemoveResultAction.java | 101 --- .../internal/ui/ResourceToItemsMapper.java | 175 ----- .../eclipse/search/internal/ui/ScopePart.java | 4 +- .../eclipse/search/internal/ui/Search.java | 214 ------ .../search/internal/ui/SearchAgainAction.java | 35 - .../internal/ui/SearchDropDownAction.java | 103 --- .../search/internal/ui/SearchManager.java | 501 ------------ .../search/internal/ui/SearchMessages.java | 1 - .../internal/ui/SearchMessages.properties | 9 +- .../search/internal/ui/SearchPlugin.java | 36 - .../ui/SearchResultLabelProvider.java | 85 --- .../search/internal/ui/SearchResultView.java | 341 --------- .../internal/ui/SearchResultViewEntry.java | 211 ----- .../SearchResultViewEntryAdapterFactory.java | 65 -- .../internal/ui/SearchResultViewer.java | 721 ------------------ .../internal/ui/ShowNextResultAction.java | 40 - .../internal/ui/ShowPreviousResultAction.java | 40 - .../search/internal/ui/ShowSearchAction.java | 45 -- .../internal/ui/ShowSearchesAction.java | 144 ---- .../internal/ui/SortDropDownAction.java | 226 ------ .../internal/ui/text/FileSearchPage.java | 31 +- .../internal/ui/text/FileSearchQuery.java | 94 +-- .../ui/text/FileTreeContentProvider.java | 23 +- .../search/internal/ui/text/LineElement.java | 24 +- .../internal/ui/util/FileLabelProvider.java | 145 ---- .../search/ui/IActionGroupFactory.java | 34 +- .../search/ui/IContextMenuContributor.java | 28 +- .../search/ui/IGroupByKeyComputer.java | 18 +- .../eclipse/search/ui/ISearchResultView.java | 27 +- .../search/ui/ISearchResultViewEntry.java | 22 +- .../org/eclipse/search/ui/SearchUI.java | 20 +- .../META-INF/MANIFEST.MF | 3 +- .../build.properties | 9 +- org.eclipse.text.quicksearch.tests/pom.xml | 46 -- .../META-INF/MANIFEST.MF | 2 +- .../internal/ui/QuickSearchDialog.java | 12 +- .../internal/ui/messages.properties | 10 +- org.eclipse.text.releng/platformText.setup | 23 +- org.eclipse.text.tests/META-INF/MANIFEST.MF | 2 +- org.eclipse.text.tests/build.properties | 4 + org.eclipse.text.tests/pom.xml | 28 - org.eclipse.text/META-INF/MANIFEST.MF | 2 +- .../jface/text/AbstractLineTracker.java | 170 ++++- .../text/IDocumentPartitioningListener.java | 3 +- .../jface/text/source/AnnotationModel.java | 27 +- .../jface/text/source/IAnnotationMap.java | 2 +- .../META-INF/MANIFEST.MF | 9 +- org.eclipse.ui.editors.tests/build.properties | 6 + org.eclipse.ui.editors.tests/pom.xml | 49 -- .../ui/editors/tests/CaseActionTest.java | 112 +++ .../ui/editors/tests/EditorsTestSuite.java | 12 +- .../tests/TextMultiCaretNavigationTest.java | 129 ++++ .../TextMultiCaretSelectionCommandsTest.java | 684 +++++++++++++++++ .../ui/editors/tests/TextNavigationTest.java | 172 +++++ org.eclipse.ui.editors/META-INF/MANIFEST.MF | 2 +- org.eclipse.ui.editors/plugin.properties | 1 + org.eclipse.ui.editors/plugin.xml | 4 + .../ui/editors/text/DocumentReader.java | 6 + .../quickdiff/LastSaveReferenceProvider.java | 17 +- .../TextEditorDefaultsPreferencePage.java | 12 +- .../javaeditor/util/JavaColorProvider.java | 12 +- .../META-INF/MANIFEST.MF | 2 +- .../templateeditor/editors/ColorManager.java | 8 +- .../editors/TemplateEditor.java | 8 +- .../META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 2 +- .../build.properties | 6 + org.eclipse.ui.genericeditor.tests/pom.xml | 49 -- .../tests/GenericEditorTestSuite.java | 1 + .../tests/ShowInformationTest.java | 170 +++++ .../META-INF/MANIFEST.MF | 2 +- .../ExtensionBasedTextEditor.java | 114 ++- ...ExtensionBasedTextViewerConfiguration.java | 204 ++++- .../GenericEditorContentAssistant.java | 1 + .../ui/internal/genericeditor/Messages.java | 6 +- .../compare/GenericEditorMergeViewer.java | 29 +- .../folding/IndentFoldingStrategy.java | 2 +- .../genericeditor/messages.properties | 5 +- .../META-INF/MANIFEST.MF | 2 +- .../build.properties | 6 + .../pom.xml | 49 -- .../META-INF/MANIFEST.MF | 4 +- .../plugin.properties | 17 +- .../plugin.xml | 112 +++ .../texteditor/ToMultiSelectionHandler.java | 62 ++ .../AbstractMultiSelectionHandler.java | 462 +++++++++++ .../AddAllMatchesToMultiSelectionHandler.java | 40 + .../multiselection/MultiCaretDownHandler.java | 59 ++ .../multiselection/MultiCaretUpHandler.java | 59 ++ .../MultiSelectionDownHandler.java | 56 ++ .../MultiSelectionUpHandler.java | 56 ++ .../StopMultiSelectionHandler.java | 37 + .../ui/texteditor/AbstractTextEditor.java | 288 +++---- .../org/eclipse/ui/texteditor/CaseAction.java | 46 +- .../eclipse/ui/texteditor/EditorMessages.java | 3 + .../ui/texteditor/EditorMessages.properties | 3 + .../ui/texteditor/FindReplaceDialog.java | 121 ++- .../ui/texteditor/FindReplaceTarget.java | 11 +- .../eclipse/ui/texteditor/ResourceAction.java | 12 +- .../spelling/SpellingReconcileStrategy.java | 2 +- .../ui/texteditor/templates/ColumnLayout.java | 4 +- pom.xml | 6 +- tests-pom/pom.xml | 24 - 209 files changed, 6444 insertions(+), 5921 deletions(-) delete mode 100644 .gitignore delete mode 100644 CONTRIBUTING delete mode 100644 NOTICE delete mode 100644 org.eclipse.core.filebuffers.tests/pom.xml create mode 100644 org.eclipse.core.filebuffers.tests/src/org/eclipse/core/filebuffers/tests/ConvertLineDelemiterTest.java delete mode 100644 org.eclipse.jface.text.examples/pom.xml create mode 100644 org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java create mode 100644 org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java delete mode 100644 org.eclipse.jface.text.tests/pom.xml create mode 100644 org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/FindReplaceDocumentAdapterContentProposalProviderTest.java create mode 100644 org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java create mode 100644 org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/ProjectionViewerTest.java create mode 100644 org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/FastAbstractReconcilerTest.java delete mode 100644 org.eclipse.jface.text/src/org/eclipse/jface/internal/text/link/contentassist/LineBreakingReader.java create mode 100644 org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension4.java create mode 100644 org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java create mode 100644 org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java create mode 100644 org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java delete mode 100644 org.eclipse.search.tests/pom.xml delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/GotoMarkerAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllResultsAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllSearchesAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveMatchAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/RemovePotentialMatchesAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveResultAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/ResourceToItemsMapper.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/Search.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchAgainAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDropDownAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchManager.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultLabelProvider.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultView.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntry.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntryAdapterFactory.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewer.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/ShowNextResultAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/ShowPreviousResultAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchesAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/SortDropDownAction.java delete mode 100644 org.eclipse.search/search/org/eclipse/search/internal/ui/util/FileLabelProvider.java delete mode 100644 org.eclipse.text.quicksearch.tests/pom.xml delete mode 100644 org.eclipse.text.tests/pom.xml delete mode 100644 org.eclipse.ui.editors.tests/pom.xml create mode 100644 org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/CaseActionTest.java create mode 100644 org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java create mode 100644 org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java create mode 100644 org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java delete mode 100644 org.eclipse.ui.genericeditor.tests/pom.xml create mode 100644 org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/ShowInformationTest.java delete mode 100644 org.eclipse.ui.workbench.texteditor.tests/pom.xml create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java create mode 100644 org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java delete mode 100644 tests-pom/pom.xml diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 224e7f0..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.pc/ diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index cfc4f26..c1ea2a7 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -1,8 +1,9 @@ + - - org.eclipse.tycho.extras - tycho-pomless - 2.4.0 - + + org.eclipse.tycho + tycho-build + 3.0.0 + \ No newline at end of file diff --git a/CONTRIBUTING b/CONTRIBUTING deleted file mode 100644 index 8be8ef1..0000000 --- a/CONTRIBUTING +++ /dev/null @@ -1,71 +0,0 @@ -# Contributing to Eclipse Platform - -Thanks for your interest in this project. - -## Project description - -Eclipse Platform defines the set of frameworks and common services that -collectively make up infrastructure required to support the use of Eclipse as a -component model, as a Rich Client Platform (RCP) and as a comprehensive tool -integration platform. These services and frameworks include a standard workbench -user interface model and portable native widget toolkit, a project model for -managing resources, automatic resource delta management for incremental -compilers and builders, language-independent debug infrastructure, and -infrastructure for distributed multi-user versioned resource management. - -* https://projects.eclipse.org/projects/eclipse.platform - -## Developer resources - -Information regarding source code management, builds, coding standards, and -more. - -* https://projects.eclipse.org/projects/eclipse.platform/developer - -The project maintains the following source code repositories - -* http://git.eclipse.org/c/platform/eclipse.platform.common.git -* http://git.eclipse.org/c/platform/eclipse.platform.debug.git -* http://git.eclipse.org/c/platform/eclipse.platform.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.aggregator.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.buildtools.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.git -* http://git.eclipse.org/c/platform/eclipse.platform.resources.git -* http://git.eclipse.org/c/platform/eclipse.platform.runtime.git -* http://git.eclipse.org/c/platform/eclipse.platform.swt.git -* http://git.eclipse.org/c/platform/eclipse.platform.swt.binaries.git -* http://git.eclipse.org/c/platform/eclipse.platform.team.git -* http://git.eclipse.org/c/platform/eclipse.platform.text.git -* http://git.eclipse.org/c/platform/eclipse.platform.ua.git -* http://git.eclipse.org/c/platform/eclipse.platform.ui.git -* http://git.eclipse.org/c/platform/eclipse.platform.ui.tools.git - -This project uses Bugzilla to track ongoing development and issues. - -* Search for issues: https://eclipse.org/bugs/buglist.cgi?product=Platform -* Create a new report: https://eclipse.org/bugs/enter_bug.cgi?product=Platform - -Be sure to search for existing bugs before you create another one. Remember that -contributions are always welcome! - -## Eclipse Contributor Agreement - -Before your contribution can be accepted by the project team contributors must -electronically sign the Eclipse Contributor Agreement (ECA). - -* http://www.eclipse.org/legal/ECA.php - -Commits that are provided by non-committers must have a Signed-off-by field in -the footer indicating that the author is aware of the terms by which the -contribution has been provided to the project. The non-committer must -additionally have an Eclipse Foundation account and must have a signed Eclipse -Contributor Agreement (ECA) on file. - -For more information, please see the Eclipse Committer Handbook: -https://www.eclipse.org/projects/handbook/#resources-commit - -## Contact - -Contact the project developers via the project's "dev" list. - -* https://dev.eclipse.org/mailman/listinfo/platform-dev \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 37ada0b..906503b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,20 +2,16 @@ pipeline { options { timeout(time: 40, unit: 'MINUTES') buildDiscarder(logRotator(numToKeepStr:'5')) + disableConcurrentBuilds(abortPrevious: true) } agent { label "centos-7" } tools { maven 'apache-maven-latest' - jdk 'openjdk-jdk11-latest' + jdk 'openjdk-jdk17-latest' } stages { - stage('initialize Gerrit review') { - steps { - gerritReview labels: [Verified: 0], message: "Build started $BUILD_URL" - } - } stage('Build') { steps { wrap([$class: 'Xvnc', useXauthority: true]) { @@ -24,7 +20,9 @@ pipeline { mvn clean verify --batch-mode --fail-at-end -Dmaven.repo.local=$WORKSPACE/.m2/repository \ -Pbuild-individual-bundles -Pbree-libs -Papi-check \ -Dcompare-version-with-baselines.skip=false \ - -Dproject.build.sourceEncoding=UTF-8 + -Dproject.build.sourceEncoding=UTF-8 \ + -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss.SSS \ + -DtrimStackTrace=false """ } } @@ -34,37 +32,7 @@ pipeline { junit '**/target/surefire-reports/TEST-*.xml' publishIssues issues:[scanForIssues(tool: java()), scanForIssues(tool: mavenConsole())] } - unstable { - gerritReview labels: [Verified: -1], message: "Build UNSTABLE (test failures) $BUILD_URL" - } - failure { - gerritReview labels: [Verified: -1], message: "Build FAILED $BUILD_URL" - } - } - } - stage('Check freeze period') { - when { - not { - branch 'master' - } } - steps { - sh "wget https://git.eclipse.org/c/platform/eclipse.platform.releng.aggregator.git/plain/scripts/verifyFreezePeriod.sh" - sh "chmod +x verifyFreezePeriod.sh" - withCredentials([string(credentialsId: 'google-api-key', variable: 'GOOGLE_API_KEY')]) { - sh './verifyFreezePeriod.sh' - } - } - post { - failure { - gerritReview labels: [Verified: -1], message: "Build and test are OK, but Eclipse project is currently in a code freeze period.\nPlease wait for end of code freeze period before merging.\n $BUILD_URL" - } - } - } - } - post { - success { - gerritReview labels: [Verified: 1], message: "Build Succcess $BUILD_URL" } } } diff --git a/NOTICE b/NOTICE deleted file mode 100644 index f906f8b..0000000 --- a/NOTICE +++ /dev/null @@ -1,43 +0,0 @@ -# Notices for Eclipse Platform - -This content is produced and maintained by the Eclipse Platform project. - -* Project home: https://projects.eclipse.org/projects/eclipse.platform - -## Trademarks - -Eclipse Platform is a trademark of the Eclipse Foundation. - -## Copyright - -All content is the property of the respective authors or their employers. For -more information regarding authorship of content, please consult the listed -source code repository logs. - -## Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Eclipse Public License v. 2.0 which is available at -http://www.eclipse.org/legal/epl-2.0. - -SPDX-License-Identifier: EPL-2.0 - -## Source Code - -The project maintains the following source code repositories: - -* http://git.eclipse.org/c/platform/eclipse.platform.common.git -* http://git.eclipse.org/c/platform/eclipse.platform.debug.git -* http://git.eclipse.org/c/platform/eclipse.platform.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.aggregator.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.buildtools.git -* http://git.eclipse.org/c/platform/eclipse.platform.releng.git -* http://git.eclipse.org/c/platform/eclipse.platform.resources.git -* http://git.eclipse.org/c/platform/eclipse.platform.runtime.git -* http://git.eclipse.org/c/platform/eclipse.platform.swt.git -* http://git.eclipse.org/c/platform/eclipse.platform.swt.binaries.git -* http://git.eclipse.org/c/platform/eclipse.platform.team.git -* http://git.eclipse.org/c/platform/eclipse.platform.text.git -* http://git.eclipse.org/c/platform/eclipse.platform.ua.git -* http://git.eclipse.org/c/platform/eclipse.platform.ui.git -* http://git.eclipse.org/c/platform/eclipse.platform.ui.tools.git \ No newline at end of file diff --git a/debian/build.xml b/debian/build.xml index f078b8e..c3edbfa 100644 --- a/debian/build.xml +++ b/debian/build.xml @@ -20,7 +20,7 @@ - + diff --git a/debian/changelog b/debian/changelog index 2b202d9..9d1941e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +eclipse-platform-text (4.26-1) unstable; urgency=medium + + * New upstream release + - Depend on libswt-gtk-4-java (>= 4.21) + - Build org.eclipse.jface.text with Java 11 + * Standards-Version updated to 4.6.1 + * Track and download the new releases from GitHub + + -- Emmanuel Bourg Thu, 08 Dec 2022 19:17:54 +0100 + eclipse-platform-text (4.21-1) unstable; urgency=medium * New upstream release diff --git a/debian/control b/debian/control index 11cbea0..fc36de0 100644 --- a/debian/control +++ b/debian/control @@ -27,9 +27,9 @@ Build-Depends: libequinox-preferences-java, libequinox-registry-java, libicu4j-java, - libswt-gtk-4-java (>= 4.10), + libswt-gtk-4-java (>= 4.21), eclipse-debian-helper -Standards-Version: 4.6.0.1 +Standards-Version: 4.6.1 Vcs-Git: https://salsa.debian.org/java-team/eclipse-platform-text.git Vcs-Browser: https://salsa.debian.org/java-team/eclipse-platform-text Homepage: http://www.eclipse.org/eclipse/platform-text/ diff --git a/debian/copyright b/debian/copyright index 2fbd709..b514493 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Eclipse Platform Text -Source: https://git.eclipse.org/r/platform/eclipse.platform.text +Source: https://github.com/eclipse-platform/eclipse.platform.text Files-Excluded: *.so *.a diff --git a/debian/watch b/debian/watch index b6100bd..c5ffaa8 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ version=4 opts="mode=git,repack,compression=xz,uversionmangle=s/_/./g" \ -https://git.eclipse.org/r/platform/eclipse.platform.text refs/tags/R([\d_]+[az]?) +https://github.com/eclipse-platform/eclipse.platform.text refs/tags/R([\d_]+[az]?) diff --git a/org.eclipse.core.filebuffers.tests/META-INF/MANIFEST.MF b/org.eclipse.core.filebuffers.tests/META-INF/MANIFEST.MF index b7f97e6..86cfeec 100644 --- a/org.eclipse.core.filebuffers.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.core.filebuffers.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.core.filebuffers.tests;singleton:=true -Bundle-Version: 3.12.100.qualifier +Bundle-Version: 3.12.300.qualifier Bundle-Activator: org.eclipse.core.filebuffers.tests.FileBuffersTestPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %Plugin.providerName diff --git a/org.eclipse.core.filebuffers.tests/build.properties b/org.eclipse.core.filebuffers.tests/build.properties index 0d0b7c8..806c2a3 100644 --- a/org.eclipse.core.filebuffers.tests/build.properties +++ b/org.eclipse.core.filebuffers.tests/build.properties @@ -23,3 +23,7 @@ bin.includes = plugin.properties,\ src.includes = about.html source.. = src/ + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.core.filebuffers.tests.FileBuffersTestSuite diff --git a/org.eclipse.core.filebuffers.tests/pom.xml b/org.eclipse.core.filebuffers.tests/pom.xml deleted file mode 100644 index 2f80c7f..0000000 --- a/org.eclipse.core.filebuffers.tests/pom.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.core - org.eclipse.core.filebuffers.tests - 3.12.100-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.core.filebuffers.tests.FileBuffersTestSuite - - diff --git a/org.eclipse.core.filebuffers.tests/src/org/eclipse/core/filebuffers/tests/ConvertLineDelemiterTest.java b/org.eclipse.core.filebuffers.tests/src/org/eclipse/core/filebuffers/tests/ConvertLineDelemiterTest.java new file mode 100644 index 0000000..c3cad09 --- /dev/null +++ b/org.eclipse.core.filebuffers.tests/src/org/eclipse/core/filebuffers/tests/ConvertLineDelemiterTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2022 Joerg Kubitz and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.filebuffers.tests; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.function.Function; + +import org.junit.Test; + + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.manipulation.ConvertLineDelimitersOperation; +import org.eclipse.core.filebuffers.manipulation.FileBufferOperationRunner; + +public class ConvertLineDelemiterTest { + + private static final String[] DELIMS= new String[] { + "\r", + "\n", + "\r\n", + }; + + @Test + public void testWithDelimnAtEnd() throws Exception { + test(delim -> delim + "line1" + delim + "line2" + delim + delim + "line3" + delim); + } + + @Test + public void testWithoutDelimnAtEnd() throws Exception { + test(delim -> "line1" + delim + "line2" + delim + delim + "line3"); + } + + void test(Function testFile) throws Exception { + IProject p= ResourcesPlugin.getWorkspace().getRoot().getProject("ConvertLineDelemiterTest"); + p.create(null); + p.open(null); + try { + for (String outputDelim : DELIMS) { + IFile[] files= new IFile[DELIMS.length]; + int i= 0; + for (String inputDelim : DELIMS) { + String input= testFile.apply(inputDelim); + IFile file= ResourcesPlugin.getWorkspace().getRoot().getFile(new Path("/ConvertLineDelemiterTest/test" + i + ".txt")); + InputStream s= new ByteArrayInputStream(input.getBytes()); + file.create(s, true, null); + files[i++]= file; + } + FileBufferOperationRunner runner= new FileBufferOperationRunner(FileBuffers.getTextFileBufferManager(), null); + ConvertLineDelimitersOperation op= new ConvertLineDelimitersOperation(outputDelim); + runner.execute(Arrays.stream(files).map(f -> f.getFullPath()).toArray(IPath[]::new), op, null); + for (IFile file : files) { + String actual= Files.readString(file.getLocation().toFile().toPath()); + String expected= testFile.apply(outputDelim); + assertEquals(readable(expected), readable(actual)); + } + for (IFile file : files) { + file.delete(true, null); + } + } + } finally { + p.delete(true, null); + } + } + + private String readable(String s) { + s= s.replace("\r", "\\r"); + s= s.replace("\n", "\\n"); + return s; + } +} diff --git a/org.eclipse.core.filebuffers/META-INF/MANIFEST.MF b/org.eclipse.core.filebuffers/META-INF/MANIFEST.MF index 25c777b..a288957 100644 --- a/org.eclipse.core.filebuffers/META-INF/MANIFEST.MF +++ b/org.eclipse.core.filebuffers/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.filebuffers; singleton:=true -Bundle-Version: 3.7.0.qualifier +Bundle-Version: 3.7.200.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/filebuffers/manipulation/ConvertLineDelimitersOperation.java b/org.eclipse.core.filebuffers/src/org/eclipse/core/filebuffers/manipulation/ConvertLineDelimitersOperation.java index 32779c3..54ba2d9 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/filebuffers/manipulation/ConvertLineDelimitersOperation.java +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/filebuffers/manipulation/ConvertLineDelimitersOperation.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,16 +10,18 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Joerg Kubitz - rewrite implementation *******************************************************************************/ package org.eclipse.core.filebuffers.manipulation; +import java.util.Objects; + import org.eclipse.core.internal.filebuffers.FileBuffersPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.filebuffers.IFileBufferStatusCodes; import org.eclipse.core.filebuffers.ITextFileBuffer; @@ -29,11 +31,9 @@ import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentRewriteSessionType; import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; /** - * A text file buffer operation that changes the line delimiters to a specified - * line delimiter. + * A text file buffer operation that changes the line delimiters to a specified line delimiter. * * @since 3.1 */ @@ -42,8 +42,7 @@ public class ConvertLineDelimitersOperation extends TextFileBufferOperation { private String fLineDelimiter; /** - * Creates a new line delimiter conversion operation for the given target - * delimiter. + * Creates a new line delimiter conversion operation for the given target delimiter. * * @param lineDelimiter the target line delimiter */ @@ -56,24 +55,24 @@ public ConvertLineDelimitersOperation(String lineDelimiter) { protected MultiTextEditWithProgress computeTextEdit(ITextFileBuffer fileBuffer, IProgressMonitor progressMonitor) throws CoreException { IDocument document= fileBuffer.getDocument(); int lineCount= document.getNumberOfLines(); - - SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.ConvertLineDelimitersOperation_task_generatingChanges, lineCount); try { - - MultiTextEditWithProgress multiEdit= new MultiTextEditWithProgress(FileBuffersMessages.ConvertLineDelimitersOperation_task_applyingChanges); - + String original= document.get(); + int newLengthGuess= original.length() + (fLineDelimiter.length() - 1) * lineCount; + StringBuilder sb= new StringBuilder(newLengthGuess); for (int i= 0; i < lineCount; i++) { - final String delimiter= document.getLineDelimiter(i); - if (delimiter != null && !delimiter.isEmpty() && !delimiter.equals(fLineDelimiter)) { - IRegion region= document.getLineInformation(i); - multiEdit.addChild(new ReplaceEdit(region.getOffset() + region.getLength(), delimiter.length(), fLineDelimiter)); + int offset= document.getLineOffset(i); + int length= document.getLineLength(i); + String delim= document.getLineDelimiter(i); + sb.append(original, offset, offset + length - (delim == null ? 0 : delim.length())); + if (delim != null) { + sb.append(fLineDelimiter); } - subMonitor.split(1); } - - return multiEdit.getChildrenSize() <= 0 ? null : multiEdit; - - } catch (BadLocationException x) { + String replaced= sb.toString(); + MultiTextEditWithProgress multiEdit= new MultiTextEditWithProgress(FileBuffersMessages.ConvertLineDelimitersOperation_task_applyingChanges); + multiEdit.addChild(new ReplaceEdit(0, document.getLength(), replaced)); + return Objects.equals(replaced, original) ? null : multiEdit; + } catch (BadLocationException | IndexOutOfBoundsException x) { throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CONTENT_CHANGE_FAILED, "", x)); //$NON-NLS-1$ } } diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/DocumentReader.java b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/DocumentReader.java index 5eeb0da..7b4e0a4 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/DocumentReader.java +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/DocumentReader.java @@ -77,6 +77,12 @@ public CharSequence subSequence(int start, int end) { throw new IndexOutOfBoundsException(x.getLocalizedMessage()); } } + + /** @see CharSequence#toString **/ + @Override + public String toString() { + return fDocument.get(); + } } /** diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.java b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.java index d64141f..0f834ab 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.java +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.java @@ -41,6 +41,7 @@ private FileBuffersMessages() { public static String ResourceTextFileBuffer_error_unsupported_encoding_message_arg; public static String ResourceTextFileBuffer_error_illegal_encoding_message_arg; public static String ResourceTextFileBuffer_task_saving; + public static String ResourceTextFileBuffer_oom_on_file_read; public static String ResourceFileBuffer_task_creatingFileBuffer; public static String JavaTextFileBuffer_error_closeStream; public static String TextFileBufferManager_error_documentSetupFailed; diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.properties b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.properties index 1f05f68..8acfe38 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.properties +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileBuffersMessages.properties @@ -28,8 +28,9 @@ FileBufferManager_error_canNotCreateFilebuffer= Cannot create file buffer. ResourceTextFileBuffer_error_illegal_encoding_message_arg= Character encoding "{0}" is not a legal character encoding. ResourceTextFileBuffer_error_unsupported_encoding_message_arg= Character encoding "{0}" is not supported by this platform. -ResourceTextFileBuffer_error_charset_mapping_failed_message_arg=Some characters cannot be mapped using "{0}" character encoding.\nEither change the encoding or remove the characters which are not supported by the "{0}" character encoding. +ResourceTextFileBuffer_error_charset_mapping_failed_message_arg=Some characters cannot be mapped using "{0}" character encoding for file "{1}".\nEither change the encoding or remove the characters which are not supported by the "{0}" character encoding. ResourceTextFileBuffer_task_saving= Saving +ResourceTextFileBuffer_oom_on_file_read=OutOfMemoryError occurred while reading file "{0}". ResourceFileBuffer_task_creatingFileBuffer=creating file buffer diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileStoreTextFileBuffer.java b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileStoreTextFileBuffer.java index 289de5d..acc254e 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileStoreTextFileBuffer.java +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/FileStoreTextFileBuffer.java @@ -33,6 +33,8 @@ import java.nio.charset.UnmappableCharacterException; import java.nio.charset.UnsupportedCharsetException; +import org.eclipse.osgi.util.NLS; + import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; @@ -532,9 +534,15 @@ private void setDocumentContent(IDocument document, IFileStore file, String enco StringBuilder buffer= new StringBuilder(BUFFER_SIZE); char[] readBuffer= new char[READER_CHUNK_SIZE]; int n= in.read(readBuffer); - while (n > 0) { - buffer.append(readBuffer, 0, n); - n= in.read(readBuffer); + try { + while (n > 0) { + buffer.append(readBuffer, 0, n); + n= in.read(readBuffer); + } + } catch (OutOfMemoryError e) { + // give the JVM a hint that it can free the big buffer right away + buffer= null; + throw new IOException(NLS.bind(FileBuffersMessages.ResourceTextFileBuffer_oom_on_file_read, file.toURI()), e); } document.set(buffer.toString()); diff --git a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/ResourceTextFileBuffer.java b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/ResourceTextFileBuffer.java index 69a3e4d..fc287de 100644 --- a/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/ResourceTextFileBuffer.java +++ b/org.eclipse.core.filebuffers/src/org/eclipse/core/internal/filebuffers/ResourceTextFileBuffer.java @@ -31,6 +31,8 @@ import java.nio.charset.UnmappableCharacterException; import java.nio.charset.UnsupportedCharsetException; +import org.eclipse.osgi.util.NLS; + import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; @@ -332,7 +334,7 @@ protected void commitFileBufferContent(IProgressMonitor monitor, boolean overwri stream= new ByteArrayInputStream(bytes, 0, byteBuffer.limit()); } catch (CharacterCodingException ex) { Assert.isTrue(ex instanceof UnmappableCharacterException); - String message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_charset_mapping_failed_message_arg, encoding); + String message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_charset_mapping_failed_message_arg, new Object[] {encoding,getLocation().toString()}); IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CHARSET_MAPPING_FAILED, message, ex); throw new CoreException(s); } @@ -514,9 +516,15 @@ private void setDocumentContent(IDocument document, IFile file, String encoding) StringBuilder buffer= new StringBuilder(BUFFER_SIZE); char[] readBuffer= new char[READER_CHUNK_SIZE]; int n= in.read(readBuffer); - while (n > 0) { - buffer.append(readBuffer, 0, n); - n= in.read(readBuffer); + try { + while (n > 0) { + buffer.append(readBuffer, 0, n); + n= in.read(readBuffer); + } + } catch (OutOfMemoryError e) { + // give the JVM a hint that it can free the big buffer right away + buffer= null; + throw new IOException(NLS.bind(FileBuffersMessages.ResourceTextFileBuffer_oom_on_file_read, file.getLocationURI()), e); } if (document instanceof IDocumentExtension4) diff --git a/org.eclipse.jface.text.examples/build.properties b/org.eclipse.jface.text.examples/build.properties index 34d2e4d..99f2f22 100644 --- a/org.eclipse.jface.text.examples/build.properties +++ b/org.eclipse.jface.text.examples/build.properties @@ -2,3 +2,7 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ . + +# Maven/Tycho pom model adjustments +# bundle not in baseline +pom.model.property.skipAPIAnalysis = true diff --git a/org.eclipse.jface.text.examples/pom.xml b/org.eclipse.jface.text.examples/pom.xml deleted file mode 100644 index 7b2def2..0000000 --- a/org.eclipse.jface.text.examples/pom.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - 4.0.0 - - eclipse.platform.text - eclipse.platform.text - 4.21.0-SNAPSHOT - - org.eclipse.core - org.eclipse.jface.text.examples - 1.1.0-SNAPSHOT - eclipse-plugin - - - true - - - diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java index a1bc647..20236a6 100644 --- a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java +++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/CodeMiningDemo.java @@ -49,14 +49,29 @@ public static void main(String[] args) throws Exception { ISourceViewer sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER); sourceViewer.setDocument( new Document("// Type class & new keyword and see references CodeMining\n" - + "// Name class with a number N to emulate Nms before resolving the references CodeMining \n\n" - + "class A\n" + "new A\n" + "new A\n\n" + "class 5\n" + "new 5\n" + "new 5\n" + "new 5"), + + "// Name class with a number N to emulate Nms before resolving the references CodeMining\n" + + "// Empty lines show a header annotating they're empty.\n" + + "// The word `echo` is echoed.\n" + + "// Lines containing `end` get an annotation at their end\n\n" + + "class A\n" // + + "new A\n" // + + "new A\n\n" // + + "code mining at end here\n" + + "code mining at end here with CRLF\r\n" + + "class 5\n" // + + "new 5\n" // + + "new 5\n" // + + "new 5\n"), new AnnotationModel()); // Add AnnotationPainter (required by CodeMining) addAnnotationPainter(sourceViewer); // Initialize codemining providers ((ISourceViewerExtension5) sourceViewer).setCodeMiningProviders(new ICodeMiningProvider[] { - new ClassReferenceCodeMiningProvider(), new ClassImplementationsCodeMiningProvider(), new ToEchoWithHeaderAndInlineCodeMiningProvider("class") }); + new ClassReferenceCodeMiningProvider(), // + new ClassImplementationsCodeMiningProvider(), // + new ToEchoWithHeaderAndInlineCodeMiningProvider("echo"), // + new EmptyLineCodeMiningProvider(), // + new EchoAtEndOfLineCodeMiningProvider()}); // Execute codemining in a reconciler MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() { diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java new file mode 100644 index 0000000..732881b --- /dev/null +++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EchoAtEndOfLineCodeMiningProvider.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2022, Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.examples.codemining; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.codemining.LineEndCodeMining; +import org.eclipse.swt.events.MouseEvent; + +public class EchoAtEndOfLineCodeMiningProvider extends AbstractCodeMiningProvider { + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, + IProgressMonitor monitor) { + IDocument document = viewer.getDocument(); + List res = new ArrayList<>(); + for (int i = 0; i < document.getNumberOfLines(); i++) { + try { + if (document.get(document.getLineOffset(i), document.getLineLength(i)).contains("end")) { + res.add(new LineEndCodeMining(document, i, this) { + @Override + public String getLabel() { + return "End of line"; + } + @Override + public boolean isResolved() { + return true; + } + @Override + public Consumer getAction() { + return e -> System.err.println(getLabel() + getPosition()); + } + }); + } + } catch (BadLocationException ex) { + ex.printStackTrace(); + } + } + return CompletableFuture.completedFuture(res); + } + + @Override + public void dispose() { + + } + +} diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java new file mode 100644 index 0000000..0645d44 --- /dev/null +++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/codemining/EmptyLineCodeMiningProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2022, Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.examples.codemining; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.codemining.LineHeaderCodeMining; + +public class EmptyLineCodeMiningProvider extends AbstractCodeMiningProvider { + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, + IProgressMonitor monitor) { + IDocument document = viewer.getDocument(); + List emptyLineHeaders = new ArrayList<>(); + for (int line = 0; line < document.getNumberOfLines(); line++) { + try { + if (document.getLineLength(line) == 1) { + emptyLineHeaders.add(new LineHeaderCodeMining(line, document, this) { + @Override + public String getLabel() { + return "Next line is empty"; + } + }); + } + } catch (BadLocationException ex) { + ex.printStackTrace(); + } + } + return CompletableFuture.completedFuture(emptyLineHeaders); + } + +} diff --git a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF index 2384b19..c331d91 100644 --- a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.jface.text.tests -Bundle-Version: 3.12.200.qualifier +Bundle-Version: 3.12.600.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: @@ -16,7 +16,7 @@ Export-Package: org.eclipse.jface.text.tests.templates.persistence, org.eclipse.jface.text.tests.util Require-Bundle: - org.eclipse.jface.text;bundle-version="[3.16.0,4.0.0)", + org.eclipse.jface.text;bundle-version="[3.20.0,4.0.0)", org.eclipse.jface;bundle-version="[3.5.0,4.0.0)", org.junit;bundle-version="4.12.0", org.eclipse.text.tests;bundle-version="[3.5.0,4.0.0)", diff --git a/org.eclipse.jface.text.tests/build.properties b/org.eclipse.jface.text.tests/build.properties index 4dff298..f41fc67 100644 --- a/org.eclipse.jface.text.tests/build.properties +++ b/org.eclipse.jface.text.tests/build.properties @@ -21,3 +21,9 @@ bin.includes = plugin.properties,\ src.includes = about.html source.. = src/ + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.jface.text.tests.JFaceTextTestSuite +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.jface.text.tests/pom.xml b/org.eclipse.jface.text.tests/pom.xml deleted file mode 100644 index 7f2fde1..0000000 --- a/org.eclipse.jface.text.tests/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.jface - org.eclipse.jface.text.tests - 3.12.200-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.jface.text.tests.JFaceTextTestSuite - - - - - org.eclipse.tycho - tycho-surefire-plugin - ${tycho.version} - - true - true - - - - eclipse-plugin - org.eclipse.equinox.event - 0.0.0 - - - - - - - - diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java index 94e9966..6426384 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 SAP SE and others. + * Copyright (c) 2020, 2021 SAP SE and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,6 +14,7 @@ package org.eclipse.jface.text.tests; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.junit.Test; @@ -41,6 +42,19 @@ public void testUnderscoreHandling() throws Exception { } } + @Test + public void testClickAtLineEnd() throws Exception { + String content= "Hello world\nhow are you"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy doubleClickStrategy= new TestSpecificDefaultTextDoubleClickStrategy(); + IRegion selection= doubleClickStrategy.findWord(document, 11); + assertNotNull("Should have selected a word", selection); + assertEquals("Unexpected selection", "world", document.get(selection.getOffset(), selection.getLength())); + selection= doubleClickStrategy.findWord(document, document.getLength()); + assertNotNull("Should have selected a word", selection); + assertEquals("Unexpected selection", "you", document.get(selection.getOffset(), selection.getLength())); + } + private static final class TestSpecificDefaultTextDoubleClickStrategy extends DefaultTextDoubleClickStrategy { @Override diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/FindReplaceDocumentAdapterContentProposalProviderTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/FindReplaceDocumentAdapterContentProposalProviderTest.java new file mode 100644 index 0000000..3046512 --- /dev/null +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/FindReplaceDocumentAdapterContentProposalProviderTest.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2022 Thomas Wolf and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +import org.eclipse.jface.fieldassist.IContentProposal; + +import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider; + +public class FindReplaceDocumentAdapterContentProposalProviderTest { + + private FindReplaceDocumentAdapterContentProposalProvider provider= new FindReplaceDocumentAdapterContentProposalProvider(true); + + private void assertProposal(IContentProposal[] proposals, String prefix, String replacement) { + IContentProposal match= null; + for (IContentProposal p : proposals) { + if (p.getLabel().startsWith(prefix)) { + match= p; + break; + } + } + assertNotNull("No proposal for " + prefix + " found", match); + assertEquals("Unexpected replacement", replacement, match.getContent()); + } + + @Test + public void testEmptyTextProposal() { + assertProposal(provider.getProposals("", 0), "\\r", "\\r"); + } + + @Test + public void testNonEmptyProposal() { + assertProposal(provider.getProposals("text", 3), "\\r", "\\r"); + } + + @Test + public void testBackslashOddProposal() { + assertProposal(provider.getProposals("te\\xt", 3), "\\r", "r"); + assertProposal(provider.getProposals("te\\\\\\xt", 5), "\\r", "r"); + } + + @Test + public void testBackslashEvenProposal() { + assertProposal(provider.getProposals("te\\\\xt", 4), "\\r", "\\r"); + assertProposal(provider.getProposals("te\\\\\\xt", 4), "\\r", "\\r"); + assertProposal(provider.getProposals("te\\\\\\\\xt", 6), "\\r", "\\r"); + } +} diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java index 29c3603..6ff49e9 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java @@ -25,6 +25,7 @@ import org.eclipse.jface.text.tests.contentassist.FilteringAsyncContentAssistTests; import org.eclipse.jface.text.tests.contentassist.IncrementalAsyncContentAssistTests; import org.eclipse.jface.text.tests.reconciler.AbstractReconcilerTest; +import org.eclipse.jface.text.tests.reconciler.FastAbstractReconcilerTest; import org.eclipse.jface.text.tests.rules.DefaultPartitionerTest; import org.eclipse.jface.text.tests.rules.DefaultPartitionerZeroLengthTest; import org.eclipse.jface.text.tests.rules.FastPartitionerTest; @@ -59,6 +60,7 @@ ContextInformationPresenterTest.class, AbstractReconcilerTest.class, + FastAbstractReconcilerTest.class, DefaultPartitionerTest.class, DefaultPartitionerZeroLengthTest.class, @@ -73,8 +75,10 @@ CodeMiningProjectionViewerTest.class, TabsToSpacesConverterTest.class, - DefaultTextDoubleClickStrategyTest.class, + MultiSelectionTest.class, + FindReplaceDocumentAdapterContentProposalProviderTest.class, + ProjectionViewerTest.class }) public class JFaceTextTestSuite { // see @SuiteClasses diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java new file mode 100644 index 0000000..1c77968 --- /dev/null +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.tests; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.junit.Ignore; +import org.junit.Test; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.text.edits.MalformedTreeException; + +import org.eclipse.jface.internal.text.SelectionProcessor; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.viewers.ISelection; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextViewer; +import org.eclipse.jface.text.tests.util.DisplayHelper; + +public class MultiSelectionTest { + + @Test + public void testSelectionProcessor() throws MalformedTreeException, BadLocationException { + Shell shell= new Shell(); + TextViewer textViewer= new TextViewer(shell, SWT.NONE); + String content = "ababa\nbaba"; + Document document= new Document(content); + List regions = new ArrayList<>(); + int index = 0; + while ((index = document.get().indexOf('a', index)) >= 0) { + regions.add(new Region(index, 1)); + index++; + } + textViewer.setDocument(document); + SelectionProcessor selectionProcessor = new SelectionProcessor(textViewer); + MultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()])); + assertEquals(2, selectionProcessor.getCoveredLines(selection)); + assertEquals("aaaaa", selectionProcessor.getText(selection)); + // + document.set(content); + selectionProcessor.doDelete(selection); + assertEquals("bb\nbb", document.get()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + // + document.set(content); + selectionProcessor.doBackspace(selection); + assertEquals("bb\nbb", document.get()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + // + document.set(content); + selectionProcessor.doReplace(selection, "cc"); + assertEquals("ccbccbcc\nbccbcc", document.get()); + assertArrayEquals(new IRegion[] { new Region(2, 0), new Region(5, 0), new Region(8, 0), new Region(12, 0), new Region(15, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + // + selection = new MultiTextSelection(document, regions.stream().map(region -> new Region(region.getOffset(), 0)).toArray(IRegion[]::new)); + document.set(content); + selectionProcessor.doDelete(selection); + assertEquals("bb\nbb", document.get()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + // + document.set(content); + selectionProcessor.doBackspace(selection); + assertEquals("aaa\naa", document.get()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + // + document.set(content); + selectionProcessor.doReplace(selection, "cc"); + assertEquals("ccabccabcca\nbccabcca", document.get()); + assertArrayEquals(new IRegion[] { new Region(2, 0), new Region(6, 0), new Region(10, 0), new Region(15, 0), new Region(19, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + } + + @Test + public void testCopyPaste() throws MalformedTreeException, BadLocationException { + Shell shell= new Shell(); + TextViewer textViewer= new TextViewer(shell, SWT.NONE); + String content= "ababa\nbaba"; + Document document= new Document(content); + List regions= new ArrayList<>(); + int index= 0; + while ((index= document.get().indexOf('a', index)) >= 0) { + regions.add(new Region(index, 0)); + index++; + } + textViewer.setDocument(document); + SelectionProcessor selectionProcessor= new SelectionProcessor(textViewer); + MultiTextSelection selection= new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()])); + // + AtomicInteger idx= new AtomicInteger(); + selectionProcessor.doReplace(selection, + Arrays.stream(selection.getRegions()).mapToInt(r -> idx.getAndIncrement()).mapToObj(Integer::toString).collect(Collectors.joining(System.lineSeparator()))); + assertEquals("0ab1ab2a\nb3ab4a", document.get()); + assertArrayEquals(new IRegion[] { new Region(1, 0), new Region(4, 0), new Region(7, 0), new Region(11, 0), new Region(14, 0) }, + ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions()); + } + + @Test + public void testBackspace() throws MalformedTreeException { + Shell shell= new Shell(); + TextViewer textViewer= new TextViewer(shell, SWT.NONE); + String content = "ababa\nbaba"; + Document document= new Document(content); + List regions = new ArrayList<>(); + int index = 0; + while ((index = document.get().indexOf('a', index)) >= 0) { + regions.add(new Region(index + 1, 0)); + index++; + } + textViewer.setDocument(document); + IMultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()])); + textViewer.setSelection(selection); + Event keyEvent = new Event(); + keyEvent.type = SWT.KeyDown; + keyEvent.widget = textViewer.getTextWidget(); + keyEvent.display = textViewer.getTextWidget().getDisplay(); + keyEvent.doit = true; + keyEvent.keyCode = SWT.BS; + keyEvent.character = 0; + textViewer.getTextWidget().notifyListeners(SWT.KeyDown, keyEvent); + assertEquals("bb\nbb", textViewer.getDocument().get()); + ISelection sel = textViewer.getSelection(); + assertTrue(sel instanceof IMultiTextSelection); + selection = (IMultiTextSelection)sel; + assertArrayEquals(new IRegion[] { + new Region(0, 0), + new Region(1, 0), + new Region(2, 0), + new Region(4, 0), + new Region(5, 0)}, + selection.getRegions()); + } + + @Test + @Ignore(value = "this is currently for manual testing") + public void testViewer() { + Shell shell= new Shell(); + Button b = new Button(shell, SWT.PUSH); + b.setText("Reset selection"); + TextViewer textViewer= new TextViewer(shell, SWT.NONE); + String content = "ababa\nbaba"; + Document document= new Document(content); + List regions = new ArrayList<>(); + int index = 0; + while ((index = document.get().indexOf('a', index)) >= 0) { + regions.add(new Region(index, 1)); + index++; + } + MultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()])); + textViewer.setDocument(document); + shell.setLayout(new GridLayout(1, false)); + GridDataFactory.fillDefaults().grab(true, false).applyTo(shell); + shell.pack(); + shell.setVisible(true); + b.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { + textViewer.setSelection(selection); + textViewer.getTextWidget().setFocus(); + })); + DisplayHelper.sleep(textViewer.getTextWidget().getDisplay(), 1000000); + } +} diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/ProjectionViewerTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/ProjectionViewerTest.java new file mode 100644 index 0000000..03c3773 --- /dev/null +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/ProjectionViewerTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2022 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.tests; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.jface.text.source.projection.IProjectionPosition; +import org.eclipse.jface.text.source.projection.ProjectionAnnotation; +import org.eclipse.jface.text.source.projection.ProjectionViewer; + +public class ProjectionViewerTest { + + private static final class ProjectionPosition extends Position implements IProjectionPosition { + + public ProjectionPosition(IDocument document) { + super(0, document.getLength()); + } + + @Override + public IRegion[] computeProjectionRegions(IDocument document) throws BadLocationException { + int firstNewLine= document.get().indexOf('\n'); + int secondNewLine= document.get().indexOf('\n', firstNewLine + 1); + return new IRegion[] { new Region(0, firstNewLine + 1), new Region(secondNewLine + 1, document.getLength() - secondNewLine - 1) }; + } + + @Override + public int computeCaptionOffset(IDocument document) throws BadLocationException { + return document.get().indexOf('\n') + 1; + } + + } + + @Test + public void testCopyPaste() { + Shell shell = new Shell(); + shell.setLayout(new FillLayout()); + ProjectionViewer viewer = new ProjectionViewer(shell, null, null, false, SWT.NONE); + Document document= new Document("/*\n * content\n */"); + viewer.setDocument(document, new AnnotationModel()); + viewer.enableProjection(); + viewer.getProjectionAnnotationModel().addAnnotation(new ProjectionAnnotation(false), new ProjectionPosition(document)); + shell.setVisible(true); + viewer.getTextOperationTarget().doOperation(ProjectionViewer.COLLAPSE_ALL); + viewer.getTextOperationTarget().doOperation(ITextOperationTarget.SELECT_ALL); + try { + assertEquals(document.get(), ((ITextSelection) viewer.getSelection()).getText()); + viewer.getTextOperationTarget().doOperation(ITextOperationTarget.COPY); + assertEquals(document.get(), new Clipboard(viewer.getTextWidget().getDisplay()).getContents(TextTransfer.getInstance())); + } finally { + shell.dispose(); + } + } +} diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextPresentationTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextPresentationTest.java index b749d85..eddcaa0 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextPresentationTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextPresentationTest.java @@ -89,7 +89,11 @@ private void setUpTextPresentation() { @After public void tearDown() { fColors.clear(); - fDisplay.dispose(); + if (!fDisplay.isDisposed()) { + for (Shell shell : fDisplay.getShells()) { + shell.dispose(); + } + } } private StyleRange createStyleRange(int start, int end, int style) { diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java index 9de19b4..0adf9d3 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/TextViewerTest.java @@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import org.junit.After; import org.junit.Assume; import org.junit.Rule; import org.junit.Test; @@ -31,6 +32,10 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.StyledTextContent; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; @@ -55,6 +60,7 @@ import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.jface.text.hyperlink.URLHyperlink; @@ -68,94 +74,87 @@ public class TextViewerTest { @Rule public ScreenshotOnFailureRule screenshotRule = new ScreenshotOnFailureRule(); + private Shell fShell; + + @After + public void tearDown() { + if (fShell != null && !fShell.isDisposed()) { + fShell.dispose(); + } + fShell= null; + } @Test public void testSetRedraw_Bug441827() throws Exception { - Shell shell= new Shell(); - try { - TextViewer textViewer= new TextViewer(shell, SWT.NONE); - Document document= new Document("abc"); - textViewer.setDocument(document); - int len= document.getLength(); - // Select the whole document with the caret at the beginning. - textViewer.setSelectedRange(len, -len); - assertEquals(0, textViewer.getSelectedRange().x); - assertEquals(len, textViewer.getSelectedRange().y); - assertEquals(0, textViewer.getTextWidget().getCaretOffset()); - textViewer.setRedraw(false); - textViewer.setRedraw(true); - // Check that the selection and the caret position are preserved. - assertEquals(0, textViewer.getSelectedRange().x); - assertEquals(len, textViewer.getSelectedRange().y); - assertEquals(0, textViewer.getTextWidget().getCaretOffset()); - } finally { - shell.dispose(); - } + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + Document document= new Document("abc"); + textViewer.setDocument(document); + int len= document.getLength(); + // Select the whole document with the caret at the beginning. + textViewer.setSelectedRange(len, -len); + assertEquals(0, textViewer.getSelectedRange().x); + assertEquals(len, textViewer.getSelectedRange().y); + assertEquals(0, textViewer.getTextWidget().getCaretOffset()); + textViewer.setRedraw(false); + textViewer.setRedraw(true); + // Check that the selection and the caret position are preserved. + assertEquals(0, textViewer.getSelectedRange().x); + assertEquals(len, textViewer.getSelectedRange().y); + assertEquals(0, textViewer.getTextWidget().getCaretOffset()); } @Test public void testCaretMoveChangesSelection() throws Exception { - Shell shell= new Shell(); - try { - TextViewer textViewer= new TextViewer(shell, SWT.NONE); - Document document= new Document("abc"); - textViewer.setDocument(document); - int len= document.getLength(); - // Select the whole document with the caret at the beginning. - textViewer.setSelectedRange(0, len); - ITextSelection selection = (ITextSelection)textViewer.getSelectionProvider().getSelection(); - assertEquals(0, selection.getOffset()); - assertEquals(len, selection.getLength()); - textViewer.getTextWidget().setCaretOffset(1); - selection = (ITextSelection)textViewer.getSelectionProvider().getSelection(); - assertEquals(1, selection.getOffset()); - assertEquals(0, selection.getLength()); - } finally { - shell.dispose(); - } + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + Document document= new Document("abc"); + textViewer.setDocument(document); + int len= document.getLength(); + // Select the whole document with the caret at the beginning. + textViewer.setSelectedRange(0, len); + ITextSelection selection= (ITextSelection) textViewer.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(len, selection.getLength()); + textViewer.getTextWidget().setCaretOffset(1); + selection= (ITextSelection) textViewer.getSelectionProvider().getSelection(); + assertEquals(1, selection.getOffset()); + assertEquals(0, selection.getLength()); } @Test public void testGetCachedSelection() throws Exception { - Shell shell= new Shell(); - try { - TextViewer textViewer= new TextViewer(shell, SWT.NONE); - Document document= new Document("abc"); - textViewer.setDocument(document); - int len= document.getLength(); - // Select the whole document with the caret at the beginning. - textViewer.setSelectedRange(0, len); - checkInAndOutUIThread(() -> { - ITextSelection selection = textViewer.getLastKnownSelection(); - assertEquals(0, selection.getOffset()); - assertEquals(len, selection.getLength()); - }); - } finally { - shell.dispose(); - } + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + Document document= new Document("abc"); + textViewer.setDocument(document); + int len= document.getLength(); + // Select the whole document with the caret at the beginning. + textViewer.setSelectedRange(0, len); + checkInAndOutUIThread(() -> { + ITextSelection selection= textViewer.getLastKnownSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(len, selection.getLength()); + }); } @Test public void testBlockSelectionAccessors() throws Exception { - Shell shell= new Shell(); - try { - ITextViewer textViewer= new TextViewer(shell, SWT.NONE); - Document document= new Document("0123\n4567\n89ab\ncdef"); - textViewer.setDocument(document); - // Select the whole document with the caret at the beginning. - StyledText textWidget= textViewer.getTextWidget(); - textWidget.setBlockSelection(true); - shell.setLayout(new FillLayout()); - shell.open(); - textViewer.getSelectionProvider().setSelection(new BlockTextSelection(textViewer.getDocument(), 1, 1, 2, 2, textWidget.getTabs())); - BlockTextSelection sel = (BlockTextSelection)textViewer.getSelectionProvider().getSelection(); - assertEquals(1, sel.getStartLine()); - assertEquals(2, sel.getEndLine()); - assertEquals(1, sel.getStartColumn()); - assertEquals(2, sel.getEndColumn()); - } finally { - shell.dispose(); - } + fShell= new Shell(); + ITextViewer textViewer= new TextViewer(fShell, SWT.NONE); + Document document= new Document("0123\n4567\n89ab\ncdef"); + textViewer.setDocument(document); + // Select the whole document with the caret at the beginning. + StyledText textWidget= textViewer.getTextWidget(); + textWidget.setBlockSelection(true); + fShell.setLayout(new FillLayout()); + fShell.open(); + textViewer.getSelectionProvider().setSelection(new BlockTextSelection(textViewer.getDocument(), 1, 1, 2, 2, textWidget.getTabs())); + BlockTextSelection sel= (BlockTextSelection) textViewer.getSelectionProvider().getSelection(); + assertEquals(1, sel.getStartLine()); + assertEquals(2, sel.getEndLine()); + assertEquals(1, sel.getStartColumn()); + assertEquals(2, sel.getEndColumn()); } @@ -186,54 +185,45 @@ private void checkInAndOutUIThread(Runnable r) throws InterruptedException { @Test public void testCtrlHomeViewportListener() { Assume.assumeFalse("See bug 541415. For whatever reason, this shortcut doesn't work on Mac", Util.isMac()); - Shell shell= new Shell(); - try { - shell.setLayout(new FillLayout()); - shell.setSize(500, 200); - SourceViewer textViewer= new SourceViewer(shell, null, SWT.NONE); - textViewer.setDocument(new Document(generate5000Lines())); - shell.open(); - textViewer.revealRange(4000, 1); - AtomicBoolean notifyHomeReached = new AtomicBoolean(); - ctrlEnd(textViewer); - DisplayHelper.sleep(textViewer.getTextWidget().getDisplay(), 1000); - textViewer.addViewportListener(offset -> notifyHomeReached.set(offset == 0)); - ctrlHome(textViewer); - assertTrue(new DisplayHelper() { - @Override - protected boolean condition() { - return notifyHomeReached.get(); - } - }.waitForCondition(textViewer.getTextWidget().getDisplay(), 3000)); - } finally { - shell.dispose(); - } + fShell= new Shell(); + fShell.setLayout(new FillLayout()); + fShell.setSize(500, 200); + SourceViewer textViewer= new SourceViewer(fShell, null, SWT.NONE); + textViewer.setDocument(new Document(generate5000Lines())); + fShell.open(); + textViewer.revealRange(4000, 1); + AtomicBoolean notifyHomeReached= new AtomicBoolean(); + ctrlEnd(textViewer); + DisplayHelper.sleep(textViewer.getTextWidget().getDisplay(), 1000); + textViewer.addViewportListener(offset -> notifyHomeReached.set(offset == 0)); + ctrlHome(textViewer); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return notifyHomeReached.get(); + } + }.waitForCondition(textViewer.getTextWidget().getDisplay(), 3000)); } @Test public void testCtrlEndViewportListener() { Assume.assumeFalse("See bug 541415. For whatever reason, this shortcut doesn't work on Mac", Util.isMac()); - Shell shell= new Shell(); - try { - shell.setLayout(new FillLayout()); - shell.setSize(500, 200); - SourceViewer textViewer= new SourceViewer(shell, null, SWT.NONE); - Document document= new Document(generate5000Lines()); - textViewer.setDocument(document); - shell.open(); - AtomicBoolean notifyEndReached = new AtomicBoolean(); - textViewer.addViewportListener(offset -> - notifyEndReached.set(offset > 4000)); - ctrlEnd(textViewer); - assertTrue(new DisplayHelper() { - @Override - protected boolean condition() { - return notifyEndReached.get(); - } - }.waitForCondition(textViewer.getControl().getDisplay(), 3000)); - } finally { - shell.dispose(); - } + fShell= new Shell(); + fShell.setLayout(new FillLayout()); + fShell.setSize(500, 200); + SourceViewer textViewer= new SourceViewer(fShell, null, SWT.NONE); + Document document= new Document(generate5000Lines()); + textViewer.setDocument(document); + fShell.open(); + AtomicBoolean notifyEndReached= new AtomicBoolean(); + textViewer.addViewportListener(offset -> notifyEndReached.set(offset > 4000)); + ctrlEnd(textViewer); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return notifyEndReached.get(); + } + }.waitForCondition(textViewer.getControl().getDisplay(), 3000)); } /** @@ -242,57 +232,53 @@ protected boolean condition() { */ @Test public void testDefaultContentImplementation() { - final Shell shell= new Shell(); + fShell= new Shell(); + final StyledTextContent content; try { - final StyledTextContent content; - try { - final TextViewer textViewer= new TextViewer(shell, SWT.NONE); - textViewer.setDocument(new Document()); - content= textViewer.getTextWidget().getContent(); - } catch (Exception ex) { - fail("Failed to obtain default instance of TextViewers document adapter. " + ex.getMessage()); - return; - } - assumeNotNull(content); - - final String line0= "Hello "; - final String line1= ""; - final String line2= "World!"; - final String text= line0 + "\n" + line1 + "\r\n" + line2; - content.setText(text); - assertEquals("Get text range failed.", "H", content.getTextRange(0, 1)); - assertEquals("Get text range failed.", "ll", content.getTextRange(2, 2)); - assertEquals("Adapter content length wrong.", text.length(), content.getCharCount()); - assertEquals("Adapter returned wrong content.", line0, content.getLine(0)); - assertEquals("Adapter returned wrong content.", line1, content.getLine(1)); - assertEquals("Adapter returned wrong content.", line2, content.getLine(2)); - - content.setText("\r\n\r\n"); - assertEquals("Wrong line for offset.", 0, content.getLineAtOffset(0)); - assertEquals("Wrong line for offset.", 0, content.getLineAtOffset(1)); - assertEquals("Wrong line for offset.", 1, content.getLineAtOffset(2)); - assertEquals("Wrong line for offset.", 1, content.getLineAtOffset(3)); - assertEquals("Wrong line for offset.", 2, content.getLineAtOffset(4)); - assertEquals("Wrong line for offset.", content.getLineCount() - 1, content.getLineAtOffset(content.getCharCount())); - - content.setText(null); - assertEquals("Adapter returned wrong line count.", 1, content.getLineCount()); - content.setText(""); - assertEquals("Adapter returned wrong line count.", 1, content.getLineCount()); - content.setText("a\n"); - assertEquals("Adapter returned wrong line count.", 2, content.getLineCount()); - content.setText("\n\n"); - assertEquals("Adapter returned wrong line count.", 3, content.getLineCount()); - - content.setText("\r\ntest\r\n"); - assertEquals("Wrong offset for line.", 0, content.getOffsetAtLine(0)); - assertEquals("Wrong offset for line.", 2, content.getOffsetAtLine(1)); - assertEquals("Wrong offset for line.", 8, content.getOffsetAtLine(2)); - content.setText(""); - assertEquals("Wrong offset for line.", 0, content.getOffsetAtLine(0)); - } finally { - shell.dispose(); + final TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + textViewer.setDocument(new Document()); + content= textViewer.getTextWidget().getContent(); + } catch (Exception ex) { + fail("Failed to obtain default instance of TextViewers document adapter. " + ex.getMessage()); + return; } + assumeNotNull(content); + + final String line0= "Hello "; + final String line1= ""; + final String line2= "World!"; + final String text= line0 + "\n" + line1 + "\r\n" + line2; + content.setText(text); + assertEquals("Get text range failed.", "H", content.getTextRange(0, 1)); + assertEquals("Get text range failed.", "ll", content.getTextRange(2, 2)); + assertEquals("Adapter content length wrong.", text.length(), content.getCharCount()); + assertEquals("Adapter returned wrong content.", line0, content.getLine(0)); + assertEquals("Adapter returned wrong content.", line1, content.getLine(1)); + assertEquals("Adapter returned wrong content.", line2, content.getLine(2)); + + content.setText("\r\n\r\n"); + assertEquals("Wrong line for offset.", 0, content.getLineAtOffset(0)); + assertEquals("Wrong line for offset.", 0, content.getLineAtOffset(1)); + assertEquals("Wrong line for offset.", 1, content.getLineAtOffset(2)); + assertEquals("Wrong line for offset.", 1, content.getLineAtOffset(3)); + assertEquals("Wrong line for offset.", 2, content.getLineAtOffset(4)); + assertEquals("Wrong line for offset.", content.getLineCount() - 1, content.getLineAtOffset(content.getCharCount())); + + content.setText(null); + assertEquals("Adapter returned wrong line count.", 1, content.getLineCount()); + content.setText(""); + assertEquals("Adapter returned wrong line count.", 1, content.getLineCount()); + content.setText("a\n"); + assertEquals("Adapter returned wrong line count.", 2, content.getLineCount()); + content.setText("\n\n"); + assertEquals("Adapter returned wrong line count.", 3, content.getLineCount()); + + content.setText("\r\ntest\r\n"); + assertEquals("Wrong offset for line.", 0, content.getOffsetAtLine(0)); + assertEquals("Wrong offset for line.", 2, content.getOffsetAtLine(1)); + assertEquals("Wrong offset for line.", 8, content.getOffsetAtLine(2)); + content.setText(""); + assertEquals("Wrong offset for line.", 0, content.getOffsetAtLine(0)); } public static void ctrlEnd(ITextViewer viewer) { @@ -332,47 +318,43 @@ public static String generate5000Lines() { @Test public void testShiftLeft() { - Shell shell= new Shell(); - try { - TextViewer textViewer= new TextViewer(shell, SWT.NONE); - { - // Normal case, both lines match prefix - Document document= new Document("//line1\n//line2"); - textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); - textViewer.setDocument(document); - textViewer.setDefaultPrefixes(new String[] { "//" }, IDocument.DEFAULT_CONTENT_TYPE); - - textViewer.doOperation(ITextOperationTarget.SELECT_ALL); - textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); - - assertEquals("line1\nline2", textViewer.getDocument().get()); - } - { - // Don't shift anything, as 2nd line does not match any prefix - Document document= new Document("//line1\nline2"); - textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); - textViewer.setDocument(document); - textViewer.setDefaultPrefixes(new String[] { "//" }, IDocument.DEFAULT_CONTENT_TYPE); + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + { + // Normal case, both lines match prefix + Document document= new Document("//line1\n//line2"); + textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); + textViewer.setDocument(document); + textViewer.setDefaultPrefixes(new String[] { "//" }, IDocument.DEFAULT_CONTENT_TYPE); - textViewer.doOperation(ITextOperationTarget.SELECT_ALL); - textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); + textViewer.doOperation(ITextOperationTarget.SELECT_ALL); + textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); - assertEquals("//line1\nline2", textViewer.getDocument().get()); - } - { - // Shift line1, since line2 matches the allowed empty prefix - Document document= new Document("//line1\nline2"); - textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); - textViewer.setDocument(document); - textViewer.setDefaultPrefixes(new String[] { "//", "" }, IDocument.DEFAULT_CONTENT_TYPE); + assertEquals("line1\nline2", textViewer.getDocument().get()); + } + { + // Don't shift anything, as 2nd line does not match any prefix + Document document= new Document("//line1\nline2"); + textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); + textViewer.setDocument(document); + textViewer.setDefaultPrefixes(new String[] { "//" }, IDocument.DEFAULT_CONTENT_TYPE); - textViewer.doOperation(ITextOperationTarget.SELECT_ALL); - textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); + textViewer.doOperation(ITextOperationTarget.SELECT_ALL); + textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); - assertEquals("line1\nline2", textViewer.getDocument().get()); - } - } finally { - shell.dispose(); + assertEquals("//line1\nline2", textViewer.getDocument().get()); + } + { + // Shift line1, since line2 matches the allowed empty prefix + Document document= new Document("//line1\nline2"); + textViewer.setDocumentPartitioning(IDocumentExtension3.DEFAULT_PARTITIONING); + textViewer.setDocument(document); + textViewer.setDefaultPrefixes(new String[] { "//", "" }, IDocument.DEFAULT_CONTENT_TYPE); + + textViewer.doOperation(ITextOperationTarget.SELECT_ALL); + textViewer.doOperation(ITextOperationTarget.STRIP_PREFIX); + + assertEquals("line1\nline2", textViewer.getDocument().get()); } } @@ -407,29 +389,79 @@ private void checkHyperlink(TextViewer textViewer, int pos, String text, String @Test public void testURLHyperlinkDetector() { - Shell shell = new Shell(); + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + checkHyperlink(textViewer, 3, "https://foo ", "[https://foo]"); + checkHyperlink(textViewer, 0, "", "[]"); + checkHyperlink(textViewer, 3, "https", "[]"); + checkHyperlink(textViewer, 3, "https://", "[]"); + checkHyperlink(textViewer, 3, "https:// ", "[]"); + checkHyperlink(textViewer, 3, "https:// foo", "[]"); + checkHyperlink(textViewer, 3, "https://foo bar", "[https://foo]"); + checkHyperlink(textViewer, 3, "\"https://\" foo bar", "[]"); + checkHyperlink(textViewer, 3, "\"https:// \" foo bar", "[]"); + checkHyperlink(textViewer, 3, "\"https:// foo\" bar", "[]"); + checkHyperlink(textViewer, 15, "https:// foo https://bar bar", "[https://bar]"); + checkHyperlink(textViewer, 24, "https:// foo https://bar bar", "[https://bar]"); + checkHyperlink(textViewer, 15, "", "[https://bugs.eclipse.org/bugs]"); + checkHyperlink(textViewer, 19, "", "[https://bugs.eclipse.org/bugs]"); + checkHyperlink(textViewer, 40, "Find more information at https://www.eclipse.org.", "[https://www.eclipse.org]"); + checkHyperlink(textViewer, 3, "http://... links should not be used anymore; use https://... instead.", "[]"); + checkHyperlink(textViewer, 50, "http://... links should not be used anymore; use https://... instead.", "[]"); + } - try { - TextViewer textViewer= new TextViewer(shell, SWT.NONE); - checkHyperlink(textViewer, 3, "https://foo ", "[https://foo]"); - checkHyperlink(textViewer, 0, "", "[]"); - checkHyperlink(textViewer, 3, "https", "[]"); - checkHyperlink(textViewer, 3, "https://", "[]"); - checkHyperlink(textViewer, 3, "https:// ", "[]"); - checkHyperlink(textViewer, 3, "https:// foo", "[]"); - checkHyperlink(textViewer, 3, "https://foo bar", "[https://foo]"); - checkHyperlink(textViewer, 3, "\"https://\" foo bar", "[]"); - checkHyperlink(textViewer, 3, "\"https:// \" foo bar", "[]"); - checkHyperlink(textViewer, 3, "\"https:// foo\" bar", "[]"); - checkHyperlink(textViewer, 15, "https:// foo https://bar bar", "[https://bar]"); - checkHyperlink(textViewer, 24, "https:// foo https://bar bar", "[https://bar]"); - checkHyperlink(textViewer, 15, "", "[https://bugs.eclipse.org/bugs]"); - checkHyperlink(textViewer, 19, "", "[https://bugs.eclipse.org/bugs]"); - checkHyperlink(textViewer, 40, "Find more information at https://www.eclipse.org.", "[https://www.eclipse.org]"); - checkHyperlink(textViewer, 3, "http://... links should not be used anymore; use https://... instead.", "[]"); - checkHyperlink(textViewer, 50, "http://... links should not be used anymore; use https://... instead.", "[]"); - } finally { - shell.dispose(); - } + @Test + public void testPasteMultiLines() { + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + Document document= new Document(); + textViewer.setDocument(document); + new Clipboard(fShell.getDisplay()).setContents(new Object[] { "a" + System.lineSeparator() + "a" }, new Transfer[] { TextTransfer.getInstance() }, DND.CLIPBOARD); + textViewer.doOperation(ITextOperationTarget.PASTE); + assertEquals("a" + System.lineSeparator() + "a", textViewer.getTextWidget().getText()); + // + document.set("a\na\na\nb"); + textViewer.setSelectedRange(0, 6); + new Clipboard(fShell.getDisplay()).setContents(new Object[] { "b" }, new Transfer[] { TextTransfer.getInstance() }, DND.CLIPBOARD); + textViewer.doOperation(ITextOperationTarget.PASTE); + assertEquals("bb", textViewer.getTextWidget().getText()); + } + + @Test + public void testSetSelectionNoDoc() { + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + textViewer.setSelection(TextSelection.emptySelection()); + // assert no exception is thrown + } + + @Test + public void testSelectionFromViewerState() { + fShell= new Shell(); + TextViewer textViewer= new TextViewer(fShell, SWT.NONE); + textViewer.setDocument(new Document( + "/**\n" + + " *\n" + + " * HEADER\n" + + " */\n" + + "package pack;\n" + + "\n" + + "public final class C {\n" + + " /** \n" + + "* javadoc\n" + + " */\n" + + " public void method() {\n" + + "/* a\n" + + "comment */\n" + + "int local;\n" + + " }\n" + + "}\n")); + textViewer.setSelectedRange(118, 0); + ITextSelection textSelection= (ITextSelection) textViewer.getSelection(); + assertEquals(118, textSelection.getOffset()); + textViewer.setRedraw(false); // switch to usage of ViewerState + textViewer.setSelectedRange(113, 15); + textSelection= (ITextSelection) textViewer.getSelection(); + assertEquals(113, textSelection.getOffset()); } } diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java index e4af1d0..493bf13 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningProjectionViewerTest.java @@ -13,10 +13,15 @@ */ package org.eclipse.jface.text.tests.codemining; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Assert; @@ -28,6 +33,8 @@ import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.eclipse.core.runtime.ILog; @@ -38,18 +45,24 @@ import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.codemining.ICodeMining; import org.eclipse.jface.text.codemining.ICodeMiningProvider; import org.eclipse.jface.text.codemining.LineContentCodeMining; +import org.eclipse.jface.text.contentassist.ContentAssistant; +import org.eclipse.jface.text.contentassist.IContentAssistant; import org.eclipse.jface.text.source.AnnotationPainter; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; +import org.eclipse.jface.text.tests.contentassist.BarContentAssistProcessor; import org.eclipse.jface.text.tests.source.inlined.LineContentBoundsDrawingTest.AccessAllAnnoations; import org.eclipse.jface.text.tests.util.DisplayHelper; @@ -82,7 +95,11 @@ public void dispose() { @Before public void setUp() { - fParent= new Shell(); + Shell[] shells= Display.getDefault().getShells(); + for (Shell shell : shells) { + shell.dispose(); + } + fParent= new Shell(SWT.ON_TOP); fParent.setSize(500, 200); fParent.setLayout(new FillLayout()); fViewer= new ProjectionViewer(fParent, null, null, false, SWT.NONE); @@ -108,6 +125,30 @@ public void tearDown() { fParent.dispose(); } + protected List getCurrentShells() { + return Arrays.stream(fParent.getDisplay().getShells()) + .filter(Shell::isVisible) + .collect(Collectors.toList()); + } + + protected List findNewShells(Collection beforeShells) { + return Arrays.stream(fParent.getDisplay().getShells()) + .filter(Shell::isVisible) + .filter(shell -> !beforeShells.contains(shell)) + .collect(Collectors.toList()); + } + + protected Shell findNewShell(Collection beforeShells) { + DisplayHelper.sleep(fParent.getDisplay(), 100); + List afterShells= findNewShells(beforeShells); + if (afterShells.isEmpty()) { + DisplayHelper.sleep(fParent.getDisplay(), 1000); + } + afterShells= findNewShells(beforeShells); + assertTrue("No new shell found, existing: " + beforeShells, afterShells.size() > beforeShells.size()); + return afterShells.get(0); + } + @Test public void testCollapse() throws Exception { fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { @@ -140,4 +181,52 @@ public void testCollapse() throws Exception { } } } + + @Test + public void testCodeMiningDoesntAlterFocus() { + fViewer.configure(new SourceViewerConfiguration() { + @Override + public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { + ContentAssistant contentAssistant= new ContentAssistant(true); + contentAssistant.addContentAssistProcessor(new BarContentAssistProcessor("1hi"), IDocument.DEFAULT_CONTENT_TYPE); + contentAssistant.addContentAssistProcessor(new BarContentAssistProcessor("1hello"), IDocument.DEFAULT_CONTENT_TYPE); + contentAssistant.setShowEmptyList(true); + return contentAssistant; + } + }); + final List beforeShells= getCurrentShells(); + fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { + new DelayedEchoCodeMiningProvider() + }); + fViewer.getDocument().set("1a\n2a\n3a\n4a\n5a\n6a\n"); + fParent.setSize(200, 4 * fViewer.getTextWidget().getLineHeight()); + //fParent.pack(true); + fParent.open(); + DisplayHelper.driveEventQueue(fParent.getDisplay()); + // ensure ViewportGuard is initialized + fViewer.getControl().notifyListeners(SWT.KeyUp, new Event()); + fViewer.setSelectedRange(1, 0); + fViewer.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS); + Display display= fParent.getDisplay(); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return display.getShells().length > beforeShells.size(); + } + }.waitForCondition(display, 1000)); + Shell completionShell= findNewShell(beforeShells); + // ↓ showing codeminings changing viewport while completion popup is open + fViewer.updateCodeMinings(); + assertTrue(new DisplayHelper() { + @Override + protected boolean condition() { + return fViewer.getTextWidget().getLineVerticalIndent(0) > 0; + } + }.waitForCondition(display, 3000)); + Event e= new Event(); + e.widget= fViewer.getTextWidget(); + e.keyCode= SWT.ARROW_DOWN; + fViewer.getTextWidget().notifyListeners(SWT.KeyDown, e); + assertTrue(completionShell.isVisible()); + } } diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java index 7975534..b32bfd7 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java @@ -14,7 +14,6 @@ package org.eclipse.jface.text.tests.contentassist; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Arrays; @@ -24,6 +23,7 @@ import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ST; @@ -31,6 +31,7 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; @@ -58,12 +59,24 @@ public class AbstractContentAssistTest { public AbstractContentAssistTest() { } + @Before + public void setUp() { + Shell[] shells= Display.getDefault().getShells(); + for (Shell s : shells) { + s.dispose(); + } + DisplayHelper.driveEventQueue(Display.getDefault()); + } @After public void close() { if (shell != null && !shell.isDisposed()) { shell.close(); } + Shell[] shells= Display.getDefault().getShells(); + for (Shell s : shells) { + s.dispose(); + } } @@ -87,12 +100,13 @@ protected void setupSourceViewer(ContentAssistant contentAssistant, String initi button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); shell.open(); + processEvents(); Assert.assertTrue(new DisplayHelper() { @Override protected boolean condition() { return viewer.getTextWidget().isVisible(); } - }.waitForCondition(shell.getDisplay(), 3000)); + }.waitForCondition(getDisplay(), 3000)); } protected SourceViewerConfiguration createSourceViewerConfiguration() { @@ -192,31 +206,35 @@ public Button getButton() { protected void processEvents() { - DisplayHelper.driveEventQueue(shell.getDisplay()); + DisplayHelper.driveEventQueue(getDisplay()); + } + + private Display getDisplay() { + return Display.getDefault(); } protected List getCurrentShells() { - return Arrays.stream(shell.getDisplay().getShells()) + return Arrays.stream(getDisplay().getShells()) .filter(Shell::isVisible) .collect(Collectors.toList()); } protected List findNewShells(Collection beforeShells) { - return Arrays.stream(shell.getDisplay().getShells()) + return Arrays.stream(getDisplay().getShells()) .filter(Shell::isVisible) - .filter(shell -> !beforeShells.contains(shell)) + .filter(s -> !beforeShells.contains(s)) .collect(Collectors.toList()); } protected Shell findNewShell(Collection beforeShells) { - DisplayHelper.sleep(shell.getDisplay(), 100); + DisplayHelper.sleep(getDisplay(), 100); List afterShells= findNewShells(beforeShells); if (afterShells.isEmpty()) { - DisplayHelper.sleep(shell.getDisplay(), 1000); + DisplayHelper.sleep(getDisplay(), 1000); } afterShells= findNewShells(beforeShells); - assertEquals("No new shell found", 1, afterShells.size()); - return afterShells.get(0); + assertTrue("No new shell found, existing: " + beforeShells, afterShells.size() > beforeShells.size()); + return afterShells.get(afterShells.size() - 1); } } diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java index c96e182..f82d598 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AsyncContentAssistTest.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.junit.After; @@ -52,8 +53,11 @@ public class AsyncContentAssistTest { private ILogListener listener; private IStatus errorStatus; + private Shell shell; + @Before public void setUp() { + shell= new Shell(); listener= (status, plugin) -> { if(status.getSeverity() == IStatus.ERROR && "org.eclipse.jface.text".equals(status.getPlugin())) { errorStatus = status; @@ -64,12 +68,12 @@ public void setUp() { @After public void tearDown() { + shell.dispose(); Platform.removeLogListener(listener); } @Test public void testAsyncFailureStackOverflow() { - Shell shell = new Shell(); SourceViewer viewer = new SourceViewer(shell, null, SWT.NONE); Document document = new Document("a"); viewer.setDocument(document); @@ -85,7 +89,6 @@ public void testAsyncFailureStackOverflow() { @Test public void testSyncFailureNPE() { - Shell shell = new Shell(); SourceViewer viewer = new SourceViewer(shell, null, SWT.NONE); Document document = new Document("a"); viewer.setDocument(document); @@ -101,7 +104,6 @@ public void testSyncFailureNPE() { @Test public void testCompletePrefix() { - Shell shell = new Shell(); shell.setLayout(new FillLayout()); shell.setSize(500, 300); SourceViewer viewer = new SourceViewer(shell, null, SWT.NONE); @@ -113,6 +115,7 @@ public void testCompletePrefix() { contentAssistant.enablePrefixCompletion(true); contentAssistant.install(viewer); shell.open(); + DisplayHelper.driveEventQueue(shell.getDisplay()); Display display = shell.getDisplay(); final Set beforeShells = Arrays.stream(display.getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); contentAssistant.showPossibleCompletions(); @@ -132,7 +135,6 @@ protected boolean condition() { @Test public void testCompleteActivationChar() { - Shell shell= new Shell(); shell.setLayout(new FillLayout()); shell.setSize(500, 300); SourceViewer viewer= new SourceViewer(shell, null, SWT.NONE); @@ -152,35 +154,50 @@ public void testCompleteActivationChar() { final Set beforeShells= Arrays.stream(display.getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); Event keyEvent= new Event(); Control control= viewer.getTextWidget(); - display.timerExec(200, new Runnable() { - @Override - public void run() { - control.forceFocus(); - keyEvent.widget= control; - keyEvent.type= SWT.KeyDown; - keyEvent.character= 'b'; - keyEvent.keyCode= 'b'; - control.getDisplay().post(keyEvent); - keyEvent.type= SWT.KeyUp; - control.getDisplay().post(keyEvent); - DisplayHelper.driveEventQueue(control.getDisplay()); - if (!document.get().startsWith("bb")) { - display.timerExec(200, this); + AtomicBoolean testEnded= new AtomicBoolean(); + try { + display.timerExec(0, new Runnable() { + @Override + public void run() { + if (control.isDisposed() || testEnded.get()) { + // https://github.com/eclipse-platform/eclipse.platform.text/issues/75#issuecomment-1263429480 + return; // do not fail other unit tests + } + control.getShell().forceActive(); + if (!control.forceFocus()) { + display.timerExec(200, this); + System.out.println("no focus"); + return; + } + keyEvent.widget= control; + keyEvent.type= SWT.KeyDown; + keyEvent.character= 'b'; + keyEvent.keyCode= 'b'; + control.getDisplay().post(keyEvent); + keyEvent.type= SWT.KeyUp; + control.getDisplay().post(keyEvent); + DisplayHelper.driveEventQueue(control.getDisplay()); + if (!document.get().startsWith("bb")) { + System.out.println("character b not added to control"); + display.timerExec(200, this); + } } - } - }); - assertTrue("Completion item not shown", new DisplayHelper() { - @Override - protected boolean condition() { - Set newShells= Arrays.stream(display.getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); - newShells.removeAll(beforeShells); - if (!newShells.isEmpty()) { - Table completionTable= findCompletionSelectionControl(newShells.iterator().next()); - return Arrays.stream(completionTable.getItems()).map(TableItem::getText).anyMatch(item -> item.contains(BarContentAssistProcessor.PROPOSAL.substring(document.getLength()))); + }); + assertTrue("Completion item not shown", new DisplayHelper() { + @Override + protected boolean condition() { + Set newShells= Arrays.stream(display.getShells()).filter(Shell::isVisible).collect(Collectors.toSet()); + newShells.removeAll(beforeShells); + if (!newShells.isEmpty()) { + Table completionTable= findCompletionSelectionControl(newShells.iterator().next()); + return Arrays.stream(completionTable.getItems()).map(TableItem::getText).anyMatch(item -> item.contains(BarContentAssistProcessor.PROPOSAL.substring(document.getLength()))); + } + return false; } - return false; - } - }.waitForCondition(display, 4000)); + }.waitForCondition(display, 4000)); + } finally { + testEnded.set(true); + } } private static Table findCompletionSelectionControl(Widget control) { diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java index 3d69a7b..ecf95d7 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java @@ -87,9 +87,8 @@ public IContextInformationValidator getContextInformationValidator() { @Test public void testContextInfo_withStyledTextPresentation() throws Exception { - setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); - final List beforeShells= getCurrentShells(); + setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); postSourceViewerKeyEvent(SWT.ARROW_RIGHT, 0, SWT.KeyDown); selectAndReveal(4, 0); diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java index f1ce338..32793b9 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java @@ -49,9 +49,8 @@ private ContentAssistant createBarContentAssist() { @Test public void testContextInfo() throws Exception { - setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); - final List beforeShells= getCurrentShells(); + setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); selectAndReveal(4, 0); processEvents(); @@ -70,9 +69,8 @@ public void testContextInfo() throws Exception { @Test public void testContextInfo_hide_Bug512251() throws Exception { - setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); - final List beforeShells= getCurrentShells(); + setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); selectAndReveal(4, 0); processEvents(); @@ -96,10 +94,9 @@ public void testContextInfo_hide_Bug512251() throws Exception { public void testContextInfo_hide_focusOut() throws Exception { assumeFalse("Test fails on Mac: Bug 558989", Platform.OS_MACOSX.equals(Platform.getOS())); + final List beforeShells= getCurrentShells(); setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); - final List beforeShells = getCurrentShells(); - selectAndReveal(4, 0); processEvents(); @@ -122,10 +119,9 @@ public void testContextInfo_hide_focusOut() throws Exception { @Test public void testContextInfo_hide_keyEsc() throws Exception { + final List beforeShells= getCurrentShells(); setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL); - final List beforeShells = getCurrentShells(); - selectAndReveal(4, 0); processEvents(); @@ -152,10 +148,9 @@ public void testContextInfo_hide_keyEsc() throws Exception { @Test public void testContextInfo_hide_validRange() throws Exception { + final List beforeShells= getCurrentShells(); setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL + '\n'); - final List beforeShells = getCurrentShells(); - selectAndReveal(4, 0); processEvents(); diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/DelayedErrorContentAssistProcessor.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/DelayedErrorContentAssistProcessor.java index 10c2ec2..2cde5f3 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/DelayedErrorContentAssistProcessor.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/DelayedErrorContentAssistProcessor.java @@ -28,7 +28,7 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - throw new RuntimeException(); + throw new RuntimeException("Expected Exception for junit test"); } @Override diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java index 4ff6095..8dcd16c 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/FilteringAsyncContentAssistTests.java @@ -71,6 +71,7 @@ public void setup() { shell = new Shell(); shell.setSize(300, 300); shell.open(); + DisplayHelper.driveEventQueue(shell.getDisplay()); viewer = new SourceViewer(shell, null, SWT.NONE); Document document = new Document(); diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java index be82f68..8f467a7 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/IncrementalAsyncContentAssistTests.java @@ -46,6 +46,7 @@ public void setup() { shell= new Shell(); shell.setSize(300, 300); shell.open(); + DisplayHelper.driveEventQueue(shell.getDisplay()); viewer= new SourceViewer(shell, null, SWT.NONE); Document document= new Document(); diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java index 134c59d..9778c25 100644 --- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java @@ -164,6 +164,10 @@ protected void reconcilerDocumentChanged(IDocument newDocument) { fCallLog.add("reconcilerDocumentChanged"); } @Override + protected void aboutToWork() { + AbstractReconcilerTest.this.aboutToWork(this); + } + @Override protected void aboutToBeReconciled() { fCallLog.add("aboutToBeReconciled"); } @@ -177,7 +181,7 @@ public IReconcilingStrategy getReconcilingStrategy(String contentType) { } }; fReconciler.setIsIncrementalReconciler(false); - fReconciler.setDelay(50); // make tests run faster + fReconciler.setDelay(getDelay()); fProgressMonitor= new NullProgressMonitor(); fReconciler.setProgressMonitor(fProgressMonitor); @@ -190,6 +194,14 @@ public IReconcilingStrategy getReconcilingStrategy(String contentType) { fAccessor= new Accessor(object, object.getClass()); } + int getDelay() { + return 50; // make tests run faster + } + + void aboutToWork(@SuppressWarnings("unused") AbstractReconciler reconciler) { + // nothing + } + @After public void tearDown() throws Exception { fBarrier.shutdown(); diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/FastAbstractReconcilerTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/FastAbstractReconcilerTest.java new file mode 100644 index 0000000..e68fc85 --- /dev/null +++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/FastAbstractReconcilerTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2021 Joerg Kubitz. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.text.tests.reconciler; + +import org.eclipse.jface.text.reconciler.AbstractReconciler; + +public class FastAbstractReconcilerTest extends AbstractReconcilerTest { + + @Override + int getDelay() { + return 10000; // make tests run slower (too slow without signalWaitForFinish) + } + + @Override + void aboutToWork(AbstractReconciler reconciler) { + reconciler.signalWaitForFinish(); // make tests run faster (instant) + } +} diff --git a/org.eclipse.jface.text/.settings/.api_filters b/org.eclipse.jface.text/.settings/.api_filters index 1396277..052f20f 100644 --- a/org.eclipse.jface.text/.settings/.api_filters +++ b/org.eclipse.jface.text/.settings/.api_filters @@ -1,26 +1,18 @@ - - + + - - + + - - + + - - - - - - - - - - + + diff --git a/org.eclipse.jface.text/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jface.text/.settings/org.eclipse.jdt.ui.prefs index 27b19cc..ef6b56d 100644 --- a/org.eclipse.jface.text/.settings/org.eclipse.jdt.ui.prefs +++ b/org.eclipse.jface.text/.settings/org.eclipse.jdt.ui.prefs @@ -10,6 +10,7 @@ org.eclipse.jdt.ui.keywordthis=false org.eclipse.jdt.ui.ondemandthreshold=99 org.eclipse.jdt.ui.overrideannotation=true org.eclipse.jdt.ui.staticondemandthreshold=99 +sp_cleanup.add_all=false sp_cleanup.add_default_serial_version_id=false sp_cleanup.add_generated_serial_version_id=false sp_cleanup.add_missing_annotations=false @@ -23,31 +24,81 @@ sp_cleanup.always_use_blocks=false sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.array_with_curly=false +sp_cleanup.arrays_fill=false +sp_cleanup.bitwise_conditional_expression=false +sp_cleanup.boolean_literal=false +sp_cleanup.boolean_value_rather_than_comparison=false +sp_cleanup.break_loop=false +sp_cleanup.collection_cloning=false +sp_cleanup.comparing_on_criteria=false +sp_cleanup.comparison_statement=false +sp_cleanup.controlflow_merge=false sp_cleanup.convert_functional_interfaces=false sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false +sp_cleanup.convert_to_switch_expressions=false sp_cleanup.correct_indentation=false +sp_cleanup.do_while_rather_than_while=false +sp_cleanup.double_negation=false +sp_cleanup.else_if=false +sp_cleanup.embedded_if=false +sp_cleanup.evaluate_nullable=false +sp_cleanup.extract_increment=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=true +sp_cleanup.hash=false +sp_cleanup.if_condition=false sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.instanceof=false +sp_cleanup.instanceof_keyword=false +sp_cleanup.invert_equals=false +sp_cleanup.join=false +sp_cleanup.lazy_logical_operator=false sp_cleanup.make_local_variable_final=false sp_cleanup.make_parameters_final=false sp_cleanup.make_private_fields_final=false sp_cleanup.make_type_abstract_if_missing_method=false sp_cleanup.make_variable_declarations_final=false +sp_cleanup.map_cloning=false +sp_cleanup.merge_conditional_blocks=false +sp_cleanup.multi_catch=false sp_cleanup.never_use_blocks=false sp_cleanup.never_use_parentheses_in_expressions=false +sp_cleanup.no_string_creation=false +sp_cleanup.no_super=false +sp_cleanup.number_suffix=false +sp_cleanup.objects_equals=false sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false +sp_cleanup.operand_factorization=false sp_cleanup.organize_imports=true +sp_cleanup.overridden_assignment=false +sp_cleanup.plain_replacement=false +sp_cleanup.precompile_regex=false +sp_cleanup.primitive_comparison=false +sp_cleanup.primitive_parsing=false +sp_cleanup.primitive_rather_than_wrapper=false +sp_cleanup.primitive_serialization=false +sp_cleanup.pull_out_if_from_if_else=false +sp_cleanup.pull_up_assignment=false +sp_cleanup.push_down_negation=false sp_cleanup.qualify_static_field_accesses_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_with_declaring_class=false sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.reduce_indentation=false +sp_cleanup.redundant_comparator=false +sp_cleanup.redundant_falling_through_block_end=false sp_cleanup.remove_private_constructors=false -sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=true +sp_cleanup.remove_redundant_type_arguments=true sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_array_creation=false sp_cleanup.remove_unnecessary_casts=false sp_cleanup.remove_unnecessary_nls_tags=false sp_cleanup.remove_unused_imports=false @@ -56,14 +107,44 @@ sp_cleanup.remove_unused_private_fields=false sp_cleanup.remove_unused_private_members=false sp_cleanup.remove_unused_private_methods=false sp_cleanup.remove_unused_private_types=false +sp_cleanup.return_expression=false +sp_cleanup.simplify_lambda_expression_and_method_ref=false +sp_cleanup.single_used_field=false sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false +sp_cleanup.standard_comparison=false +sp_cleanup.static_inner_class=false +sp_cleanup.strictly_equal_or_different=false +sp_cleanup.stringbuffer_to_stringbuilder=false +sp_cleanup.stringbuilder=false +sp_cleanup.stringbuilder_for_local_vars=false +sp_cleanup.stringconcat_to_textblock=false +sp_cleanup.substring=false +sp_cleanup.switch=false +sp_cleanup.system_property=false +sp_cleanup.system_property_boolean=false +sp_cleanup.system_property_file_encoding=false +sp_cleanup.system_property_file_separator=false +sp_cleanup.system_property_line_separator=false +sp_cleanup.system_property_path_separator=false +sp_cleanup.ternary_operator=false +sp_cleanup.try_with_resource=false +sp_cleanup.unlooped_while=false +sp_cleanup.unreachable_block=false sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=false sp_cleanup.use_blocks=false sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=false sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_string_is_blank=false sp_cleanup.use_this_for_non_static_field_access=false sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false sp_cleanup.use_this_for_non_static_method_access=false sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false +sp_cleanup.use_unboxing=false +sp_cleanup.use_var=false +sp_cleanup.useless_continue=false +sp_cleanup.useless_return=false +sp_cleanup.valueof_rather_than_instantiation=false diff --git a/org.eclipse.jface.text/META-INF/MANIFEST.MF b/org.eclipse.jface.text/META-INF/MANIFEST.MF index 28416d1..0c38fd3 100644 --- a/org.eclipse.jface.text/META-INF/MANIFEST.MF +++ b/org.eclipse.jface.text/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface.text -Bundle-Version: 3.18.100.qualifier +Bundle-Version: 3.22.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: @@ -35,7 +35,7 @@ Export-Package: Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)", org.eclipse.text;bundle-version="[3.8.0,4.0.0)";visibility:=reexport, - org.eclipse.swt;bundle-version="[3.110.100,4.0.0)", + org.eclipse.swt;bundle-version="[3.117.0,4.0.0)", org.eclipse.jface;bundle-version="[3.19.0,4.0.0)" Import-Package: com.ibm.icu.text Bundle-RequiredExecutionEnvironment: JavaSE-11 diff --git a/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java b/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java index a17c26f..400c8e2 100644 --- a/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java +++ b/org.eclipse.jface.text/projection/org/eclipse/jface/text/source/projection/ProjectionViewer.java @@ -1053,7 +1053,9 @@ private void processDeletions(AnnotationModelEvent event, Annotation[] removedAn ProjectionAnnotation annotation = (ProjectionAnnotation) removedAnnotation; if (annotation.isCollapsed()) { Position expanded= event.getPositionOfRemovedAnnotation(annotation); - expand(expanded.getOffset(), expanded.getLength(), fireRedraw); + if (expanded != null) { + expand(expanded.getOffset(), expanded.getLength(), fireRedraw); + } } } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/IContentAssistSubjectControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/IContentAssistSubjectControl.java index 57b4ba0..d958fcd 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/IContentAssistSubjectControl.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/contentassist/IContentAssistSubjectControl.java @@ -244,7 +244,6 @@ public interface IContentAssistSubjectControl { /** * Removes the specified selection listener. - *

* * @param selectionListener the listener * @exception org.eclipse.swt.SWTException @@ -261,7 +260,6 @@ public interface IContentAssistSubjectControl { /** * If supported, adds a selection listener. A Selection event is sent by the widget when the * selection has changed. - *

* * @param selectionListener the listener * @return true if adding a selection listener is supported diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/InformationControlReplacer.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/InformationControlReplacer.java index 96331f9..c713f7c 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/InformationControlReplacer.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/InformationControlReplacer.java @@ -131,39 +131,45 @@ protected void computeInformation() { public void showInformationControl(Rectangle subjectArea, Object information) { IInformationControl informationControl= getInformationControl(); - Rectangle controlBounds= fContentBounds; - if (informationControl instanceof IInformationControlExtension3) { - IInformationControlExtension3 iControl3= (IInformationControlExtension3) informationControl; - Rectangle trim= iControl3.computeTrim(); - controlBounds= Geometry.add(controlBounds, trim); - - /* - * Ensure minimal size. Interacting with a tiny information control - * (resizing, selecting text) would be a pain. - */ - controlBounds.width= Math.max(controlBounds.width, MIN_WIDTH); - controlBounds.height= Math.max(controlBounds.height, MIN_HEIGHT); - - getInternalAccessor().cropToClosestMonitor(controlBounds); - } - - Point location= Geometry.getLocation(controlBounds); - Point size= Geometry.getSize(controlBounds); + Rectangle controlBounds= computeBoundsFromContent(informationControl, fContentBounds); // Caveat: some IInformationControls fail unless setSizeConstraints(..) is called with concrete values - informationControl.setSizeConstraints(size.x, size.y); + informationControl.setSizeConstraints(controlBounds.width, controlBounds.height); if (informationControl instanceof IInformationControlExtension2) ((IInformationControlExtension2) informationControl).setInput(information); else informationControl.setInformation(information.toString()); - informationControl.setLocation(location); - informationControl.setSize(size.x, size.y); + // need to recompute the bounds because trim might have changed based on input + controlBounds= computeBoundsFromContent(informationControl, fContentBounds); + + informationControl.setLocation(new Point(controlBounds.x, controlBounds.y)); + informationControl.setSize(controlBounds.width, controlBounds.height); showInformationControl(subjectArea); } + private Rectangle computeBoundsFromContent(IInformationControl informationControl, Rectangle controlBounds) { + Rectangle result= Geometry.copy(controlBounds); + + if (informationControl instanceof IInformationControlExtension3) { + IInformationControlExtension3 iControl3= (IInformationControlExtension3) informationControl; + Rectangle trim= iControl3.computeTrim(); + result= Geometry.add(result, trim); + + /* + * Ensure minimal size. Interacting with a tiny information control + * (resizing, selecting text) would be a pain. + */ + result.width= Math.max(result.width, MIN_WIDTH); + result.height= Math.max(result.height, MIN_HEIGHT); + + getInternalAccessor().cropToClosestMonitor(result); + } + return result; + } + @Override public void hideInformationControl() { super.hideInformationControl(); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java index f0dac1f..08ed671 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 Avaloq Evolution AG and others. + * Copyright (c) 2009, 2021 Avaloq Evolution AG and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,7 +13,11 @@ *******************************************************************************/ package org.eclipse.jface.internal.text; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.GC; @@ -34,6 +38,7 @@ import org.eclipse.jface.text.BlockTextSelection; import org.eclipse.jface.text.IBlockTextSelection; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.IRewriteTarget; import org.eclipse.jface.text.ITextSelection; @@ -41,6 +46,7 @@ import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.MultiStringMatcher; import org.eclipse.jface.text.MultiStringMatcher.Match; +import org.eclipse.jface.text.MultiTextSelection; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.TextUtilities; @@ -51,7 +57,7 @@ * @since 3.5 */ public final class SelectionProcessor { - private static class Implementation { + private static class Implementation { /** * Returns a text edit describing the text modification that would be executed if the given * selection was replaced by replacement. @@ -61,7 +67,7 @@ private static class Implementation { * @return a text edit describing the operation needed to replace selection * @throws BadLocationException if computing the edit failed */ - TextEdit replace(ISelection selection, String replacement) throws BadLocationException { + TextEdit replace(T selection, String replacement) throws BadLocationException { return new MultiTextEdit(); } @@ -72,7 +78,7 @@ TextEdit replace(ISelection selection, String replacement) throws BadLocationExc * @return the text covered by selection * @throws BadLocationException if computing the edit failed */ - String getText(ISelection selection) throws BadLocationException { + String getText(T selection) throws BadLocationException { return ""; //$NON-NLS-1$ } @@ -86,7 +92,7 @@ String getText(ISelection selection) throws BadLocationException { * false otherwise * @throws BadLocationException if accessing the document failed */ - boolean isEmpty(ISelection selection) throws BadLocationException { + boolean isEmpty(T selection) throws BadLocationException { return selection.isEmpty(); } @@ -99,17 +105,17 @@ boolean isEmpty(ISelection selection) throws BadLocationException { * false otherwise * @throws BadLocationException if selection is not a valid selection on the target document */ - boolean isMultiline(ISelection selection) throws BadLocationException { + boolean isMultiline(T selection) throws BadLocationException { if (selection == null) throw new NullPointerException(); return false; } - TextEdit delete(ISelection selection) throws BadLocationException { + TextEdit delete(T selection) throws BadLocationException { return replace(selection, ""); //$NON-NLS-1$ } - TextEdit backspace(ISelection selection) throws BadLocationException { + TextEdit backspace(T selection) throws BadLocationException { return replace(selection, ""); //$NON-NLS-1$ } @@ -124,7 +130,7 @@ TextEdit backspace(ISelection selection) throws BadLocationException { * @return an empty variant of selection * @throws BadLocationException if accessing the document failed */ - ISelection makeEmpty(ISelection selection, boolean beginning) throws BadLocationException { + T makeEmpty(T selection, boolean beginning) throws BadLocationException { return selection; } @@ -135,7 +141,7 @@ ISelection makeEmpty(ISelection selection, boolean beginning) throws BadLocation * @return the text regions corresponding to selection * @throws BadLocationException if accessing the document failed */ - IRegion[] getRanges(ISelection selection) throws BadLocationException { + IRegion[] getRanges(T selection) throws BadLocationException { return new IRegion[0]; } @@ -146,7 +152,7 @@ IRegion[] getRanges(ISelection selection) throws BadLocationException { * @return the number of lines touched by selection * @throws BadLocationException if accessing the document failed */ - int getCoveredLines(ISelection selection) throws BadLocationException { + int getCoveredLines(T selection) throws BadLocationException { return 0; } @@ -158,90 +164,311 @@ int getCoveredLines(ISelection selection) throws BadLocationException { * @return the selection that the user expects after the specified replacement operation * @throws BadLocationException if accessing the document failed */ - ISelection makeReplaceSelection(ISelection selection, String replacement) throws BadLocationException { + T makeReplaceSelection(T selection, String replacement) throws BadLocationException { return makeEmpty(selection, false); } + + /** + * Returns the selection after hitting backspace. + * + * @param selection the selection to be replaced + * @return the selection that the user expects after the specified backspace operation + * @throws BadLocationException if accessing the document failed + */ + public ISelection makeBackspaceSelection(T selection) throws BadLocationException { + return makeEmpty(selection, true); + } + + /** + * Returns the selection after hitting delete. + * + * @param selection the selection to be replaced + * @return the selection that the user expects after the specified backspace operation + * @throws BadLocationException if accessing the document failed + */ + public ISelection makeDeleteSelection(T selection) throws BadLocationException { + return makeEmpty(selection, true); + } } - private final Implementation NULL_IMPLEMENTATION= new Implementation(); + private final Implementation NULL_IMPLEMENTATION= new Implementation<>(); - private final Implementation RANGE_IMPLEMENTATION= new Implementation() { + private final Implementation RANGE_IMPLEMENTATION= new Implementation<>() { @Override - TextEdit replace(ISelection selection, String replacement) { - ITextSelection ts= (ITextSelection)selection; - return new ReplaceEdit(ts.getOffset(), ts.getLength(), replacement); + TextEdit replace(ITextSelection selection, String replacement) { + return new ReplaceEdit(selection.getOffset(), selection.getLength(), replacement); } @Override - String getText(ISelection selection) { - ITextSelection ts= (ITextSelection)selection; - return ts.getText(); + String getText(ITextSelection selection) { + return selection.getText(); } @Override - boolean isEmpty(ISelection selection) { - ITextSelection ts= (ITextSelection)selection; - return ts.getLength() <= 0; + boolean isEmpty(ITextSelection selection) { + return selection.getLength() <= 0; } @Override - boolean isMultiline(ISelection selection) throws BadLocationException { - ITextSelection ts= (ITextSelection)selection; - return fDocument.getLineOfOffset(ts.getOffset()) < fDocument.getLineOfOffset(ts.getOffset() + ts.getLength()); + boolean isMultiline(ITextSelection selection) throws BadLocationException { + return fDocument.getLineOfOffset(selection.getOffset()) < fDocument.getLineOfOffset(selection.getOffset() + selection.getLength()); } @Override - TextEdit delete(ISelection selection) { - ITextSelection ts= (ITextSelection)selection; - if (isEmpty(selection)) - return new DeleteEdit(ts.getOffset(), 1); - return new DeleteEdit(ts.getOffset(), ts.getLength()); + TextEdit delete(ITextSelection selection) { + return isEmpty(selection) ? new DeleteEdit(selection.getOffset(), 1) : new DeleteEdit(selection.getOffset(), selection.getLength()); } @Override - TextEdit backspace(ISelection selection) throws BadLocationException { - ITextSelection ts= (ITextSelection)selection; - if (isEmpty(selection)) - return new DeleteEdit(ts.getOffset() - 1, 1); - return new DeleteEdit(ts.getOffset(), ts.getLength()); + TextEdit backspace(ITextSelection selection) throws BadLocationException { + return isEmpty(selection) ? new DeleteEdit(selection.getOffset() - 1, 1) : new DeleteEdit(selection.getOffset(), selection.getLength()); } @Override - ISelection makeEmpty(ISelection selection, boolean beginning) { - ITextSelection ts= (ITextSelection)selection; + ITextSelection makeEmpty(ITextSelection selection, boolean beginning) { return beginning ? - new TextSelection(fDocument, ts.getOffset(), 0) - : new TextSelection(fDocument, ts.getOffset() + ts.getLength(), 0); + new TextSelection(fDocument, selection.getOffset(), 0) : new TextSelection(fDocument, selection.getOffset() + selection.getLength(), 0); + } + + @Override + IRegion[] getRanges(ITextSelection selection) { + return new IRegion[] { new Region(selection.getOffset(), selection.getLength()) }; + } + + @Override + int getCoveredLines(ITextSelection selection) throws BadLocationException { + return selection.getEndLine() - selection.getStartLine() + 1; + } + + @Override + ITextSelection makeReplaceSelection(ITextSelection selection, String replacement) { + return new TextSelection(fDocument, selection.getOffset() + replacement.length(), 0); + } + + @Override + public ISelection makeBackspaceSelection(ITextSelection selection) throws BadLocationException { + if (isEmpty(selection)) { + return new TextSelection(Math.max(0, selection.getOffset() - 1), selection.getLength()); + } + return makeEmpty(selection, true); + } + }; + + private final Implementation RANGES_IMPLEMENTATION= new Implementation<>() { + + private MultiTextEdit rangeEdits(IMultiTextSelection selection, Function regionToTextEdit) { + MultiTextEdit res= new MultiTextEdit(); + Arrays.stream(selection.getRegions()) + .map(regionToTextEdit) + .filter(Objects::nonNull) + .forEach(res::addChild); + return res; + } + + @Override + TextEdit replace(IMultiTextSelection selection, String replacement) { + if (replacement.isBlank() || !replacement.contains(System.lineSeparator())) { // simple edit + return rangeEdits(selection, region -> new ReplaceEdit(region.getOffset(), region.getLength(), replacement)); + } else { // paste + MultiTextEdit root; + root= new MultiTextEdit(); + String[] delimiters= fDocument.getLegalLineDelimiters(); + MultiStringMatcher delimiterMatcher= MultiStringMatcher.create(delimiters); + + int lastDelim= 0; + for (IRegion region : selection.getRegions()) { + String string; + if (lastDelim == -1) { + string= ""; //$NON-NLS-1$ + } else { + Match m= delimiterMatcher.indexOf(replacement, lastDelim); + if (m == null) { + string= replacement.substring(lastDelim); + lastDelim= -1; + } else { + string= replacement.substring(lastDelim, m.getOffset()); + lastDelim= m.getOffset() + m.getText().length(); + } + } + TextEdit replace= new ReplaceEdit(region.getOffset(), region.getLength(), string); + root.addChild(replace); + } + // while (lastDelim != -1) { + // // more stuff to insert + // String string; + // Match m= delimiterMatcher.indexOf(replacement, lastDelim); + // if (m == null) { + // string= replacement.substring(lastDelim); + // lastDelim= -1; + // } else { + // string= replacement.substring(lastDelim, m.getOffset()); + // lastDelim= m.getOffset() + m.getText().length(); + // } + // endLine++; + // TextEdit edit; + // if (endLine < fDocument.getNumberOfLines()) { + // edit= createReplaceEdit(endLine, visualStartColumn, visualEndColumn, string, delete); + // } else { + // // insertion reaches beyond the last line + // int insertLocation= root.getExclusiveEnd(); + // int spaces= visualStartColumn; + // char[] array= new char[spaces]; + // Arrays.fill(array, ' '); + // string= TextUtilities.getDefaultLineDelimiter(fDocument) + String.valueOf(array) + string; + // edit= new InsertEdit(insertLocation, string); + // insertLocation+= string.length(); + // } + // root.addChild(edit); + // } + return root; + } + } + + @Override + String getText(IMultiTextSelection selection) throws BadLocationException { + StringBuilder builder = new StringBuilder(); + for (IRegion region : selection.getRegions()) { + builder.append(fDocument.get(region.getOffset(), region.getLength())); + } + return builder.toString(); } @Override - IRegion[] getRanges(ISelection selection) { - ITextSelection ts= (ITextSelection)selection; - return new IRegion[] { new Region(ts.getOffset(), ts.getLength()) }; + boolean isEmpty(IMultiTextSelection selection) { + return Arrays.stream(selection.getRegions()).allMatch(r -> r.getLength() == 0); } @Override - int getCoveredLines(ISelection selection) throws BadLocationException { - ITextSelection ts= (ITextSelection)selection; - return ts.getEndLine() - ts.getStartLine() + 1; + boolean isMultiline(IMultiTextSelection selection) throws BadLocationException { + int line = -1; + for (IRegion region : selection.getRegions()) { + if (line == -1) { + line = fDocument.getLineOfOffset(region.getOffset()); + } else if ( + line != fDocument.getLineOfOffset(region.getOffset()) || + line != fDocument.getLineOfOffset(region.getOffset() + region.getLength())) { + return true; + } + } + return false; + } + + @Override + TextEdit delete(IMultiTextSelection selection) { + if (isEmpty(selection)) { + return rangeEdits(selection, region -> new DeleteEdit(region.getOffset(), 1)); + } + return rangeEdits(selection, region -> new DeleteEdit(region.getOffset(), region.getLength())); + } + + @Override + TextEdit backspace(IMultiTextSelection selection) throws BadLocationException { + if (isEmpty(selection)) { + return rangeEdits(selection, region -> region.getOffset() == 0 ? null : new DeleteEdit(region.getOffset() - 1, 1)); + } + return rangeEdits(selection, region -> { + if (region.getLength() > 0) { + return new DeleteEdit(region.getOffset(), region.getLength()); + } else if (region.getOffset() > 0) { + return new DeleteEdit(region.getOffset() - 1, 1); + } else { + return null; + } + }); + } + + @Override + IMultiTextSelection makeEmpty(IMultiTextSelection selection, boolean beginning) { + int[] deletedCount= new int[] { 0 }; + return new MultiTextSelection(fDocument, Arrays.stream(selection.getRegions()).map(region -> { + Region res= beginning + ? new Region(region.getOffset() - deletedCount[0], 0) + : new Region(region.getOffset() - deletedCount[0] + region.getLength(), 0); + deletedCount[0]+= region.getLength(); + return res; + }).toArray(Region[]::new)); + } + + @Override + IRegion[] getRanges(IMultiTextSelection selection) { + return selection.getRegions().clone(); + } + + @Override + int getCoveredLines(IMultiTextSelection selection) throws BadLocationException { + int res = 0; + int lastLine = -1; + for (IRegion region : selection.getRegions()) { + if (lastLine == fDocument.getLineOfOffset(region.getOffset())) { + res--; // ignore 1st line if already processed + } + res++; // at least 1 line for the range + res+= (fDocument.getLineOfOffset(region.getOffset() + region.getLength()) - fDocument.getLineOfOffset(region.getOffset())); + lastLine = fDocument.getLineOfOffset(region.getOffset() + region.getLength()); + } + return res; + } + + @Override + IMultiTextSelection makeReplaceSelection(IMultiTextSelection selection, String replacement) { + if (!replacement.contains(System.lineSeparator())) { // simple edit + int[] offset= new int[] { 0 }; + return new MultiTextSelection(fDocument, + Arrays.stream(selection.getRegions()).map(region -> { + Region res= new Region(region.getOffset() + offset[0] + replacement.length(), 0); + offset[0]+= (replacement.length() - region.getLength()); + return res; + }).toArray(Region[]::new)); + } else { // paste + TextEdit edit= replace(selection, replacement); + if (edit instanceof MultiTextEdit) { + int offsetDelta= 0; + List afterEdit= new ArrayList<>(Math.min(edit.getLength(), selection.getLength())); + for (int i= 0; i < Math.min(edit.getChildrenSize(), selection.getLength()); i++) { + ReplaceEdit currentEdit= (ReplaceEdit) edit.getChildren()[i]; + offsetDelta+= currentEdit.getText().length() - currentEdit.getRegion().getLength(); + afterEdit.add(new Region(currentEdit.getOffset() + offsetDelta, 0)); + } + return new MultiTextSelection(fDocument, afterEdit.toArray(IRegion[]::new)); + } else { + return new TextSelection(fDocument, edit.getOffset() + replacement.length() - edit.getRegion().getLength(), 0); + } + } } @Override - ISelection makeReplaceSelection(ISelection selection, String replacement) { - ITextSelection ts= (ITextSelection)selection; - return new TextSelection(fDocument, ts.getOffset() + replacement.length(), 0); + public ISelection makeBackspaceSelection(IMultiTextSelection selection) throws BadLocationException { + int[] removedChars= { 0 }; + return new MultiTextSelection(fDocument, + Arrays.stream(selection.getRegions()).map(region -> { + int length= region.getLength() != 0 ? region.getLength() : (region.getOffset() != 0 ? 1 : 0); + Region res= new Region(Math.max(0, region.getOffset() - removedChars[0] - (region.getLength() == 0 ? length : 0)), 0); + removedChars[0]+= length; + return res; + }).toArray(Region[]::new)); + } + + @Override + public ISelection makeDeleteSelection(IMultiTextSelection selection) throws BadLocationException { + int[] removedChars= { 0 }; + return new MultiTextSelection(fDocument, + Arrays.stream(selection.getRegions()).map(region -> { + int length= region.getLength() != 0 ? region.getLength() : 1; + Region res= new Region(Math.max(0, region.getOffset() - removedChars[0]), 0); + removedChars[0]+= length; + return res; + }).toArray(Region[]::new)); } }; - private final Implementation COLUMN_IMPLEMENTATION= new Implementation() { - private TextEdit replace(ISelection selection, String replacement, boolean delete) throws BadLocationException { + private final Implementation COLUMN_IMPLEMENTATION= new Implementation<>() { + private TextEdit replace(IBlockTextSelection selection, String replacement, boolean delete) throws BadLocationException { try { MultiTextEdit root; - IBlockTextSelection cts= (IBlockTextSelection)selection; - int startLine= cts.getStartLine(); - int endLine= cts.getEndLine(); - int startColumn= cts.getStartColumn(); - int endColumn= cts.getEndColumn(); + int startLine= selection.getStartLine(); + int endLine= selection.getEndLine(); + int startColumn= selection.getStartColumn(); + int endColumn= selection.getEndColumn(); int visualStartColumn= computeVisualColumn(startLine, startColumn); int visualEndColumn= computeVisualColumn(endLine, endColumn); root= new MultiTextEdit(); @@ -301,18 +528,17 @@ private TextEdit replace(ISelection selection, String replacement, boolean delet } @Override - TextEdit replace(ISelection selection, String replacement) throws BadLocationException { + TextEdit replace(IBlockTextSelection selection, String replacement) throws BadLocationException { return replace(selection, replacement, false); } @Override - String getText(ISelection selection) throws BadLocationException { - IBlockTextSelection cts= (IBlockTextSelection)selection; - StringBuilder buf= new StringBuilder(cts.getLength()); - int startLine= cts.getStartLine(); - int endLine= cts.getEndLine(); - int startColumn= cts.getStartColumn(); - int endColumn= cts.getEndColumn(); + String getText(IBlockTextSelection selection) throws BadLocationException { + StringBuilder buf= new StringBuilder(selection.getLength()); + int startLine= selection.getStartLine(); + int endLine= selection.getEndLine(); + int startColumn= selection.getStartColumn(); + int endColumn= selection.getEndColumn(); int visualStartColumn= computeVisualColumn(startLine, startColumn); int visualEndColumn= computeVisualColumn(endLine, endColumn); @@ -326,79 +552,81 @@ String getText(ISelection selection) throws BadLocationException { } @Override - boolean isEmpty(ISelection selection) throws BadLocationException { - IBlockTextSelection cts= (IBlockTextSelection)selection; - int startLine= cts.getStartLine(); - int endLine= cts.getEndLine(); - int startColumn= cts.getStartColumn(); - int endColumn= cts.getEndColumn(); + boolean isEmpty(IBlockTextSelection selection) throws BadLocationException { + int startLine= selection.getStartLine(); + int endLine= selection.getEndLine(); + int startColumn= selection.getStartColumn(); + int endColumn= selection.getEndColumn(); int visualStartColumn= computeVisualColumn(startLine, startColumn); int visualEndColumn= computeVisualColumn(endLine, endColumn); return visualEndColumn == visualStartColumn; } @Override - boolean isMultiline(ISelection selection) { - ITextSelection ts= (ITextSelection)selection; - return ts.getEndLine() > ts.getStartLine(); + boolean isMultiline(IBlockTextSelection selection) { + return selection.getEndLine() > selection.getStartLine(); } @Override - TextEdit delete(ISelection selection) throws BadLocationException { + TextEdit delete(IBlockTextSelection selection) throws BadLocationException { if (isEmpty(selection)) { - IBlockTextSelection cts= (IBlockTextSelection)selection; - selection= new BlockTextSelection(fDocument, cts.getStartLine(), cts.getStartColumn(), cts.getEndLine(), cts.getEndColumn() + 1, fTabWidth); + selection= new BlockTextSelection(fDocument, selection.getStartLine(), selection.getStartColumn(), selection.getEndLine(), selection.getEndColumn() + 1, fTabWidth); } return replace(selection, "", true); //$NON-NLS-1$ } @Override - TextEdit backspace(ISelection selection) throws BadLocationException { - IBlockTextSelection cts= (IBlockTextSelection)selection; - if (isEmpty(selection) && cts.getStartColumn() > 0) { - selection= new BlockTextSelection(fDocument, cts.getStartLine(), cts.getStartColumn() - 1, cts.getEndLine(), cts.getEndColumn(), fTabWidth); + TextEdit backspace(IBlockTextSelection selection) throws BadLocationException { + if (isEmpty(selection) && selection.getStartColumn() > 0) { + selection= new BlockTextSelection(fDocument, selection.getStartLine(), selection.getStartColumn() - 1, selection.getEndLine(), selection.getEndColumn(), fTabWidth); } return replace(selection, ""); //$NON-NLS-1$ } @Override - ISelection makeEmpty(ISelection selection, boolean beginning) throws BadLocationException { - IBlockTextSelection cts= (IBlockTextSelection)selection; + IBlockTextSelection makeEmpty(IBlockTextSelection selection, boolean beginning) throws BadLocationException { int startLine, startColumn, endLine, endColumn; if (beginning) { - startLine= cts.getStartLine(); - startColumn= cts.getStartColumn(); - endLine= cts.getEndLine(); + startLine= selection.getStartLine(); + startColumn= selection.getStartColumn(); + endLine= selection.getEndLine(); endColumn= computeCharacterColumn(endLine, computeVisualColumn(startLine, startColumn)); } else { - endLine= cts.getEndLine(); - endColumn= cts.getEndColumn(); - startLine= cts.getStartLine(); + endLine= selection.getEndLine(); + endColumn= selection.getEndColumn(); + startLine= selection.getStartLine(); startColumn= computeCharacterColumn(startLine, computeVisualColumn(endLine, endColumn)); } return new BlockTextSelection(fDocument, startLine, startColumn, endLine, endColumn, fTabWidth); } @Override - ISelection makeReplaceSelection(ISelection selection, String replacement) throws BadLocationException { - IBlockTextSelection bts= (IBlockTextSelection)selection; + IBlockTextSelection makeReplaceSelection(IBlockTextSelection selection, String replacement) throws BadLocationException { Match m= MultiStringMatcher.indexOf(replacement, 0, fDocument.getLegalLineDelimiters()); int length= m != null ? m.getOffset() : replacement.length(); - int startLine= bts.getStartLine(); - int column= bts.getStartColumn() + length; - int endLine= bts.getEndLine(); + int startLine= selection.getStartLine(); + int column= selection.getStartColumn() + length; + int endLine= selection.getEndLine(); int endColumn= computeCharacterColumn(endLine, computeVisualColumn(startLine, column)); return new BlockTextSelection(fDocument, startLine, column, endLine, endColumn, fTabWidth); } @Override - IRegion[] getRanges(ISelection selection) throws BadLocationException { - IBlockTextSelection cts= (IBlockTextSelection)selection; - final int startLine= cts.getStartLine(); - final int endLine= cts.getEndLine(); - int visualStartColumn= computeVisualColumn(startLine, cts.getStartColumn()); - int visualEndColumn= computeVisualColumn(endLine, cts.getEndColumn()); + public ISelection makeBackspaceSelection(IBlockTextSelection selection) throws BadLocationException { + if (!isEmpty(selection)) { + return makeEmpty(selection, true); + } + int column= Math.max(0, selection.getStartColumn()); + return new BlockTextSelection(fDocument, selection.getStartLine(), column, selection.getEndLine(), column, fTabWidth); + } + + @Override + IRegion[] getRanges(IBlockTextSelection selection) throws BadLocationException { + final int startLine= selection.getStartLine(); + final int endLine= selection.getEndLine(); + int visualStartColumn= computeVisualColumn(startLine, selection.getStartColumn()); + int visualEndColumn= computeVisualColumn(endLine, selection.getEndColumn()); IRegion[] ranges= new IRegion[endLine - startLine + 1]; for (int line= startLine; line <= endLine; line++) { @@ -415,9 +643,8 @@ IRegion[] getRanges(ISelection selection) throws BadLocationException { } @Override - int getCoveredLines(ISelection selection) throws BadLocationException { - ITextSelection ts= (ITextSelection)selection; - return ts.getEndLine() - ts.getStartLine() + 1; + int getCoveredLines(IBlockTextSelection selection) throws BadLocationException { + return selection.getEndLine() - selection.getStartLine() + 1; } private TextEdit createReplaceEdit(int line, int visualStartColumn, int visualEndColumn, String replacement, boolean delete) throws BadLocationException { @@ -691,6 +918,14 @@ private ISelection makeReplaceSelection(ISelection selection, String replacement return getImplementation(selection).makeReplaceSelection(selection, replacement); } + private ISelection makeBackspaceSelection(ISelection selection) throws BadLocationException { + return getImplementation(selection).makeBackspaceSelection(selection); + } + + private ISelection makeDeleteSelection(ISelection selection) throws BadLocationException { + return getImplementation(selection).makeDeleteSelection(selection); + } + /** * Convenience method that applies the edit returned from {@link #delete(ISelection)} to the * underlying document. @@ -706,7 +941,7 @@ public void doDelete(ISelection selection) throws BadLocationException { try { edit.apply(fDocument, TextEdit.UPDATE_REGIONS); if (fSelectionProvider != null) { - ISelection empty= makeEmpty(selection, true); + ISelection empty= makeDeleteSelection(selection); fSelectionProvider.setSelection(empty); } } finally { @@ -715,6 +950,30 @@ public void doDelete(ISelection selection) throws BadLocationException { } } + /** + * Convenience method that applies the edit returned from {@link #backspace(ISelection)} to the + * underlying document. + * + * @param selection the selection to delete + * @throws BadLocationException if accessing the document failed + */ + public void doBackspace(ISelection selection) throws BadLocationException { + TextEdit edit= backspace(selection); + boolean complex= edit.hasChildren(); + if (complex && fRewriteTarget != null) + fRewriteTarget.beginCompoundChange(); + try { + ISelection newSelection= makeBackspaceSelection(selection); + edit.apply(fDocument, TextEdit.UPDATE_REGIONS); + if (fSelectionProvider != null) { + fSelectionProvider.setSelection(newSelection); + } + } finally { + if (complex && fRewriteTarget != null) + fRewriteTarget.endCompoundChange(); + } + } + /** * Convenience method that applies the edit returned from {@link #replace(ISelection, String)} * to the underlying document and adapts the selection accordingly. @@ -770,12 +1029,15 @@ public int getCoveredLines(ISelection selection) throws BadLocationException { * @param selection the selection * @return the corresponding processor implementation */ - private Implementation getImplementation(ISelection selection) { - if (selection instanceof IBlockTextSelection) - return COLUMN_IMPLEMENTATION; - else if (selection instanceof ITextSelection) - return RANGE_IMPLEMENTATION; - else - return NULL_IMPLEMENTATION; + @SuppressWarnings("unchecked") + private Implementation getImplementation(ISelection selection) { + if (selection instanceof IBlockTextSelection) { + return (Implementation) COLUMN_IMPLEMENTATION; + } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection)selection).getRegions().length > 1) { + return (Implementation) RANGES_IMPLEMENTATION; + } else if (selection instanceof ITextSelection) { + return (Implementation) RANGE_IMPLEMENTATION; + } + return (Implementation) NULL_IMPLEMENTATION; } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineHeaderAnnotation.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineHeaderAnnotation.java index b7188ea..abed58b 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineHeaderAnnotation.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/codemining/CodeMiningLineHeaderAnnotation.java @@ -88,6 +88,10 @@ public int getHeight() { * label and false otherwise. */ private boolean hasAtLeastOneResolvedMiningNotEmpty() { + if (fMinings.stream().anyMatch(m -> m.getLabel() != null)) { + return true; // will have a resolved mining. + } + if (fResolvedMinings == null || fResolvedMinings.length == 0) { return false; } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/BrowserInformationControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/BrowserInformationControl.java index f08f6fa..b364045 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/BrowserInformationControl.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/BrowserInformationControl.java @@ -42,6 +42,7 @@ import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.Util; import org.eclipse.jface.text.AbstractInformationControl; import org.eclipse.jface.text.IDelayedInputChangeProvider; @@ -288,11 +289,16 @@ public void setInput(Object input) { boolean RTL= (getShell().getStyle() & SWT.RIGHT_TO_LEFT) != 0; boolean resizable= isResizable(); + String scrollbarStyle= "overflow:scroll;"; //$NON-NLS-1$ + // workaround for bug 546870, don't use a horizontal scrollbar on Linux as its broken for GTK3 and WebKit + if (Util.isLinux()) { + scrollbarStyle= "word-wrap:break-word;"; //$NON-NLS-1$ + } // The default "overflow:auto" would not result in a predictable width for the client area // and the re-wrapping would cause visual noise String[] styles= null; if (RTL && resizable) - styles= new String[] { "direction:rtl;", "overflow:scroll;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + styles= new String[] { "direction:rtl;", scrollbarStyle, "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ else if (RTL && !resizable) styles= new String[] { "direction:rtl;", "overflow:hidden;", "word-wrap:break-word;" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ else if (!resizable) @@ -300,7 +306,7 @@ else if (!resizable) // Re-check whether we really still need this now that the Javadoc Hover header already sets this style. styles= new String[] { "overflow:hidden;"/*, "word-wrap: break-word;"*/}; //$NON-NLS-1$ else - styles= new String[] { "overflow:scroll;" }; //$NON-NLS-1$ + styles= new String[] { scrollbarStyle }; StringBuilder buffer= new StringBuilder(content); HTMLPrinter.insertStyles(buffer, styles); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/HTMLTextPresenter.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/HTMLTextPresenter.java index 93e77f0..3090f4f 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/HTMLTextPresenter.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/html/HTMLTextPresenter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,17 +14,22 @@ package org.eclipse.jface.internal.text.html; import java.io.IOException; -import java.io.Reader; import java.io.StringReader; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Drawable; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.graphics.TextLayout; +import org.eclipse.swt.graphics.TextStyle; import org.eclipse.swt.widgets.Display; -import org.eclipse.jface.internal.text.link.contentassist.LineBreakingReader; - import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextPresentation; @@ -36,13 +41,11 @@ */ public class HTMLTextPresenter implements DefaultInformationControl.IInformationPresenter, DefaultInformationControl.IInformationPresenterExtension { - private static final String LINE_DELIM= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + private static final String LINE_DELIM= System.lineSeparator(); - private int fCounter; private boolean fEnforceUpperLineLimit; public HTMLTextPresenter(boolean enforceUpperLineLimit) { - super(); fEnforceUpperLineLimit= enforceUpperLineLimit; } @@ -50,53 +53,6 @@ public HTMLTextPresenter() { this(true); } - protected Reader createReader(String hoverInfo, TextPresentation presentation) { - return new HTML2TextReader(new StringReader(hoverInfo), presentation); - } - - protected void adaptTextPresentation(TextPresentation presentation, int offset, int insertLength) { - - int yoursStart= offset; - - Iterator e= presentation.getAllStyleRangeIterator(); - while (e.hasNext()) { - - StyleRange range= e.next(); - - int myStart= range.start; - int myEnd= range.start + range.length -1; - myEnd= Math.max(myStart, myEnd); - - if (myEnd < yoursStart) - continue; - - if (myStart < yoursStart) - range.length += insertLength; - else - range.start += insertLength; - } - } - - private void append(StringBuilder buffer, String string, TextPresentation presentation) { - - int length= string.length(); - buffer.append(string); - - if (presentation != null) - adaptTextPresentation(presentation, fCounter, length); - - fCounter += length; - } - - private String getIndent(String line) { - int length= line.length(); - - int i= 0; - while (i < length && Character.isWhitespace(line.charAt(i))) ++i; - - return (i == length ? line : line.substring(0, i)) + " "; //$NON-NLS-1$ - } - /** * {@inheritDoc} * @@ -117,92 +73,148 @@ public String updatePresentation(Drawable drawable, String hoverInfo, TextPresen if (hoverInfo == null) return null; - GC gc= new GC(drawable); - try { - - StringBuilder buffer= new StringBuilder(); - int maxNumberOfLines= (int) Math.round((double) maxHeight / gc.getFontMetrics().getHeight()); - - fCounter= 0; - LineBreakingReader reader= new LineBreakingReader(createReader(hoverInfo, presentation), hoverInfo.length(), gc, maxWidth); + if (!fEnforceUpperLineLimit) { + maxHeight= Integer.MAX_VALUE; + } - boolean lastLineFormatted= false; - String lastLineIndent= null; + presentation.clear(); - String line=reader.readLine(); - boolean lineFormatted= reader.isFormattedLine(); - boolean firstLineProcessed= false; + try (HTML2TextReader reader= new HTML2TextReader(new StringReader(hoverInfo), presentation)){ + hoverInfo= reader.getString().trim(); + } catch (IOException e) { + return null; + } - while (line != null) { + Font[] fonts= new Font[(SWT.BOLD | SWT.ITALIC)]; + GC gc= new GC(drawable); + Font baseFont= gc.getFont(); // the html reader does not set font info, so ranges will based on the default font of the drawable + fonts[0]= baseFont; + try { + TextLayout layout= new TextLayout(gc.getDevice()); + try { + layout.setWidth(maxWidth); + layout.setText(hoverInfo); + layout.setFont(baseFont); + + for (Iterator iterator= presentation.getAllStyleRangeIterator(); iterator.hasNext();) { + StyleRange range= iterator.next(); + // text layouts don't use fontStyles, so use a bold/italic font instead + fonts[range.fontStyle]= maybeAllocateFont(baseFont, range.fontStyle, fonts[range.fontStyle]); + range.font= fonts[range.fontStyle]; + layout.setStyle(range, range.start, range.start + range.length - 1); + } - if (fEnforceUpperLineLimit && maxNumberOfLines <= 0) - break; + if (layout.getLineCount() == 0) { + return ""; //$NON-NLS-1$ + } - if (firstLineProcessed) { - if (!lastLineFormatted) - append(buffer, LINE_DELIM, null); - else { - append(buffer, LINE_DELIM, presentation); - if (lastLineIndent != null) - append(buffer, lastLineIndent, presentation); + int[] lineOffsets= layout.getLineOffsets(); + + int textHeight= 0; + StringBuilder buffer= new StringBuilder(); + Rectangle currentLineBounds= layout.getLineBounds(0); + int currentLineIndex= 0; + List positionOfInsertedLineBreaks= new ArrayList<>(); + boolean addMoreLines= true; + + // append lines to the buffer until we run out of vertical space + while (currentLineIndex < layout.getLineCount() - 1) { + textHeight+= currentLineBounds.height; + + Rectangle nextLineBounds= layout.getLineBounds(currentLineIndex + 1); + if (textHeight + nextLineBounds.height <= maxHeight) { + // we have room for at least the current line and the next one + String line= hoverInfo.substring(lineOffsets[currentLineIndex], lineOffsets[currentLineIndex + 1]); + buffer.append(line); + if (!line.endsWith(LINE_DELIM)) { + // new line was started by the layout wrapping the text, not by a line delimiter in the text, need to add it + positionOfInsertedLineBreaks.add(lineOffsets[currentLineIndex + 1]); + buffer.append(LINE_DELIM); + } + } else { + // we only have room for one more line; + addMoreLines= false; + if (currentLineIndex == 0) { + // never make the first line an ellipsis, even if we don't have enough space + buffer.append(hoverInfo.substring(lineOffsets[currentLineIndex], lineOffsets[currentLineIndex + 1])); + } else { + buffer.append(HTMLMessages.getString("HTMLTextPresenter.ellipse")); //$NON-NLS-1$ + } + break; } + currentLineBounds= nextLineBounds; + currentLineIndex++; + } + if (addMoreLines && currentLineIndex < layout.getLineCount()) { + // either there is only a single line, or we have enough space for all lines + buffer.append(hoverInfo.substring(lineOffsets[currentLineIndex], lineOffsets[currentLineIndex + 1])); } - append(buffer, line, null); - firstLineProcessed= true; + presentation.clear(); + int insertedBreaksCount= 0; // we need to adjust all positions by the space taken by inserted line breaks + int[] ranges= layout.getRanges(); + int[] adjustedRanges= new int[ranges.length]; + for (int i= 0; i < ranges.length; i++) { + while (insertedBreaksCount < positionOfInsertedLineBreaks.size() && + positionOfInsertedLineBreaks.get(insertedBreaksCount) <= ranges[i]) { + // advance in the list of positions until the current position is greater or equal than where the range offset is + insertedBreaksCount++; + } + adjustedRanges[i]= ranges[i] + insertedBreaksCount * System.lineSeparator().length(); + } - lastLineFormatted= lineFormatted; - if (!lineFormatted) - lastLineIndent= null; - else if (lastLineIndent == null) - lastLineIndent= getIndent(line); + for (int i= 0; i < ranges.length; i+= 2) { + // re-apply the styles, adjusting for inserted line breaks + TextStyle style= layout.getStyle(ranges[i]); - line= reader.readLine(); - lineFormatted= reader.isFormattedLine(); + StyleRange styleRange= new StyleRange(style); + styleRange.fontStyle= indexOf(fonts, style.font); + styleRange.font= null; // need to remove the bold/italic font: it's going to be disposed + styleRange.start= adjustedRanges[i]; + styleRange.length= adjustedRanges[i + 1] - adjustedRanges[i] + 1; + presentation.addStyleRange(styleRange); + } - maxNumberOfLines--; - } + presentation.setResultWindow(new Region(0, buffer.length())); - if (line != null) { - append(buffer, LINE_DELIM, lineFormatted ? presentation : null); - append(buffer, HTMLMessages.getString("HTMLTextPresenter.ellipse"), presentation); //$NON-NLS-1$ + return buffer.toString(); + } finally { + layout.dispose(); } - - return trim(buffer, presentation); - - } catch (IOException e) { - - // ignore TODO do something else? - return null; - } finally { gc.dispose(); + // dispose all fonts except the first font, which is the base font + for (int i= 1; i < fonts.length; i++) { + if (fonts[i] != null) { + fonts[i].dispose(); + } + } } } - private String trim(StringBuilder buffer, TextPresentation presentation) { - - int length= buffer.length(); - - int end= length -1; - while (end >= 0 && Character.isWhitespace(buffer.charAt(end))) - -- end; - - if (end == -1) - return ""; //$NON-NLS-1$ + private int indexOf(Font[] fonts, Font font) { + for (int i= 0; i < fonts.length; i++) { + if (fonts[i] == font) { + return i; + } + } + throw new IllegalArgumentException("Unexpected font found"); //$NON-NLS-1$ + } - if (end < length -1) - buffer.delete(end + 1, length); - else - end= length; + private Font maybeAllocateFont(Font baseFont, int fontStyle, Font font) { + if (font != null) { + return font; + } - int start= 0; - while (start < end && Character.isWhitespace(buffer.charAt(start))) - ++ start; + return new Font(baseFont.getDevice(), getFontData(baseFont, fontStyle)); + } - buffer.delete(0, start); - presentation.setResultWindow(new Region(start, buffer.length())); - return buffer.toString(); + private FontData[] getFontData(Font baseFont, int style) { + FontData[] fontDatas= baseFont.getFontData(); + for (FontData fontData : fontDatas) { + fontData.setStyle(style); + } + return fontDatas; } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/link/contentassist/LineBreakingReader.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/link/contentassist/LineBreakingReader.java deleted file mode 100644 index 81ae35c..0000000 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/link/contentassist/LineBreakingReader.java +++ /dev/null @@ -1,156 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.jface.internal.text.link.contentassist; - - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.text.BreakIterator; - -import org.eclipse.swt.graphics.GC; - -/* - * Not a real reader. Could change if requested - */ -public class LineBreakingReader { - - private BufferedReader fReader; - private GC fGC; - private int fMaxWidth; - - private String fLine; - private int fOffset; - - private BreakIterator fLineBreakIterator; - private boolean fBreakWords; - - /** - * Creates a reader that breaks an input text to fit in a given width. - * - * @param reader reader for the input text - * @param gc The graphic context that defines the currently used font sizes - * @param maxLineWidth The max width (pixels) where the text has to fit in - */ - public LineBreakingReader(Reader reader, GC gc, int maxLineWidth) { - this(new BufferedReader(reader), gc, maxLineWidth); - } - - /** - * Creates a reader that breaks an input text to fit in a given width. - * - * @param reader the reader for the input text - * @param bufferSize the buffer size - * @param gc The graphic context that defines the currently used font sizes - * @param maxLineWidth The max width (pixels) where the text has to fit in - */ - public LineBreakingReader(Reader reader, int bufferSize, GC gc, int maxLineWidth) { - this(new BufferedReader(reader, bufferSize), gc, maxLineWidth); - } - - /** - * Creates a reader that breaks an input text to fit in a given width. - * - * @param reader the buffered reader for the input text - * @param gc The graphic context that defines the currently used font sizes - * @param maxLineWidth The max width (pixels) where the text has to fit in - */ - private LineBreakingReader(BufferedReader reader, GC gc, int maxLineWidth) { - fReader= reader; - fGC= gc; - fMaxWidth= maxLineWidth; - fOffset= 0; - fLine= null; - fLineBreakIterator= BreakIterator.getLineInstance(); - fBreakWords= true; - } - - public boolean isFormattedLine() { - return fLine != null; - } - - /** - * Reads the next line. The lengths of the line will not exceed the given maximum width. - * - * @return the next line - * @throws IOException if an I/O error occurs - */ - public String readLine() throws IOException { - if (fLine == null) { - String line= fReader.readLine(); - if (line == null) - return null; - - int lineLen= fGC.textExtent(line).x; - if (lineLen < fMaxWidth) { - return line; - } - fLine= line; - fLineBreakIterator.setText(line); - fOffset= 0; - } - int breakOffset= findNextBreakOffset(fOffset); - String res; - if (breakOffset != BreakIterator.DONE) { - res= fLine.substring(fOffset, breakOffset); - fOffset= findWordBegin(breakOffset); - if (fOffset == fLine.length()) { - fLine= null; - } - } else { - res= fLine.substring(fOffset); - fLine= null; - } - return res; - } - - private int findNextBreakOffset(int currOffset) { - int currWidth= 0; - int nextOffset= fLineBreakIterator.following(currOffset); - while (nextOffset != BreakIterator.DONE) { - String word= fLine.substring(currOffset, nextOffset); - int wordWidth= fGC.textExtent(word).x; - int nextWidth= wordWidth + currWidth; - if (nextWidth > fMaxWidth) { - if (currWidth > 0) - return currOffset; - - if (!fBreakWords) - return nextOffset; - - // need to fit into fMaxWidth - int length= word.length(); - while (length > 0) { - length--; - word= word.substring(0, length); - wordWidth= fGC.textExtent(word).x; - if (wordWidth + currWidth < fMaxWidth) - return currOffset + length; - } - return nextOffset; - } - currWidth= nextWidth; - currOffset= nextOffset; - nextOffset= fLineBreakIterator.next(); - } - return nextOffset; - } - - private int findWordBegin(int idx) { - while (idx < fLine.length() && Character.isWhitespace(fLine.charAt(idx))) { - idx++; - } - return idx; - } -} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/source/DiffPainter.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/source/DiffPainter.java index 00b0414..05b2e14 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/source/DiffPainter.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/source/DiffPainter.java @@ -63,6 +63,8 @@ public void modelChanged(IAnnotationModel model) { private CompositeRuler fParentRuler; /** The column's control, typically a {@link Canvas}, possibly null. */ private Control fControl; + /** Display on which to post async runnables. */ + private Display fDisplay; /** The text viewer that the column is attached to. */ private ITextViewer fViewer; /** The viewer's text widget. */ @@ -179,6 +181,7 @@ private void connectIfNeeded() { return; fControl.addDisposeListener(e -> handleDispose()); + fDisplay= fControl.getDisplay(); } /** @@ -356,7 +359,7 @@ private void setDiffer(IAnnotationModel differ) { */ private final void postRedraw() { if (isConnected() && !fControl.isDisposed()) { - Display d= fControl.getDisplay(); + Display d= fDisplay; if (d != null) { d.asyncExec(this::redraw); } @@ -367,7 +370,9 @@ private final void postRedraw() { * Triggers redrawing of the column. */ private void redraw() { - fColumn.redraw(); + if (!fControl.isDisposed()) { + fColumn.redraw(); + } } /** diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java index 36fe3ff..feed6d2 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControl.java @@ -636,7 +636,7 @@ private void setStatusLabelColors(Color foreground, Color background) { */ @Override public boolean isFocusControl() { - return fShell.getDisplay().getActiveShell() == fShell; + return !fShell.isDisposed() && fShell.getDisplay().getActiveShell() == fShell; } /** diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java index 0ca3f4b..58f7443 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2020 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -267,7 +267,7 @@ private IRegion findWord(IDocument document, int offset, BreakIterator wordBreak return null; } - if (offset == line.getOffset() + line.getLength()) + if (offset > line.getOffset() + line.getLength()) return null; fDocIter.setDocument(document, line); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/FindReplaceDocumentAdapterContentProposalProvider.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/FindReplaceDocumentAdapterContentProposalProvider.java index 94cc140..de047a9 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/FindReplaceDocumentAdapterContentProposalProvider.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/FindReplaceDocumentAdapterContentProposalProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2009 IBM Corporation and others. + * Copyright (c) 2008, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -306,18 +306,10 @@ private void addBracketProposal(String proposal, int cursorPosition, String disp * @param additionalInfo the additional information */ private void addBsProposal(String proposal, String displayString, String additionalInfo) { - String prolog= fExpression.substring(0, fDocumentOffset); - int position= proposal.length(); - // If the string already contains the backslash, do not include in the proposal - if (prolog.endsWith("\\")) { //$NON-NLS-1$ - position--; - proposal= proposal.substring(1); - } - if (fIsEscape) { - fPriorityProposals.add(new ContentProposal(proposal, displayString, additionalInfo, position)); + fPriorityProposals.add(new ContentProposal(proposal.substring(1), displayString, additionalInfo, proposal.length() - 1)); } else { - addProposal(proposal, position, displayString, additionalInfo); + addProposal(proposal, proposal.length(), displayString, additionalInfo); } } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java index 5e931d2..3a4b649 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java @@ -71,11 +71,13 @@ public interface IBlockTextSelection extends ITextSelection { */ @Override String getText(); + /** * Returns a non-empty array containing the selected text range for each line covered by the * selection. * * @return an array containing a the covered text range for each line covered by the receiver + * @see IMultiTextSelection#getRegions() */ IRegion[] getRegions(); } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java index dc2939b..00042b6 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java @@ -20,31 +20,29 @@ /** * Defines the target for finding and replacing strings. *

- * The two main methods are findAndSelect and - * replaceSelection. The target does not provide any way to - * modify the content other than replacing the selection. + * The two main methods are findAndSelect and replaceSelection. The target + * does not provide any way to modify the content other than replacing the selection. *

* - * In order to provide backward compatibility for clients of - * IFindReplaceTarget, extension interfaces are used as a means - * of evolution. The following extension interfaces exist: + * In order to provide backward compatibility for clients of IFindReplaceTarget, + * extension interfaces are used as a means of evolution. The following extension interfaces exist: *

    - *
  • {@link org.eclipse.jface.text.IFindReplaceTargetExtension} since version - * 2.0 introducing the notion of find/replace session and of a find/replace - * scope. In additions, in allows clients to replace all occurrences of a given - * find query.
  • - *
  • {@link org.eclipse.jface.text.IFindReplaceTargetExtension3} since - * version 3.0 allowing clients to specify search queries as regular - * expressions.
  • + *
  • {@link org.eclipse.jface.text.IFindReplaceTargetExtension} since version 2.0 introducing the + * notion of find/replace session and of a find/replace scope. In additions, in allows clients to + * replace all occurrences of a given find query.
  • + *
  • {@link org.eclipse.jface.text.IFindReplaceTargetExtension3} since version 3.0 allowing + * clients to specify search queries as regular expressions.
  • + *
  • {@link org.eclipse.jface.text.IFindReplaceTargetExtension4} since version 3.19 allowing + * clients to select multiple text ranges in the target.
  • *
*

* Clients of a IFindReplaceTarget that also implements the - * IFindReplaceTargetExtension have to indicate the start of a find/replace - * session before using the target and to indicate the end of the session when the - * target is no longer used. + * IFindReplaceTargetExtension have to indicate the start of a find/replace session + * before using the target and to indicate the end of the session when the target is no longer used. * * @see org.eclipse.jface.text.IFindReplaceTargetExtension * @see org.eclipse.jface.text.IFindReplaceTargetExtension3 + * @see org.eclipse.jface.text.IFindReplaceTargetExtension4 */ public interface IFindReplaceTarget { diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java index fdce26b..ace456b 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java @@ -76,6 +76,7 @@ public interface IFindReplaceTargetExtension { * * @param offset the offset of the selection * @param length the length of the selection + * @see IFindReplaceTargetExtension4#setSelection(IRegion[]) */ void setSelection(int offset, int length); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension4.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension4.java new file mode 100644 index 0000000..ed88eb5 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension4.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text; + +/** + * Extension interface for {@link org.eclipse.jface.text.IFindReplaceTarget} providing methods to + * select multiple text ranges. + * + * @since 3.19 + */ +public interface IFindReplaceTargetExtension4 { + + void setSelection(IRegion[] ranges); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java new file mode 100644 index 0000000..d208cc7 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text; + +/** + * This interface represents a textual selection that can be made of multiple discontinued selected + * ranges. + * + * @since 3.19 + */ +public interface IMultiTextSelection extends ITextSelection { + + /** + * Returns a non-empty array containing the selected text range for each line covered by the + * selection. + * + * @return an array containing a the covered text range for each line covered by the receiver + */ + IRegion[] getRegions(); + +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java new file mode 100644 index 0000000..87eec04 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2019 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.Assert; + +/** + * + * @since 3.19 + */ +public class MultiTextSelection implements IMultiTextSelection { + + private final IDocument fDocument; + + private final IRegion[] fRegions; + + private final int fLength; + + private final int fStartLine; + + private final int fLastLine; + + private final String fText; + + public MultiTextSelection(IDocument document, IRegion[] regions) { + Assert.isNotNull(document); + Assert.isNotNull(regions); + fDocument= document; + fRegions= Arrays.copyOf(regions, regions.length); + Arrays.sort(fRegions, Comparator.comparingInt(IRegion::getOffset).thenComparingInt(IRegion::getLength)); + if (fRegions != null && fRegions.length > 0) { + IRegion lastRegion= fRegions[fRegions.length - 1]; + fLength= lastRegion.getOffset() + lastRegion.getLength() - fRegions[0].getOffset(); + fStartLine= getLineOfOffset(document, fRegions[0].getOffset()); + fLastLine= getLineOfOffset(document, lastRegion.getOffset() + lastRegion.getLength()); + fText= Arrays.stream(fRegions) + .map(region -> { + try { + return fDocument.get(region.getOffset(), region.getLength()); + } catch (BadLocationException e) { + return e.getMessage(); + } + }) + .collect(Collectors.joining()); + } else { + fLength= 0; + fStartLine= 0; + fLastLine= 0; + fText= null; + } + } + + private static int getLineOfOffset(IDocument document, int offset) { + try { + return document.getLineOfOffset(offset); + } catch (BadLocationException e) { + return 0; + } + } + + @Override + public int getOffset() { + if (fRegions.length > 0) { + return fRegions[0].getOffset(); + } + return 0; + } + + @Override + public int getLength() { + return fLength; + } + + @Override + public int getStartLine() { + return fStartLine; + } + + @Override + public int getEndLine() { + return fLastLine; + } + + @Override + public String getText() { + return fText; + } + + @Override + public boolean isEmpty() { + return Arrays.stream(fRegions).allMatch(region -> region.getLength() == 0); + } + + @Override + public IRegion[] getRegions() { + return fRegions; + } + +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java index 4352ed9..ebaa434 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -25,7 +25,7 @@ * generated from a selection provider, it only remembers its offset and length * and computes the remaining information on request.

*/ -public class TextSelection implements ITextSelection { +public class TextSelection implements IMultiTextSelection { /** * Debug option for asserting valid offset and length. @@ -225,5 +225,10 @@ public String toString() { sb.append("]"); //$NON-NLS-1$ return sb.toString(); } + + @Override + public IRegion[] getRegions() { + return new IRegion[] { new Region(getOffset(), getLength()) }; + } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java index 099931c..eef1d5b 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java @@ -24,6 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.regex.PatternSyntaxException; @@ -66,8 +67,6 @@ import org.eclipse.core.runtime.Assert; -import org.eclipse.text.edits.TextEdit; - import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.internal.text.NonDeletingPositionUpdater; import org.eclipse.jface.internal.text.SelectionProcessor; @@ -766,7 +765,7 @@ else if (offset < fPosition.getOffset() + fPosition.getLength()) /** * This viewer's find/replace target. */ - class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension3 { + class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension3, IFindReplaceTargetExtension4 { /** The range for this target. */ private FindReplaceRange fRange; @@ -908,6 +907,11 @@ public void setSelection(int modelOffset, int modelLength) { TextViewer.this.setSelectedRange(modelOffset, modelLength); } + @Override + public void setSelection(IRegion[] widgetRegions) { + TextViewer.this.setSelectedRanges(Arrays.stream(widgetRegions).map(TextViewer.this::widgetRange2ModelRange).toArray(IRegion[]::new)); + } + @Override public void setScope(IRegion scope) { if (fRange != null) @@ -1077,7 +1081,7 @@ private static final class ColumnPosition extends Position { */ private final class ViewerState { /** The position tracking the selection. */ - private Position fSelection; + private Position[] fSelections; /** true if {@link #fSelection} was originally backwards. */ private boolean fReverseSelection; /** true if the selection has been updated while in redraw(off) mode. */ @@ -1108,24 +1112,30 @@ public ViewerState() { * * @return the normalized selection */ - public Point getSelection() { - if (fSelection == null) - return new Point(-1, -1); - return new Point(fSelection.getOffset(), fSelection.getLength()); + public Point[] getSelection() { + if (fSelections == null) + return new Point[0]; + return Arrays.stream(fSelections).map(position -> new Point(position.getOffset(), position.getLength())).toArray(Point[]::new); } /** * Updates the selection. * - * @param offset the new selection offset - * @param length the new selection length + * @param selections new selections */ - public void updateSelection(int offset, int length) { + public void updateSelection(Position[] selections) { fSelectionSet= true; - if (fSelection == null) - fSelection= new Position(offset, length); - else - updatePosition(fSelection, offset, length); + if (selections == null) { + fSelections = new Position[0]; + } else if (fSelections != null && fSelections.length == selections.length) { + for (int i = 0; i < fSelections.length; i++) { + updatePosition(fSelections[i], selections[i].getOffset(), selections[i].getLength()); + } + } else { + fSelections= Arrays.stream(selections) + .map(position -> new Position(position.getOffset(), position.getLength())) /*force deleted=false*/ + .toArray(Position[]::new); + } } /** @@ -1138,18 +1148,18 @@ public void updateSelection(int offset, int length) { public void restore(boolean restoreViewport) { if (isConnected()) disconnect(); - if (fSelection != null) { - if (fSelection instanceof ColumnPosition) { - ColumnPosition cp= (ColumnPosition)fSelection; + if (fSelections != null && fSelections.length > 0) { + if (fSelections[0] instanceof ColumnPosition) { + ColumnPosition cp= (ColumnPosition) fSelections[0]; IDocument document= fDocument; try { - int startLine= document.getLineOfOffset(fSelection.getOffset()); + int startLine= document.getLineOfOffset(cp.getOffset()); int startLineOffset= document.getLineOffset(startLine); - int selectionEnd= fSelection.getOffset() + fSelection.getLength(); + int selectionEnd= cp.getOffset() + cp.getLength(); int endLine= document.getLineOfOffset(selectionEnd); int endLineOffset= document.getLineOffset(endLine); int tabs= getTextWidget().getTabs(); - int startColumn= fSelection.getOffset() - startLineOffset + cp.fStartColumn; + int startColumn= cp.getOffset() - startLineOffset + cp.fStartColumn; int endColumn= selectionEnd - endLineOffset + cp.fEndColumn; setSelection(new BlockTextSelection(document, startLine, startColumn, endLine, endColumn, tabs)); } catch (BadLocationException e) { @@ -1157,13 +1167,11 @@ public void restore(boolean restoreViewport) { setSelectedRange(cp.getOffset(), cp.getLength()); } } else { - int offset= fSelection.getOffset(); - int length= fSelection.getLength(); - if (fReverseSelection) { - offset+= length; - length= -length; - } - setSelectedRange(offset, length); + setSelectedRanges(Arrays.stream(fSelections) + .map(position -> fReverseSelection + ? new Region(position.getOffset() + position.getLength(), -position.getLength()) + : new Region(position.getOffset(), position.getLength())) + .toArray(IRegion[]::new)); } if (restoreViewport) updateViewport(); @@ -1177,7 +1185,7 @@ public void restore(boolean restoreViewport) { */ private void updateViewport() { if (fSelectionSet) { - revealRange(fSelection.getOffset(), fSelection.getLength()); + revealRange(fSelections[0].getOffset(), fSelections[0].getLength()); } else if (fStableLine != null) { int stableLine; try { @@ -1216,17 +1224,25 @@ private void connect(IDocument document) { IBlockTextSelection bts= (IBlockTextSelection) selection; int startVirtual= Math.max(0, bts.getStartColumn() - document.getLineInformationOfOffset(bts.getOffset()).getLength()); int endVirtual= Math.max(0, bts.getEndColumn() - document.getLineInformationOfOffset(bts.getOffset() + bts.getLength()).getLength()); - fSelection= new ColumnPosition(bts.getOffset(), bts.getLength(), startVirtual, endVirtual); + fSelections= new Position[] { new ColumnPosition(bts.getOffset(), bts.getLength(), startVirtual, endVirtual) }; + } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) { + IMultiTextSelection multiSelection= (IMultiTextSelection) selection; + fSelections= Arrays.stream(multiSelection.getRegions()) + .map(region -> new Position(region.getOffset(), region.getLength())) + .toArray(Position[]::new); + fReverseSelection= (fTextWidget.getCaretOffset() == fSelections[0].getOffset()); } else { Point range= fTextWidget.getSelectionRange(); int caretOffset= fTextWidget.getCaretOffset(); fReverseSelection= caretOffset == range.x; Point selectionRange= getSelectedRange(); - fSelection= new Position(selectionRange.x, selectionRange.y); + fSelections= new Position[] { new Position(selectionRange.x, selectionRange.y) }; } fSelectionSet= false; - fUpdaterDocument.addPosition(fUpdaterCategory, fSelection); + for (Position position : fSelections) { + fUpdaterDocument.addPosition(fUpdaterCategory, position); + } int stableLine= getStableLine(); int stableWidgetLine= modelLine2WidgetLine(stableLine); @@ -1291,7 +1307,9 @@ private boolean isConnected() { private void disconnect() { Assert.isTrue(isConnected()); try { - fUpdaterDocument.removePosition(fUpdaterCategory, fSelection); + for (Position selection : fSelections) { + fUpdaterDocument.removePosition(fUpdaterCategory, selection); + } fUpdaterDocument.removePosition(fUpdaterCategory, fStableLine); fUpdaterDocument.removePositionUpdater(fUpdater); fUpdater= null; @@ -1513,7 +1531,7 @@ public void documentRewriteSessionChanged(DocumentRewriteSessionEvent event) { * Last selection range sent to selection change listeners. * @since 3.0 */ - private IRegion fLastSentSelectionChange; + private IRegion[] fLastSentSelectionChange; /** * The registered post selection changed listeners. * @since 3.0 @@ -2251,9 +2269,13 @@ public ITextSelection getLastKnownSelection() { @Override public Point getSelectedRange() { - - if (!redraws() && fViewerState != null) - return fViewerState.getSelection(); + // TODO multi-cursor: this method is by designed single selection + if (!redraws() && fViewerState != null) { + Point[] selections= fViewerState.getSelection(); + if (selections.length > 0) { + return selections[0]; + } + } if (fTextWidget != null) { Point p= fTextWidget.getSelectionRange(); @@ -2267,26 +2289,49 @@ public Point getSelectedRange() { @Override public void setSelectedRange(int selectionOffset, int selectionLength) { + setSelectedRanges(new IRegion[] { new Region(selectionOffset, selectionLength) }); + } + private static Position toPosition(IRegion region) { + return region.getLength() < 0 ? new Position(region.getOffset() + region.getLength(), -region.getLength()) : new Position(region.getOffset(), region.getLength()); + } + + /** + * + * @param modelRanges are in model (document) domain + */ + private void setSelectedRanges(IRegion[] modelRanges) { if (!redraws()) { if (fViewerState != null) - fViewerState.updateSelection(selectionOffset, selectionLength); + fViewerState.updateSelection(Arrays.stream(modelRanges).map(TextViewer::toPosition).toArray(Position[]::new)); return; } if (fTextWidget == null) return; - IRegion widgetSelection= modelRange2ClosestWidgetRange(new Region(selectionOffset, selectionLength)); - if (widgetSelection != null) { - - int[] selectionRange= new int[] { widgetSelection.getOffset(), widgetSelection.getLength() }; - validateSelectionRange(selectionRange); - if (selectionRange[0] >= 0) { - fTextWidget.setSelectionRange(selectionRange[0], selectionRange[1]); - selectionChanged(selectionRange[0], selectionRange[1]); - } + IRegion[] widgetSelection= Arrays.stream(modelRanges) + .map(range -> new Region(range.getOffset(), range.getLength())) + .map(this::modelRange2ClosestWidgetRange) + .filter(Objects::nonNull) + .map(range -> new int[] { range.getOffset(), range.getLength() }) + .map(range -> { + validateSelectionRange(range); + return range; + }) + .map(rangeAsArray -> new Region(rangeAsArray[0], rangeAsArray[1])) + .filter(widgetRange -> widgetRange.getOffset() >= 0) + .toArray(IRegion[]::new); + if (widgetSelection.length == 0) { + return; + } + int[] widgetRanges= new int[2 * widgetSelection.length]; + for (int i= 0; i < widgetSelection.length; i++) { + widgetRanges[2 * i]= widgetSelection[i].getOffset(); + widgetRanges[2 * i + 1]= widgetSelection[i].getLength(); } + fTextWidget.setSelectionRanges(widgetRanges); + selectionChanged(widgetSelection); } /** @@ -2415,6 +2460,14 @@ public void setSelection(ISelection selection, boolean reveal) { } if (reveal) revealRange(s.getOffset(), s.getLength()); + } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) { + IMultiTextSelection multiSelection= (IMultiTextSelection) selection; + setSelectedRanges(Arrays.stream(multiSelection.getRegions()) + .map(region -> new Region(region.getOffset(), region.getLength())) + .toArray(IRegion[]::new)); + if (reveal && multiSelection.getRegions().length > 0) { + revealRange(multiSelection.getRegions()[0].getOffset(), multiSelection.getRegions()[0].getLength()); + } } else if (selection instanceof ITextSelection) { ITextSelection s= (ITextSelection) selection; setSelectedRange(s.getOffset(), s.getLength()); @@ -2466,11 +2519,21 @@ private ITextSelection computeSelection() { } } - Point p= getSelectedRange(); - if (p.x == -1 || p.y == -1) + if (!redraws() && fViewerState != null) { + return toSelection(Arrays.stream(fViewerState.getSelection()).map(point -> new Region(point.x, point.y)).toArray(IRegion[]::new)); + } + + if (fTextWidget == null) return TextSelection.emptySelection(); - return new TextSelection(getDocument(), p.x, p.y); + int[] ranges= fTextWidget.getSelectionRanges(); + IRegion[] selectedRanges= new IRegion[ranges.length / 2]; + for (int i= 0; i < selectedRanges.length; i++) { + Point widgetSelection= new Point(ranges[2 * i], ranges[2 * i + 1]); + Point modelSelection= widgetSelection2ModelSelection(widgetSelection); + selectedRanges[i]= new Region(modelSelection.x, modelSelection.y); + } + return toSelection(selectedRanges); } /** @@ -2595,9 +2658,13 @@ protected void firePostSelectionChanged(int offset, int length) { * @param length the length of the newly selected range in the visible document */ protected void selectionChanged(int offset, int length) { + selectionChanged(new IRegion[] { new Region(offset, length) }); + } + + private void selectionChanged(IRegion[] widgetRanges) { updateSelectionCache(); queuePostSelectionChanged(true); - fireSelectionChanged(offset, length); + fireSelectionChanged(widgetRanges); } /** @@ -2608,21 +2675,34 @@ protected void selectionChanged(int offset, int length) { * @since 3.0 */ protected void fireSelectionChanged(int offset, int length) { + fireSelectionChanged(new Region[] { new Region(offset, length) }); + } + + private void fireSelectionChanged(IRegion[] widgetRanges) { if (redraws()) { - if (length < 0) { - length= -length; - offset= offset + length; - } - IRegion r= widgetRange2ModelRange(new Region(offset, length)); - if ((r != null && !r.equals(fLastSentSelectionChange)) || r == null) { - fLastSentSelectionChange= r; - ISelection selection= r != null ? new TextSelection(getDocument(), r.getOffset(), r.getLength()) : TextSelection.emptySelection(); - SelectionChangedEvent event= new SelectionChangedEvent(this, selection); + IRegion[] ranges = Arrays.stream(widgetRanges) + .map(range -> range.getLength() < 0 ? new Region(range.getOffset() + range.getLength(), -range.getLength()) : range) + .map(this::widgetRange2ModelRange) + .filter(Objects::nonNull) + .toArray(IRegion[]::new); + if (ranges.length == 0 || !Arrays.equals(ranges, fLastSentSelectionChange)) { + fLastSentSelectionChange= ranges; + SelectionChangedEvent event= new SelectionChangedEvent(this, toSelection(ranges)); fireSelectionChanged(event); } } } + private ITextSelection toSelection(IRegion[] ranges) { + ITextSelection selection= TextSelection.emptySelection(); + if (ranges.length == 1) { + selection= new TextSelection(getDocument(), ranges[0].getOffset(), ranges[0].getLength()); + } else if (ranges.length > 1) { + selection= new MultiTextSelection(getDocument(), ranges); + } + return selection; + } + /** * Sends the given event to all registered post selection changed listeners. * @@ -3646,12 +3726,16 @@ protected void handleVerifyEvent(VerifyEvent e) { return; } + ITextSelection selection= (ITextSelection)getSelection(); if (fTextWidget.getBlockSelection() && (e.text == null || e.text.length() < 2)) { Point sel = fTextWidget.getSelection(); if (fTextWidget.getLineAtOffset(sel.x) != fTextWidget.getLineAtOffset(sel.y)) { - verifyEventInBlockSelection(e); + verifyEventInMultiOrBlockSelection(e); return; } + } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) { + verifyEventInMultiOrBlockSelection(e); + return; } IRegion modelRange= event2ModelRange(e); @@ -3716,9 +3800,8 @@ else if (documentCaret >= region.getOffset() + region.getLength()) * Simulates typing behavior in block selection mode. * * @param e the verify event. - * @since 3.5 */ - private void verifyEventInBlockSelection(final VerifyEvent e) { + private void verifyEventInMultiOrBlockSelection(final VerifyEvent e) { /* Implementation Note: StyledText sends a sequence of n events for a single character typed, where n is the number of affected lines. Since @@ -3739,12 +3822,8 @@ private void verifyEventInBlockSelection(final VerifyEvent e) { ISelection selection= getSelection(); int length= e.text.length(); if (length == 0 && e.character == '\0') { - // backspace in StyledText block selection mode... - TextEdit edit= processor.backspace(selection); - edit.apply(fDocument, TextEdit.UPDATE_REGIONS); - ISelection empty= processor.makeEmpty(selection, true); - setSelection(empty); - } else { + processor.doBackspace(selection); + } else if (selection instanceof IBlockTextSelection) { int lines= processor.getCoveredLines(selection); String delim= fDocument.getLegalLineDelimiters()[0]; StringBuilder text= new StringBuilder(lines * length + (lines - 1) * delim.length()); @@ -3754,6 +3833,8 @@ private void verifyEventInBlockSelection(final VerifyEvent e) { text.append(e.text); } processor.doReplace(selection, text.toString()); + } else if (selection instanceof IMultiTextSelection) { + processor.doReplace(selection, e.text); } } catch (BadLocationException x) { if (TRACE_ERRORS) @@ -3901,8 +3982,7 @@ public void doOperation(int operation) { // Workaround to fix bug 434791 during 4.4 RC2. Will be replaced by official API during 4.5. case -100: if (fLastSentSelectionChange != null) { - ISelection lastSelection= new TextSelection(getDocument(), fLastSentSelectionChange.getOffset(), fLastSentSelectionChange.getLength()); - fireSelectionChanged(new SelectionChangedEvent(this, lastSelection)); + fireSelectionChanged(new SelectionChangedEvent(this, toSelection(fLastSentSelectionChange))); } return; @@ -3910,7 +3990,7 @@ public void doOperation(int operation) { } private void delete() { - if (!fTextWidget.getBlockSelection()) { + if (!fTextWidget.getBlockSelection() && isWidgetSelectionSingleRange()) { fTextWidget.invokeAction(ST.DELETE_NEXT); } else { wrapCompoundChange(() -> { @@ -3926,9 +4006,13 @@ private void delete() { fireSelectionChanged(selection.x, selection.y); } + private boolean isWidgetSelectionSingleRange() { + return fTextWidget.getSelectionRanges().length <= 2; + } + private void paste() { // ignoreAutoEditStrategies(true); - if (!fTextWidget.getBlockSelection()) { + if (isWidgetSelectionSingleRange()) { // single selection fTextWidget.paste(); } else { wrapCompoundChange(() -> { diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java index e1e4d8a..edfe22c 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java @@ -175,7 +175,7 @@ public void documentUndoNotification(DocumentUndoEvent event ){ // Reveal the change if this manager's viewer has the focus. if (fTextViewer != null) { StyledText widget= fTextViewer.getTextWidget(); - if (widget != null && !widget.isDisposed() && (widget.isFocusControl()))// || fTextViewer.getTextWidget() == control)) + if (widget != null && !widget.isDisposed() && (widget.isFocusControl()) && (widget.getSelectionRanges().length <= 2))// || fTextViewer.getTextWidget() == control)) selectAndReveal(event.getOffset(), event.getText() == null ? 0 : event.getText().length()); } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/CodeMiningReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/CodeMiningReconciler.java index cf66a7d..d642aa6 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/CodeMiningReconciler.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/CodeMiningReconciler.java @@ -44,10 +44,11 @@ public void install(ITextViewer textViewer) { @Override public void uninstall() { ITextViewer viewer= getTextViewer(); - if (viewer != null && viewer.getTextWidget().getData(KEY) == this) { + if (viewer != null && viewer.getTextWidget() != null && viewer.getTextWidget().getData(KEY) == this) { super.uninstall(); viewer.getTextWidget().setData(KEY, null); } + viewer= null; } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java new file mode 100644 index 0000000..e52786f --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/codemining/LineEndCodeMining.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2022, Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jface.text.codemining; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Position; + +/** + * A code mining that is positioned on end of a line. + * + * @since 3.20 + */ +public abstract class LineEndCodeMining extends LineContentCodeMining { + + protected LineEndCodeMining(IDocument document, int line, ICodeMiningProvider provider) throws BadLocationException { + super(getLineEndPosition(document, line), provider); + } + + private static Position getLineEndPosition(IDocument document, int line) throws BadLocationException { + int lastCharOffset= document.getLineOffset(line) + document.getLineLength(line); + String delimiter= document.getLineDelimiter(line); + return delimiter == null ? // last line of document + new Position(document.getLength(), 0) : // + new Position(lastCharOffset - delimiter.length(), delimiter.length()); + } + +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java index 47e14c1..3333639 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java @@ -59,7 +59,7 @@ private static abstract class Timer { * elapsed after it was scheduled without a {@link #reset(ICompletionProposal) reset} * to occur. */ - private abstract class Task implements Runnable { + private abstract static class Task implements Runnable { /** * @return the delay in milliseconds before this task should be run */ diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java index 3383c9f..64c5415 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AsyncCompletionProposalPopup.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -36,6 +37,7 @@ import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; import org.eclipse.core.runtime.SafeRunner; @@ -66,6 +68,8 @@ class AsyncCompletionProposalPopup extends CompletionProposalPopup { private Collection> toCancelFutures= new LinkedList<>(); + private PopupVisibleTimer fPopupVisibleTimer= new PopupVisibleTimer(); + private static final class ComputingProposal implements ICompletionProposal, ICompletionProposalExtension { private final int fOffset; @@ -203,7 +207,8 @@ private void computeAndPopulateProposals(int offset, Consumer 1) + || (!stillComputing && !fComputedProposals.isEmpty()); + + if ((autoActivated && hasProposals) || !autoActivated) { + setProposals(fComputedProposals, false); + displayProposals(true); + } else if (isValid(fProposalShell) && !fProposalShell.isVisible() && remaining.get() == 0) { + hide(); // we only tear down if the popup is not visible. + } } }); } @@ -264,7 +277,19 @@ && canAutoInsert(fComputedProposals.get(0))) { fAggregatedPopulateFuture= CompletableFuture.allOf(populateFutures.toArray(new CompletableFuture[populateFutures.size()])); toCancelFutures.add(fAggregatedPopulateFuture); } - displayProposals(); + displayProposals(!autoActivated); + } + + @Override + void displayProposals(boolean showPopup) { + if (showPopup) { + fPopupVisibleTimer.stop(); + } + + super.displayProposals(showPopup); + if (!showPopup) { + fPopupVisibleTimer.start(); + } } @Override @@ -330,6 +355,7 @@ protected List computeFilteredProposals(int offset, Documen @Override public void hide() { + fPopupVisibleTimer.stop(); super.hide(); cancelFutures(); } @@ -380,4 +406,40 @@ private String getTokenContentType(int invocationOffset) throws BadLocationExcep return IDocument.DEFAULT_CONTENT_TYPE; } + private class PopupVisibleTimer implements Runnable { + private Thread fThread; + + private Object fMutex= new Object(); + + private int fAutoActivationDelay= 500; + + protected void start() { + fThread= new Thread(this, JFaceTextMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$ + fThread.start(); + } + + @Override + public void run() { + try { + while (true) { + synchronized (fMutex) { + if (fAutoActivationDelay != 0) + fMutex.wait(fAutoActivationDelay); + } + Optional display= Optional.ofNullable(fContentAssistSubjectControlAdapter.getControl()).map(Control::getDisplay); + display.ifPresent(d -> d.asyncExec(() -> displayProposals(true))); + break; + } + } catch (InterruptedException e) { + } + fThread= null; + } + + protected void stop() { + Thread threadToStop= fThread; + if (threadToStop != null && threadToStop.isAlive()) + threadToStop.interrupt(); + } + + } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java index 4c27e4c..13b12ec 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java @@ -1219,10 +1219,21 @@ Point getSize() { } /** - * Displays this popup and install the additional info controller, so that additional info - * is displayed when a proposal is selected and additional info is available. + * Displays this popup and install the additional info controller, so that additional info is + * displayed when a proposal is selected and additional info is available. */ void displayProposals() { + displayProposals(true); + } + + /** + * Displays this popup and install the additional info controller, so that additional info is + * displayed when a proposal is selected and additional info is available. + * + * @param showPopup if true show the popup or otherwise initialize the popup and listeners + * without showing the popup. + */ + void displayProposals(boolean showPopup) { if (!isValid(fProposalShell) || !isValid(fProposalTable)) return; @@ -1231,45 +1242,55 @@ void displayProposals() { ensureDocumentListenerInstalled(); - if (fFocusHelper == null) { - fFocusHelper= new IEditingSupport() { + if (showPopup) { + configureAndMakeVisible(); + } + } else if (!fProposalShell.isVisible() && showPopup) { + // if a previous call did initialize but did make the popup visible, this will make sure the popup will be visible. + configureAndMakeVisible(); + } else + hide(); + } - @Override - public boolean isOriginator(DocumentEvent event, IRegion focus) { - return false; // this helper just covers the focus change to the proposal shell, no remote editions - } + private void configureAndMakeVisible() { + if (fFocusHelper == null) { + fFocusHelper= new IEditingSupport() { - @Override - public boolean ownsFocusShell() { - return true; - } + @Override + public boolean isOriginator(DocumentEvent event, IRegion focus) { + return false; // this helper just covers the focus change to the proposal shell, no remote editions + } - }; - } - if (fViewer instanceof IEditingSupportRegistry) { - IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; - registry.register(fFocusHelper); - } + @Override + public boolean ownsFocusShell() { + return true; + } + }; + } - /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646 - * on GTK, setVisible and such may run the event loop - * (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511) - * Since the user may have already canceled the popup or selected - * an entry (ESC or RETURN), we have to double check whether - * the table is still okToUse. See comments below - */ - fProposalShell.setVisible(true); // may run event loop on GTK - // transfer focus since no verify key listener can be attached - if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && isValid(fProposalShell)) - fProposalShell.setFocus(); // may run event loop on GTK ?? - - if (fAdditionalInfoController != null && isValid(fProposalTable)) { - fAdditionalInfoController.install(fProposalTable); - fAdditionalInfoController.handleTableSelectionChanged(); - } - } else - hide(); + if (fViewer instanceof IEditingSupportRegistry) { + IEditingSupportRegistry registry= (IEditingSupportRegistry) fViewer; + registry.register(fFocusHelper); + } + + + /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=52646 + * on GTK, setVisible and such may run the event loop + * (see also https://bugs.eclipse.org/bugs/show_bug.cgi?id=47511) + * Since the user may have already canceled the popup or selected + * an entry (ESC or RETURN), we have to double check whether + * the table is still okToUse. See comments below + */ + fProposalShell.setVisible(true); // may run event loop on GTK + // transfer focus since no verify key listener can be attached + if (!fContentAssistSubjectControlAdapter.supportsVerifyKeyListener() && isValid(fProposalShell)) + fProposalShell.setFocus(); // may run event loop on GTK ?? + + if (fAdditionalInfoController != null && isValid(fProposalTable)) { + fAdditionalInfoController.install(fProposalTable); + fAdditionalInfoController.handleTableSelectionChanged(); + } } /** @@ -1382,6 +1403,16 @@ public boolean verifyKey(VerifyEvent e) { fProposalShell.setFocus(); return false; + case SWT.BS: { + try { + if (fFilterOffset > 0 && fContentAssistSubjectControlAdapter.getDocument().getChar(fFilterOffset - 1) == SWT.SPACE) + hide(); + } catch (BadLocationException e1) { + // ignore error + } + break; + } + default: if (fContentAssistant.isCompletionProposalTriggerCharsEnabled()) { ICompletionProposal p= getSelectedProposal(); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java index 21e307e..151b7f9 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java @@ -75,6 +75,7 @@ import org.eclipse.jface.preference.JFacePreferences; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.util.OpenStrategy; +import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; @@ -133,6 +134,12 @@ class Closer implements ControlListener, MouseListener, FocusListener, DisposeLi */ private Control fControl; + private Point fCaretLocation; + + private int fCaretOffset; + + private final ISelectionChangedListener fSelectionListener= e -> updateCurrentCaretInfo(); + /** * Installs this closer on it's viewer's text widget. */ @@ -154,8 +161,19 @@ protected void install() { */ control.addDisposeListener(this); } - if (fViewer != null) + if (fViewer != null) { fViewer.addViewportListener(this); + fViewer.getSelectionProvider().addSelectionChangedListener(fSelectionListener); + updateCurrentCaretInfo(); + } + } + + private void updateCurrentCaretInfo() { + if (fViewer == null) { + return; + } + fCaretLocation= fViewer.getTextWidget().getCaret().getLocation(); + fCaretOffset= fViewer.getSelectedRange().x; } /** @@ -181,8 +199,10 @@ protected void uninstall() { control.removeDisposeListener(this); } - if (fViewer != null) + if (fViewer != null) { fViewer.removeViewportListener(this); + fViewer.getSelectionProvider().removeSelectionChangedListener(fSelectionListener); + } } @Override @@ -241,6 +261,12 @@ public void widgetDisposed(DisposeEvent e) { @Override public void viewportChanged(int topIndex) { + if (fViewer != null && fCaretLocation != null && // + fViewer.getTextWidget().getCaret().getLocation().equals(fCaretLocation) && // + fViewer.getSelectedRange().x == fCaretOffset) { + // Most likely some codemining altered viewport but didn't modify the caret position + return; + } hide(); } } @@ -1052,6 +1078,15 @@ public void handleException(Throwable exception) { private boolean fCompletionProposalTriggerCharsEnabled= true; + /** + * Tells whether this completion list is shown on each valid character which is either a letter + * or digit. This works conjunction with {@link #fAsynchronous} + * + * @since 3.20 + */ + private boolean fAutoActivateCompletionOnType= false; + + /** * Creates a new content assistant. The content assistant is not automatically activated, * overlays the completion proposals with context information list if necessary, and shows the @@ -1077,6 +1112,7 @@ public ContentAssistant() { public ContentAssistant(boolean asynchronous) { fPartitioning= IDocumentExtension3.DEFAULT_PARTITIONING; fAsynchronous= asynchronous; + enableAutoActivateCompletionOnType(Boolean.getBoolean("org.eclipse.jface.assist.activateCompletionOnType")); //$NON-NLS-1$ } /** @@ -1202,6 +1238,11 @@ TriggerType getAutoActivationTriggerType(char c) { if (processors == null) { return TriggerType.NONE; } + + if (fAutoActivateCompletionOnType && (Character.isLetter(c) || Character.isDigit(c))) { + return TriggerType.COMPLETION_PROPOSAL; + } + for (IContentAssistProcessor processor : processors) { IContentAssistProcessorExtension extension= IContentAssistProcessorExtension.adapt(processor); if (extension.isCompletionProposalAutoActivation(c, fViewer, offset)) { @@ -2721,4 +2762,20 @@ boolean isAutoActivation() { return fIsAutoActivated; } + /** + * Sets whether this completion list is shown on each valid character which is either a letter + * or digit. This works conjunction with {@link #fAsynchronous} + * + * @param enable whether or not to enable this feature + * @since 3.21 + */ + public final void enableAutoActivateCompletionOnType(boolean enable) { + if (fAsynchronous) { + fAutoActivateCompletionOnType= enable; + } + } + + boolean isAutoActivateCompletionOnType() { + return fAutoActivateCompletionOnType; + } } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java index 3caa8c6..3feeb60 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java @@ -93,12 +93,12 @@ public interface IContentAssistProcessor { String getErrorMessage(); /** - * Returns a validator used to determine when displayed context information - * should be dismissed. May only return null if the processor is - * incapable of computing context information.

+ * Returns a validator used to determine when displayed context information should be dismissed. + * May only return null if the processor is incapable of computing context + * information. * - * @return a context information validator, or null if the processor - * is incapable of computing context information + * @return a context information validator, or null if the processor is incapable + * of computing context information */ IContextInformationValidator getContextInformationValidator(); } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContextBasedFormattingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContextBasedFormattingStrategy.java index 0136772..b266e0c 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContextBasedFormattingStrategy.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContextBasedFormattingStrategy.java @@ -21,7 +21,6 @@ * Formatting strategy for context based content formatting. Retrieves the preferences * set on the formatting context's {@link FormattingContextProperties#CONTEXT_PREFERENCES} * property and makes them available to subclasses. - *

* * @since 3.0 */ diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatterExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatterExtension.java index d06bbc5..5a16b2b 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatterExtension.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatterExtension.java @@ -19,29 +19,28 @@ /** * Extension interface for {@link IContentFormatter}. *

- * Updates the content formatter to be able to pass {@link IFormattingContext} - * context objects to {@link IFormattingStrategyExtension} objects - * operating in context based mode. - *

+ * Updates the content formatter to be able to pass {@link IFormattingContext} context objects to + * {@link IFormattingStrategyExtension} objects operating in context based mode. + *

* Clients using context based formatting call the method - * format(IDocument, IFormattingContext) with a properly - * initialized formatting context.
+ * format(IDocument, IFormattingContext) with a properly initialized formatting + * context.
* The formatting context must be set up according to the desired formatting mode: *
    - *
  • For whole document formatting set the property {@link FormattingContextProperties#CONTEXT_DOCUMENT}. - * This is equivalent to setting {@link FormattingContextProperties#CONTEXT_REGION} with a region spanning - * the whole document.
  • - *
  • For multiple region formatting set the property {@link FormattingContextProperties#CONTEXT_REGION}. - * Note that the content formatter automatically aligns the region to a block selected region, - * and if the region spans multiple partitions, it also completes eventual partitions covered only - * partially by the region.
  • + *
  • For whole document formatting set the property + * {@link FormattingContextProperties#CONTEXT_DOCUMENT}. This is equivalent to setting + * {@link FormattingContextProperties#CONTEXT_REGION} with a region spanning the whole + * document.
  • + *
  • For multiple region formatting set the property + * {@link FormattingContextProperties#CONTEXT_REGION}. Note that the content formatter automatically + * aligns the region to a block selected region, and if the region spans multiple partitions, it + * also completes eventual partitions covered only partially by the region.
  • *
- * Depending on the registered formatting strategies, more context information must - * be passed in the formatting context, like e.g. {@link FormattingContextProperties#CONTEXT_PREFERENCES}. - *

- * Note that in context based mode the content formatter is fully reentrant, but not - * thread-safe. + * Depending on the registered formatting strategies, more context information must be passed in the + * formatting context, like e.g. {@link FormattingContextProperties#CONTEXT_PREFERENCES}. *

+ * Note that in context based mode the content formatter is fully reentrant, but not thread-safe. + *

* * @see IFormattingContext * @see FormattingContextProperties diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/link/LinkedModeUI.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/link/LinkedModeUI.java index 062d093..c7483e1 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/link/LinkedModeUI.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/link/LinkedModeUI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -32,6 +32,11 @@ import org.eclipse.core.runtime.Assert; +import org.eclipse.text.undo.DocumentUndoEvent; +import org.eclipse.text.undo.DocumentUndoManagerRegistry; +import org.eclipse.text.undo.IDocumentUndoListener; +import org.eclipse.text.undo.IDocumentUndoManager; + import org.eclipse.jface.internal.text.link.contentassist.ContentAssistant2; import org.eclipse.jface.internal.text.link.contentassist.IProposalListener; import org.eclipse.jface.viewers.IPostSelectionProvider; @@ -277,6 +282,19 @@ public interface IExitPolicy { * should be taken */ ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length); + + /** + * Checks whether the linked mode should be left after receiving the given + * DocumentEvent, especially allowing to control Copy-Paste operations. + * + * @param model the linked mode model + * @param event the document event + * @return valid exit flags or null if no special action should be taken + * @since 3.22 + */ + default ExitFlags doExit(LinkedModeModel model, DocumentEvent event) { + return null; + } } /** @@ -292,7 +310,7 @@ public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, in /** * Listens for shell events and acts upon them. */ - private class Closer implements ShellListener, ITextInputListener { + private class Closer implements ShellListener, ITextInputListener, IDocumentUndoListener { @Override public void shellActivated(ShellEvent e) { @@ -369,6 +387,34 @@ public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { } + @Override + public void documentUndoNotification(DocumentUndoEvent event) { + int type= event.getEventType(); + if ((type & DocumentUndoEvent.ABOUT_TO_UNDO) != 0 || (type & DocumentUndoEvent.ABOUT_TO_REDO) != 0) { + // default behavior: any document change outside a linked position + // causes us to exit + String textRemoved= event.getPreservedText(); + int end= event.getOffset() + (textRemoved != null ? textRemoved.length() : 0); + for (int offset= event.getOffset(); offset <= end; offset++) { + if (!fModel.anyPositionContains(offset)) { + ITextViewer viewer= fCurrentTarget.getViewer(); + if (fFramePosition != null && viewer instanceof IEditingSupportRegistry) { + IEditingSupport[] helpers= ((IEditingSupportRegistry) viewer).getRegisteredSupports(); + for (IEditingSupport helper : helpers) { + if (helper.isOriginator(null, new Region(fFramePosition.getOffset(), fFramePosition.getLength()))) + return; + } + } + + leave(ILinkedModeListener.EXTERNAL_MODIFICATION); + return; + } + } + + // Make sure that any document compound change is done committed + endCompoundChangeIfNeeded(); + } + } } /** @@ -397,6 +443,15 @@ public void documentAboutToBeChanged(DocumentEvent event) { } } + // Apply ExitPolicy to any inserted text if the insertion is made inside a linked region + if (fExitPolicy != null) { + ExitFlags flags= fExitPolicy.doExit(fModel, event); + if (flags != null) { + leave(flags.flags); + return; + } + } + // Make sure that any document change is done inside a compound change beginCompoundChangeIfNeeded(); @@ -955,6 +1010,11 @@ private void connect() { viewer.addTextInputListener(fCloser); viewer.getDocument().addDocumentListener(fDocumentListener); + + IDocumentUndoManager undoManager= DocumentUndoManagerRegistry.getDocumentUndoManager(viewer.getDocument()); + if (undoManager != null) { + undoManager.addDocumentUndoListener(fCloser); + } } /** @@ -1097,6 +1157,11 @@ private void disconnect() { ((IPostSelectionProvider) viewer).removePostSelectionChangedListener(fSelectionListener); + IDocumentUndoManager undoManager= DocumentUndoManagerRegistry.getDocumentUndoManager(viewer.getDocument()); + if (undoManager != null) { + undoManager.removeDocumentUndoListener(fCloser); + } + redraw(); } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java index 8b61857..c7dccfd 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java @@ -113,6 +113,7 @@ public void cancel() { * emptied the dirty region queue. */ public void suspendCallerWhileDirty() { + AbstractReconciler.this.signalWaitForFinish(); boolean isDirty; do { synchronized (fDirtyRegionQueue) { @@ -138,6 +139,9 @@ public void reset() { fIsDirty= true; fReset= true; } + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay); + } } else { @@ -150,6 +154,7 @@ public void reset() { } } + informNotFinished(); reconcilerReset(); } @@ -164,12 +169,7 @@ public void reset() { @Override public void run() { - synchronized (fDirtyRegionQueue) { - try { - fDirtyRegionQueue.wait(fDelay); - } catch (InterruptedException x) { - } - } + delay(); if (fCanceled) return; @@ -178,18 +178,15 @@ public void run() { while (!fCanceled) { - synchronized (fDirtyRegionQueue) { - try { - fDirtyRegionQueue.wait(fDelay); - } catch (InterruptedException x) { - } - } + delay(); if (fCanceled) break; - if (!isDirty()) + if (!isDirty()) { + waitFinish= false; //signalWaitForFinish() was called but nothing todo continue; + } synchronized (this) { if (fReset) { @@ -238,7 +235,7 @@ public void documentChanged(DocumentEvent e) { if (fThread.isActive() || !fThread.isDirty() && fThread.isAlive()) { if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread) throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$ - aboutToBeReconciled(); + aboutToBeReconciledInternal(); } /* @@ -292,7 +289,7 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { fDocument.addDocumentListener(this); if (!fThread.isDirty()) - aboutToBeReconciled(); + aboutToBeReconciledInternal(); startReconciling(); } @@ -306,6 +303,8 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { private Listener fListener; /** The background thread delay. */ private int fDelay= 500; + /** Signal that the the background thread should not delay. */ + volatile boolean waitFinish; /** Are there incremental reconciling strategies? */ private boolean fIsIncrementalReconciler= true; /** The progress monitor used by this reconciler. */ @@ -526,6 +525,56 @@ private void createDirtyRegion(DocumentEvent e) { protected void aboutToBeReconciled() { } + /** + * Hook for subclasses which want to perform some action as soon as the reconciler starts work + * (initial or reconciling) or waiting. + *

+ * Default implementation is to do nothing. Implementors may call + * {@link #signalWaitForFinish()}. + *

+ * + * @since 3.20 + * @see #signalWaitForFinish + */ + protected void aboutToWork() { + } + + /** + * Signal reconciling should finish as soon as possible. + * + * @since 3.20 + * @see #aboutToWork + */ + public void signalWaitForFinish() { + synchronized (fDirtyRegionQueue) { + waitFinish= true; + fDirtyRegionQueue.notifyAll(); // notify AbstractReconciler#delay about waitFinish + } + } + + private void informNotFinished() { + waitFinish= false; + aboutToWork(); + } + + private void aboutToBeReconciledInternal() { + aboutToBeReconciled(); + informNotFinished(); + } + + + private void delay() { + synchronized (fDirtyRegionQueue) { + if (waitFinish) { + return; // do not delay when waiting; + } + try { + fDirtyRegionQueue.wait(fDelay); + } catch (InterruptedException x) { + } + } + } + /** * This method is called on startup of the background activity. It is called only * once during the life time of the reconciler. Clients may reimplement this method. @@ -542,7 +591,7 @@ protected void forceReconciling() { if (fDocument != null) { if (!fThread.isDirty()&& fThread.isAlive()) - aboutToBeReconciled(); + aboutToBeReconciledInternal(); if (fThread.isActive()) fProgressMonitor.setCanceled(true); diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java index e86801b..28bc891 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java @@ -126,13 +126,33 @@ private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText text */ private static void draw(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) { - if (annotation.drawRightToPreviousChar(widgetOffset)) { + if (annotation.isEndOfLine(widgetOffset)) { + drawAfterLine(annotation, gc, textWidget, widgetOffset, length, color); + } else if (annotation.drawRightToPreviousChar(widgetOffset)) { drawAsRightOfPreviousCharacter(annotation, gc, textWidget, widgetOffset, length, color); } else { drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color); } } + private static void drawAfterLine(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) { + if (gc == null) { + return; + } + if (textWidget.getCharCount() == 0) { + annotation.draw(gc, textWidget, widgetOffset, length, color, 0, 0); + } else { + int line= textWidget.getLineAtOffset(widgetOffset); + int lineEndOffset= (line == textWidget.getLineCount() - 1) ? // + textWidget.getCharCount() - 1 : // + textWidget.getOffsetAtLine(line + 1) - 1; + Rectangle bounds= textWidget.getTextBounds(lineEndOffset, lineEndOffset); + int lineEndX= bounds.x + bounds.width + gc.stringExtent(" ").x; //$NON-NLS-1$ + annotation.setLocation(lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line)); + annotation.draw(gc, textWidget, widgetOffset, length, color, lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line)); + } + } + protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) { StyleRange style= null; try { @@ -182,7 +202,7 @@ protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, // END TO REMOVE // Annotation takes place, add GlyphMetrics width to the style - StyleRange newStyle= annotation.updateStyle(style); + StyleRange newStyle= annotation.updateStyle(style, gc.getFontMetrics()); if (newStyle != null) { textWidget.setStyleRange(newStyle); return; @@ -265,7 +285,7 @@ protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annot // END TO REMOVE // Annotation takes place, add GlyphMetrics width to the style - StyleRange newStyle= annotation.updateStyle(style); + StyleRange newStyle= annotation.updateStyle(style, gc.getFontMetrics()); if (newStyle != null) { textWidget.setStyleRange(newStyle); return; diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java index 6682ad7..0282beb 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java @@ -35,6 +35,8 @@ import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.GlyphMetrics; import org.eclipse.swt.widgets.Display; @@ -112,7 +114,7 @@ public void applyTextPresentation(TextPresentation textPresentation) { .forEachRemaining(annotation -> { if (annotation instanceof LineContentAnnotation) { LineContentAnnotation ann= (LineContentAnnotation) annotation; - StyleRange style= ann.updateStyle(null); + StyleRange style= ann.updateStyle(null, fFontMetrics); if (style != null) { if (fViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 projectionViewer= (ITextViewerExtension5) fViewer; @@ -340,6 +342,8 @@ public void mouseUp(MouseEvent e) { */ private final MouseTracker fMouseTracker= new MouseTracker(); + private FontMetrics fFontMetrics; + /** * Install the inlined annotation support for the given viewer. * @@ -364,6 +368,10 @@ public void install(ISourceViewer viewer, AnnotationPainter painter) { text.addMouseListener(fMouseTracker); text.addMouseMoveListener(fMouseTracker); setColor(text.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); + GC gc= new GC(viewer.getTextWidget()); + gc.setFont(viewer.getTextWidget().getFont()); + fFontMetrics= gc.getFontMetrics(); + gc.dispose(); } /** diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java index 0e5df94..fff0731 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java @@ -16,6 +16,7 @@ import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.GlyphMetrics; @@ -110,10 +111,11 @@ boolean contains(int x, int y) { * model position before passing it to a {@link TextPresentation}. * * @param style the current style and null otherwise. + * @param fontMetrics font metrics * @return the style to apply with GlyphMetrics width only if needed. It uses widget position, * not model position. */ - StyleRange updateStyle(StyleRange style) { + StyleRange updateStyle(StyleRange style, FontMetrics fontMetrics) { Position widgetPosition= computeWidgetPosition(); if (widgetPosition == null) { return null; @@ -134,7 +136,7 @@ StyleRange updateStyle(StyleRange style) { GlyphMetrics metrics= style.metrics; if (!isMarkedDeleted()) { if (metrics == null) { - metrics= new GlyphMetrics(0, 0, fullWidth); + metrics= new GlyphMetrics(fontMetrics.getAscent(), fontMetrics.getDescent(), fullWidth); } else { if (metrics.width == fullWidth) { return null; @@ -144,7 +146,7 @@ StyleRange updateStyle(StyleRange style) { * later in StyledText#setStyleRange will compare the same (modified) and won't * realize an update happened. */ - metrics= new GlyphMetrics(0, 0, fullWidth); + metrics= new GlyphMetrics(fontMetrics.getAscent(), fontMetrics.getDescent(), fullWidth); } } else { metrics= null; @@ -158,4 +160,15 @@ boolean drawRightToPreviousChar(int widgetOffset) { getTextWidget().getLineAtOffset(widgetOffset) == getTextWidget().getLineAtOffset(widgetOffset - 1); } + boolean isEndOfLine(int widgetOffset) { + StyledText text= getTextWidget(); + if (text.getCharCount() <= widgetOffset) { // Assuming widgetOffset >= 0 + return true; + } + int line= text.getLineAtOffset(widgetOffset); + int startOfLine= text.getOffsetAtLine(line); + int offsetInLine= widgetOffset - startOfLine; + return offsetInLine >= text.getLine(line).length(); + } + } diff --git a/org.eclipse.search.tests/META-INF/MANIFEST.MF b/org.eclipse.search.tests/META-INF/MANIFEST.MF index b91fb98..f41ba15 100644 --- a/org.eclipse.search.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.search.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search.tests -Bundle-Version: 3.10.0.qualifier +Bundle-Version: 3.10.200.qualifier Bundle-Activator: org.eclipse.search.tests.SearchTestPlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/org.eclipse.search.tests/build.properties b/org.eclipse.search.tests/build.properties index 001154e..45e2741 100644 --- a/org.eclipse.search.tests/build.properties +++ b/org.eclipse.search.tests/build.properties @@ -20,3 +20,9 @@ bin.includes = plugin.properties,\ META-INF/,\ about.html,\ test.xml + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.search.tests.AllSearchTests +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.search.tests/pom.xml b/org.eclipse.search.tests/pom.xml deleted file mode 100644 index 35e4b15..0000000 --- a/org.eclipse.search.tests/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.search - org.eclipse.search.tests - 3.10.0-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.search.tests.AllSearchTests - - - - - org.eclipse.tycho - tycho-surefire-plugin - ${tycho.version} - - true - true - - - - eclipse-plugin - org.eclipse.equinox.event - 0.0.0 - - - - - - - diff --git a/org.eclipse.search.tests/src/org/eclipse/search/core/tests/TestSearchResult.java b/org.eclipse.search.tests/src/org/eclipse/search/core/tests/TestSearchResult.java index 3e88db4..a2e4b11 100644 --- a/org.eclipse.search.tests/src/org/eclipse/search/core/tests/TestSearchResult.java +++ b/org.eclipse.search.tests/src/org/eclipse/search/core/tests/TestSearchResult.java @@ -101,23 +101,6 @@ public void testAddMatchDifferentLength() { assertTrue("matches[1]", matches[1] == match1); } - @Test - public void testAddMatchOrderPreserving() { - ISearchQuery query= new NullQuery(); - AbstractTextSearchResult result= (AbstractTextSearchResult) query.getSearchResult(); - - String object= "object"; //$NON-NLS-1$ - - Match match1= new Match(object, 1, 0); - result.addMatch(match1); - assertEquals(result.getMatchCount(), 1); - Match match2= new Match(object, 1, 0); - result.addMatch(match2); - Match[] matches= result.getMatches(object); - assertTrue("matches[0]", matches[0] == match1); - assertTrue("matches[1]", matches[1] == match2); - } - @Test public void testAddMatches() { ISearchQuery query= new NullQuery(); diff --git a/org.eclipse.search.tests/src/org/eclipse/search/tests/filesearch/FileSearchTests.java b/org.eclipse.search.tests/src/org/eclipse/search/tests/filesearch/FileSearchTests.java index 51e3c09..8439dc2 100644 --- a/org.eclipse.search.tests/src/org/eclipse/search/tests/filesearch/FileSearchTests.java +++ b/org.eclipse.search.tests/src/org/eclipse/search/tests/filesearch/FileSearchTests.java @@ -361,6 +361,45 @@ public void testDerivedFilesSerial() throws Exception { testDerivedFiles(new SerialTestResultCollector()); } + @Test + public void testWildcardQuotes() throws Exception { + assertWildcardReplace("H", "Hallo", "-allo"); + assertWildcardReplace("a", "Hallo", "H-llo"); + assertWildcardReplace("al", "Hallo", "H-lo"); + assertWildcardReplace("a*", "Hallo", "H-"); + assertWildcardReplace("a?", "Hallo", "H-lo"); + assertWildcardReplace("?", "Hallo", "-----"); + assertWildcardReplace("{", "Ha({o", "Ha(-o"); + assertWildcardReplace("(", "Ha({o", "Ha-{o"); + assertWildcardReplace("\\", "Ha\\\\o", "Ha--o"); + assertWildcardReplace("\\\\", "Ha\\\\o", "Ha--o"); + assertWildcardReplace("\\*", "Hall*", "Hall-"); + assertWildcardReplace("\\?", "Ha??o?", "Ha--o-"); + assertWildcardReplace("Du?und?ich", "Du und ich nicht", "- nicht"); + assertWildcardReplace("Du*ich", "Du und ich nicht", "-t"); + assertWildcardReplace("und*ich", "Du und ich nicht", "Du -t"); + assertWildcardReplace("*ich", "Du und ich nicht", "-t"); + + assertWildcardReplace("*", "Hallo", "--"); + // XXX i expect it to be "-" but ".*" indeed matches chars 0-5 and 5-5 + // it would need ".+" to not match the empty string at the end + } + + private void assertWildcardReplace(String pattern, String in, String expected) { + String regex= asRegEx(true, pattern); + try { + String replaced= in.replaceAll(regex, "-"); + assertEquals(expected, replaced); + } catch (Exception e) { + throw new RuntimeException("Error with pattern:" + pattern + " regex=" + regex, e); + } + } + + String asRegEx(boolean wildcards, String pattern) { + StringBuilder b= new StringBuilder(); + org.eclipse.search.internal.core.text.PatternConstructor.appendAsRegEx(wildcards, pattern, b); + return b.toString(); + } @Test public void testDerivedFilesParallel() throws Exception { testDerivedFiles(new ParallelTestResultCollector()); diff --git a/org.eclipse.search/.settings/.api_filters b/org.eclipse.search/.settings/.api_filters index 89f4b44..ce20ce6 100644 --- a/org.eclipse.search/.settings/.api_filters +++ b/org.eclipse.search/.settings/.api_filters @@ -1,13 +1,5 @@ - - - - - - - - @@ -16,12 +8,4 @@ - - - - - - - - diff --git a/org.eclipse.search/META-INF/MANIFEST.MF b/org.eclipse.search/META-INF/MANIFEST.MF index 37f8476..0e5a24e 100644 --- a/org.eclipse.search/META-INF/MANIFEST.MF +++ b/org.eclipse.search/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search; singleton:=true -Bundle-Version: 3.13.300.qualifier +Bundle-Version: 3.14.300.qualifier Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/org.eclipse.search/new search/org/eclipse/search/core/text/TextSearchRequestor.java b/org.eclipse.search/new search/org/eclipse/search/core/text/TextSearchRequestor.java index eca7b53..a9c49e8 100644 --- a/org.eclipse.search/new search/org/eclipse/search/core/text/TextSearchRequestor.java +++ b/org.eclipse.search/new search/org/eclipse/search/core/text/TextSearchRequestor.java @@ -100,6 +100,21 @@ public boolean acceptFile(IFile file) throws CoreException { return true; } + /** + * Notification that the matches of the given file should be flushed. The + * default behaviour is to ignore this notification. Implementors can use + * this notification to update the progress after a file was searched. + * Otherwise the progress may not be visible until all files have been + * searched. + * + * @param file + * the file that was just processed. + * @since 3.14 + */ + public void flushMatches(IFile file) { + // do nothing + } + /** * Notification sent that a file might contain binary context. * It is the choice of the search engine to report binary files and it is the heuristic of the search engine to decide diff --git a/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchResult.java b/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchResult.java index c013321..c7200c9 100644 --- a/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchResult.java +++ b/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchResult.java @@ -14,12 +14,16 @@ package org.eclipse.search.ui.text; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; +import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.eclipse.search.ui.ISearchResult; import org.eclipse.search.ui.ISearchResultListener; @@ -36,7 +40,7 @@ public abstract class AbstractTextSearchResult implements ISearchResult { private static final Match[] EMPTY_ARRAY= new Match[0]; - private final Map> fElementsToMatches; + private final ConcurrentMap> fElementsToMatches; private final List fListeners; private final MatchEvent fMatchEvent; @@ -46,7 +50,7 @@ public abstract class AbstractTextSearchResult implements ISearchResult { * Constructs a new AbstractTextSearchResult */ protected AbstractTextSearchResult() { - fElementsToMatches= new HashMap<>(); + fElementsToMatches= new ConcurrentHashMap<>(); fListeners= new ArrayList<>(); fMatchEvent= new MatchEvent(this); @@ -55,19 +59,53 @@ protected AbstractTextSearchResult() { /** * Returns an array with all matches reported against the given element. - * Note that all matches of the given element are returned. The filter state of the matches is not relevant. + * Note that all matches of the given element are returned. The filter state + * of the matches is not relevant. The matches are reported sorted per + * offset and length. The order may be important for example stepping + * between matches (see bug 58417). For calculating just a match count the + * order is not needed and the faster {@link #getMatchSet(Object)} should be used instead. + * The order of reported matches found (with equal offset and length) is not preserved + * (Does not make sense during parallel search). * - * @param element the element to report matches for + * @param element + * the element to report matches for * @return all matches reported for this element * @see Match#getElement() */ public Match[] getMatches(Object element) { - synchronized (fElementsToMatches) { - List matches= fElementsToMatches.get(element); - if (matches != null) - return matches.toArray(new Match[matches.size()]); + if (element == null) { return EMPTY_ARRAY; } + Set matches = fElementsToMatches.get(element); + if (matches != null) { + Match[] sortingCopy = matches.toArray(new Match[matches.size()]); + Arrays.sort(sortingCopy, AbstractTextSearchResult::compare); + return sortingCopy; + } + return EMPTY_ARRAY; + } + + /** + * Returns an Enumeration of all matches reported against the given element. + * Note that all matches of the given element are returned. The filter state + * of the matches is not relevant. Like {@link #getMatches(Object)} but + * unordered result. + * + * @param element + * the element to report matches for + * @return all matches reported for this element + * @since 3.14 + * @see AbstractTextSearchResult#getMatches(Object) + */ + public Enumeration getMatchSet(Object element) { + if (element == null) { + return Collections.emptyEnumeration(); + } + Set matches = fElementsToMatches.get(element); + if (matches != null) { + return Collections.enumeration(matches); + } + return Collections.emptyEnumeration(); } /** @@ -80,11 +118,7 @@ public Match[] getMatches(Object element) { * @param match the match to add */ public void addMatch(Match match) { - boolean hasAdded= false; - synchronized (fElementsToMatches) { - hasAdded= doAddMatch(match); - } - if (hasAdded) + if (didAddMatch(match)) fireChange(getSearchResultEvent(match, MatchEvent.ADDED)); } @@ -98,10 +132,9 @@ public void addMatch(Match match) { */ public void addMatches(Match[] matches) { Collection reallyAdded= new ArrayList<>(); - synchronized (fElementsToMatches) { - for (Match match : matches) { - if (doAddMatch(match)) - reallyAdded.add(match); + for (Match match : matches) { + if (didAddMatch(match)) { + reallyAdded.add(match); } } if (!reallyAdded.isEmpty()) @@ -121,46 +154,12 @@ private MatchEvent getSearchResultEvent(Collection matches, int eventKind return fMatchEvent; } - private boolean doAddMatch(Match match) { + private boolean didAddMatch(Match match) { updateFilterState(match); - - List matches= fElementsToMatches.get(match.getElement()); - if (matches == null) { - matches= new ArrayList<>(); - fElementsToMatches.put(match.getElement(), matches); - matches.add(match); - return true; - } - if (!matches.contains(match)) { - insertSorted(matches, match); - return true; - } - return false; + return fElementsToMatches.computeIfAbsent(match.getElement(), k -> ConcurrentHashMap.newKeySet()).add(match); } - private static void insertSorted(List matches, Match match) { - int insertIndex= getInsertIndex(matches, match); - matches.add(insertIndex, match); - } - - private static int getInsertIndex(List matches, Match match) { - int count= matches.size(); - int min = 0; - int max = count - 1; - while (min <= max) { - int mid = (min + max) / 2; - Match data = matches.get(mid); - int compare = compare(match, data); - if (compare > 0) - max = mid - 1; - else - min = mid + 1; - } - return min; - } - - - private static int compare(Match match1, Match match2) { + private static int compare(Match match2, Match match1) { int diff= match2.getOffset()-match1.getOffset(); if (diff != 0) return diff; @@ -174,9 +173,7 @@ private static int compare(Match match1, Match match2) { *

*/ public void removeAll() { - synchronized (fElementsToMatches) { - doRemoveAll(); - } + doRemoveAll(); fireChange(new RemoveAllEvent(this)); } private void doRemoveAll() { @@ -192,11 +189,7 @@ private void doRemoveAll() { * @param match the match to remove */ public void removeMatch(Match match) { - boolean existed= false; - synchronized (fElementsToMatches) { - existed= doRemoveMatch(match); - } - if (existed) + if (didRemoveMatch(match)) fireChange(getSearchResultEvent(match, MatchEvent.REMOVED)); } @@ -211,26 +204,25 @@ public void removeMatch(Match match) { */ public void removeMatches(Match[] matches) { Collection existing= new ArrayList<>(); - synchronized (fElementsToMatches) { - for (Match match : matches) { - if (doRemoveMatch(match)) - existing.add(match); // no duplicate matches at this point - } + for (Match match : matches) { + if (didRemoveMatch(match)) + existing.add(match); // no duplicate matches at this point } if (!existing.isEmpty()) fireChange(getSearchResultEvent(existing, MatchEvent.REMOVED)); } - private boolean doRemoveMatch(Match match) { - boolean existed= false; - List matches= fElementsToMatches.get(match.getElement()); - if (matches != null) { - existed= matches.remove(match); - if (matches.isEmpty()) - fElementsToMatches.remove(match.getElement()); - } - return existed; + private boolean didRemoveMatch(Match match) { + boolean[] existed = new boolean[1]; + fElementsToMatches.computeIfPresent(match.getElement(), (f, matches) -> { + existed[0] = matches.remove(match); + if (matches.isEmpty()) { + return null; // remove + } + return matches; + }); + return existed[0]; } @Override @@ -309,12 +301,9 @@ private boolean updateFilterState(Match match) { * @return total number of matches */ public int getMatchCount() { - int count= 0; - synchronized (fElementsToMatches) { - for (List element : fElementsToMatches.values()) { - if (element != null) - count+= element.size(); - } + int count = 0; + for (Set element : fElementsToMatches.values()) { + count += element.size(); } return count; } @@ -328,7 +317,10 @@ public int getMatchCount() { * @return the number of matches reported against the element */ public int getMatchCount(Object element) { - List matches= fElementsToMatches.get(element); + if (element == null) { + return 0; + } + Set matches = fElementsToMatches.get(element); if (matches != null) return matches.size(); return 0; @@ -342,9 +334,7 @@ public int getMatchCount(Object element) { * @return the set of elements in this search result */ public Object[] getElements() { - synchronized (fElementsToMatches) { - return fElementsToMatches.keySet().toArray(); - } + return fElementsToMatches.keySet().toArray(); } /** diff --git a/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java b/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java index 65ba5c1..e4dca28 100644 --- a/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java +++ b/org.eclipse.search/new search/org/eclipse/search/ui/text/AbstractTextSearchViewPage.java @@ -15,9 +15,12 @@ package org.eclipse.search.ui.text; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.LinkedBlockingDeque; import org.osgi.framework.FrameworkUtil; @@ -226,8 +229,8 @@ public void selectionChanged(SelectionChangedEvent event) { private PageBook fPagebook; private boolean fIsBusyShown; private ISearchResultViewPart fViewPart; - private Set fBatchedUpdates; - private boolean fBatchedClearAll; + private final LinkedBlockingDeque fBatchedUpdates = new LinkedBlockingDeque<>(); + private volatile boolean fBatchedClearAll; private ISearchResultListener fListener; private IQueryListener fQueryListener; @@ -299,8 +302,6 @@ protected AbstractTextSearchViewPage(int supportedLayouts) { fSelectAllAction= new SelectAllAction(); createLayoutActions(); - fBatchedUpdates = new HashSet<>(); - fBatchedClearAll= false; fListener = this::handleSearchResultChanged; fFilterActions= null; @@ -427,21 +428,30 @@ protected void showMatch(Match match, int currentOffset, int currentLength, bool } /** - * Opens an editor on the given file resource and tries to select the given offset and length. - *

- * If the page already has an editor open on the target object then that editor is brought to - * front; otherwise, a new editor is opened. If activate == true the editor will be - * activated. + * Opens an editor on the given file resource and tries to select the given + * offset and length. *

+ * If the page already has an editor open on the target object then that + * editor is brought to front; otherwise, a new editor is opened. If + * activate == true the editor will be activated. + *

* - * @param page the workbench page in which the editor will be opened - * @param file the file to open - * @param offset the offset to select in the editor - * @param length the length to select in the editor - * @param activate if true the editor will be activated - * @return an open editor or null if an external editor was opened - * @throws PartInitException if the editor could not be initialized - * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean) + * @param page + * the workbench page in which the editor will be opened + * @param file + * the file to open + * @param offset + * the offset to select in the editor + * @param length + * the length to select in the editor + * @param activate + * if true the editor will be activated + * @return an open editor or null if an external editor was + * opened + * @throws PartInitException + * if the editor could not be initialized + * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, + * boolean) * @since 3.6 */ protected final IEditorPart openAndSelect(IWorkbenchPage page, IFile file, int offset, int length, boolean activate) throws PartInitException { @@ -451,17 +461,23 @@ protected final IEditorPart openAndSelect(IWorkbenchPage page, IFile file, int o /** * Opens an editor on the given file resource. *

- * If the page already has an editor open on the target object then that editor is brought to - * front; otherwise, a new editor is opened. If activate == true the editor will be - * activated. - *

+ * If the page already has an editor open on the target object then that + * editor is brought to front; otherwise, a new editor is opened. If + * activate == true the editor will be activated. + *

* - * @param page the workbench page in which the editor will be opened - * @param file the file to open - * @param activate if true the editor will be activated - * @return an open editor or null if an external editor was opened - * @throws PartInitException if the editor could not be initialized - * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean) + * @param page + * the workbench page in which the editor will be opened + * @param file + * the file to open + * @param activate + * if true the editor will be activated + * @return an open editor or null if an external editor was + * opened + * @throws PartInitException + * if the editor could not be initialized + * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, + * boolean) * @since 3.6 */ protected final IEditorPart open(IWorkbenchPage page, IFile file, boolean activate) throws PartInitException { @@ -1231,24 +1247,30 @@ protected void evaluateChangedElements(Match[] matches, Set changedEleme } } - private synchronized void postUpdate(Match[] matches) { - evaluateChangedElements(matches, fBatchedUpdates); - scheduleUIUpdate(); + private void postUpdate(Match[] matches) { + HashSet collect = new HashSet<>(); + // for compatibility we do not pass the "fBatchedUpdates" directly: + evaluateChangedElements(matches, collect); + // nulls are forbidden in concurrent datastructures: + collect.removeIf(Objects::isNull); + fBatchedUpdates.addAll(collect); + scheduleUIUpdate(); // still synchronized } - private synchronized void runBatchedUpdates() { - elementsChanged(fBatchedUpdates.toArray()); - fBatchedUpdates.clear(); + private void runBatchedUpdates() { + Collection drain = new ArrayList<>(); + fBatchedUpdates.drainTo(drain); + elementsChanged(drain.toArray()); updateBusyLabel(); } - private synchronized void postClear() { + private void postClear() { fBatchedClearAll= true; fBatchedUpdates.clear(); - scheduleUIUpdate(); + scheduleUIUpdate(); // still synchronized } - private synchronized boolean hasMoreUpdates() { + private boolean hasMoreUpdates() { return fBatchedClearAll || !fBatchedUpdates.isEmpty(); } diff --git a/org.eclipse.search/new search/org/eclipse/search/ui/text/FileTextSearchScope.java b/org.eclipse.search/new search/org/eclipse/search/ui/text/FileTextSearchScope.java index 94affc8..afdaed4 100644 --- a/org.eclipse.search/new search/org/eclipse/search/ui/text/FileTextSearchScope.java +++ b/org.eclipse.search/new search/org/eclipse/search/ui/text/FileTextSearchScope.java @@ -127,8 +127,8 @@ public static FileTextSearchScope newSearchScope(IWorkingSet[] workingSets, Stri private final String fDescription; private final IResource[] fRootElements; private final String[] fFileNamePatterns; - private final Matcher fPositiveFileNameMatcher; - private final Matcher fNegativeFileNameMatcher; + private final ThreadLocal fPositiveFileNameMatcher; + private final ThreadLocal fNegativeFileNameMatcher; private boolean fVisitDerived; private IWorkingSet[] fWorkingSets; @@ -139,8 +139,8 @@ private FileTextSearchScope(String description, IResource[] resources, IWorkingS fFileNamePatterns= fileNamePatterns; fVisitDerived= visitDerived; fWorkingSets= workingSets; - fPositiveFileNameMatcher= createMatcher(fileNamePatterns, false); - fNegativeFileNameMatcher= createMatcher(fileNamePatterns, true); + fPositiveFileNameMatcher = ThreadLocal.withInitial(() -> createMatcher(fileNamePatterns, false)); + fNegativeFileNameMatcher = ThreadLocal.withInitial(() -> createMatcher(fileNamePatterns, true)); } /** @@ -223,10 +223,12 @@ public boolean contains(IResourceProxy proxy) { } private boolean matchesFileName(String fileName) { - if (fPositiveFileNameMatcher != null && !fPositiveFileNameMatcher.reset(fileName).matches()) { + Matcher positiveFileNameMatcher = fPositiveFileNameMatcher.get(); + if (positiveFileNameMatcher != null && !positiveFileNameMatcher.reset(fileName).matches()) { return false; } - if (fNegativeFileNameMatcher != null && fNegativeFileNameMatcher.reset(fileName).matches()) { + Matcher negativeFileNameMatcher = fNegativeFileNameMatcher.get(); + if (negativeFileNameMatcher != null && negativeFileNameMatcher.reset(fileName).matches()) { return false; } return true; diff --git a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/text/MarkerHighlighter.java b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/text/MarkerHighlighter.java index 939c9f3..a69b957 100644 --- a/org.eclipse.search/new search/org/eclipse/search2/internal/ui/text/MarkerHighlighter.java +++ b/org.eclipse.search/new search/org/eclipse/search2/internal/ui/text/MarkerHighlighter.java @@ -17,12 +17,17 @@ import java.util.Map; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; +import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.filebuffers.IFileBuffer; @@ -35,19 +40,46 @@ import org.eclipse.search2.internal.ui.InternalSearchUI; public class MarkerHighlighter extends Highlighter { - private IFile fFile; - private Map fMatchesToAnnotations; + private final IFile fFile; + private final Map fMatchesToAnnotations; + private final Object addHighlightsJobFamily; + private volatile boolean fDisposed; + private WorkspaceJob fRemoveAllJob; + private WorkspaceJob fContentReplacedJob; public MarkerHighlighter(IFile file) { fFile= file; fMatchesToAnnotations= new HashMap<>(); + fDisposed = false; + addHighlightsJobFamily = new Object(); } @Override public void addHighlights(final Match[] matches) { + WorkspaceJob addHighlightsJob = new MarkerHighlighterWorkspaceJob("Adding highlights", fFile) { //$NON-NLS-1$ + @Override + void runOperation(IProgressMonitor monitor) { + addHighlightsInternal(matches, monitor); + } + + @Override + public boolean belongsTo(Object family) { + return family == addHighlightsJobFamily || super.belongsTo(family); + } + }; + addHighlightsJob.schedule(); + } + + private void addHighlightsInternal(final Match[] matches, IProgressMonitor monitor) { try { - SearchPlugin.getWorkspace().run((IWorkspaceRunnable) monitor -> { + SearchPlugin.getWorkspace().run((IWorkspaceRunnable) submonitor -> { + if (fDisposed) { + return; + } for (Match match : matches) { + if (monitor.isCanceled() || submonitor.isCanceled()) { + return; + } IMarker marker; marker = createMarker(match); if (marker != null) @@ -86,6 +118,18 @@ private IMarker createMarker(Match match) throws CoreException { @Override public void removeHighlights(Match[] matches) { + WorkspaceJob removeHighlightsJob = new MarkerHighlighterWorkspaceJob("Removing highlights", fFile) { //$NON-NLS-1$ + @Override + void runOperation(IProgressMonitor monitor) { + removeHighlightsInternal(matches); + } + }; + // don't cancel the job, as we want to previous highlights removal to go + // through + removeHighlightsJob.schedule(); + } + + private void removeHighlightsInternal(Match[] matches) { for (Match match : matches) { IMarker marker= fMatchesToAnnotations.remove(match); if (marker != null) { @@ -101,6 +145,20 @@ public void removeHighlights(Match[] matches) { @Override public void removeAll() { + cancelAddingHighlights(); + if (fRemoveAllJob == null) { + fRemoveAllJob = new MarkerHighlighterWorkspaceJob("Removing all search highlights", fFile) { //$NON-NLS-1$ + @Override + void runOperation(IProgressMonitor monitor) { + removeAllInternal(); + } + }; + } + fRemoveAllJob.cancel(); + fRemoveAllJob.schedule(); + } + + private void removeAllInternal() { try { fFile.deleteMarkers(NewSearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE); fFile.deleteMarkers(SearchPlugin.FILTERED_SEARCH_MARKER, true, IResource.DEPTH_INFINITE); @@ -115,9 +173,64 @@ public void removeAll() { protected void handleContentReplaced(IFileBuffer buffer) { if (!buffer.getLocation().equals(fFile.getFullPath())) return; + if (fContentReplacedJob == null) { + fContentReplacedJob = new MarkerHighlighterWorkspaceJob("Updating search highlights", fFile) { //$NON-NLS-1$ + @Override + void runOperation(IProgressMonitor monitor) { + handleContentReplacedInternal(monitor); + } + }; + } + fContentReplacedJob.cancel(); + fContentReplacedJob.schedule(); + } + + private void handleContentReplacedInternal(IProgressMonitor monitor) { + if (fDisposed) { + return; + } Match[] matches= new Match[fMatchesToAnnotations.size()]; fMatchesToAnnotations.keySet().toArray(matches); - removeAll(); - addHighlights(matches); + removeAllInternal(); + addHighlightsInternal(matches, monitor); + } + + @Override + public void dispose() { + fDisposed = true; + cancelAddingHighlights(); + super.dispose(); + } + + private void cancelAddingHighlights() { + if (fContentReplacedJob != null) { + fContentReplacedJob.cancel(); + } + Job.getJobManager().cancel(addHighlightsJobFamily); + } + + static abstract class MarkerHighlighterWorkspaceJob extends WorkspaceJob { + + public MarkerHighlighterWorkspaceJob(String jobName, IFile file) { + super(jobName); + setRule(file); + setSystem(true); + } + + @Override + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + runOperation(monitor); + return Status.OK_STATUS; + } + + @Override + public boolean belongsTo(Object family) { + return family == MarkerHighlighter.class; + } + + abstract void runOperation(IProgressMonitor monitor) throws CoreException; } } diff --git a/org.eclipse.search/plugin.xml b/org.eclipse.search/plugin.xml index fae0d43..14a0121 100644 --- a/org.eclipse.search/plugin.xml +++ b/org.eclipse.search/plugin.xml @@ -262,13 +262,6 @@ - - - - - - - - diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/DocumentCharSequence.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/DocumentCharSequence.java index b2389c6..791d1ea 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/DocumentCharSequence.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/DocumentCharSequence.java @@ -53,4 +53,9 @@ public CharSequence subSequence(int start, int end) { } } + /** @see CharSequence#toString **/ + @Override + public String toString() { + return fDocument.get(); + } } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/FileCharSequenceProvider.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/FileCharSequenceProvider.java index 1a2ef39..e879932 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/FileCharSequenceProvider.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/FileCharSequenceProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2010 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -30,12 +30,28 @@ */ public class FileCharSequenceProvider { - private static int NUMBER_OF_BUFFERS= 3; - public static int BUFFER_SIZE= 2 << 18; // public for testing + /** + * Just any number such that the most source files will fit in. And not too + * big to avoid out of memory. + **/ + private static final int MAX_BUFFER_LENGTH = 999_999; // max 2MB. + + private static int NUMBER_OF_BUFFERS = 3; + public static int BUFFER_SIZE = 2 << 18; // public for testing private FileCharSequence fReused= null; public CharSequence newCharSequence(IFile file) throws CoreException, IOException { + String string = toShortString(file); + if (string != null) { + return string; + } + FileCharSequence charSequence = getCharSequence(file); + // File too large for String + return charSequence; + } + + private FileCharSequence getCharSequence(IFile file) throws CoreException, IOException { if (fReused == null) { return new FileCharSequence(file); } @@ -68,16 +84,6 @@ public static class FileCharSequenceException extends RuntimeException { /* package */ FileCharSequenceException(CoreException e) { super(e); } - - public void throwWrappedException() throws CoreException, IOException { - Throwable wrapped= getCause(); - if (wrapped instanceof CoreException) { - throw (CoreException) wrapped; - } else if (wrapped instanceof IOException) { - throw (IOException) wrapped; - } - // not possible - } } @@ -464,7 +470,7 @@ public void close() throws IOException { @Override public String toString() { - int len= fLength != null ? fLength.intValue() : 4000; + int len = fLength != null ? fLength.intValue() : 4000; StringBuilder res= new StringBuilder(len); try { Buffer buffer= getBuffer(0); @@ -481,4 +487,38 @@ public String toString() { } } + /* + * Try to get a content as String. Avoids to scanning whole InputStream to + * get length + */ + private static String toShortString(IFile file) { + try (InputStream contents = file.getContents()) { + byte[] content = contents.readNBytes(MAX_BUFFER_LENGTH); + int length = content.length; + if (length >= MAX_BUFFER_LENGTH) { + return null; + } + String charset = file.getCharset(); + int offset = 0; + if (StandardCharsets.UTF_8.name().equals(charset)) { + if (startsWith(content, IContentDescription.BOM_UTF_8)) { + offset = IContentDescription.BOM_UTF_8.length; + } + } + return new String(content, offset, length - offset, charset); + } catch (Exception e) { + return null; + } + } + + private static boolean startsWith(byte[] a, byte[] start) { + if (a.length < start.length) { + return false; + } + for (int i = 0; i < start.length; i++) { + if (a[i] != start[i]) + return false; + } + return true; + } } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/PatternConstructor.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/PatternConstructor.java index b785782..287fb18 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/PatternConstructor.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/PatternConstructor.java @@ -66,7 +66,7 @@ public static Pattern createPattern(String pattern, boolean isRegex, boolean isS if (isWholeWord && len > 0 && isWordChar(pattern.charAt(len - 1))) { buffer.append("\\b"); //$NON-NLS-1$ } - pattern= buffer.toString(); + pattern= buffer.toString(); } int regexOptions= Pattern.MULTILINE; @@ -183,6 +183,12 @@ public static Pattern createPattern(String[] patterns, boolean isCaseSensitive) public static StringBuilder appendAsRegEx(boolean isStringMatcher, String pattern, StringBuilder buffer) { + if (!isStringMatcher) { + buffer.append(Pattern.quote(pattern)); + return buffer; + } + // isStringMatcher: '*' and '?' wildcards and '\' as escape + StringBuilder quoted = new StringBuilder(); boolean isEscaped= false; for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); @@ -190,59 +196,46 @@ public static StringBuilder appendAsRegEx(boolean isStringMatcher, String patter // the backslash case '\\': // the backslash is escape char in string matcher - if (isStringMatcher && !isEscaped) { + if (!isEscaped) { isEscaped= true; } else { - buffer.append("\\\\"); //$NON-NLS-1$ - isEscaped= false; - } - break; - // characters that need to be escaped in the regex. - case '(': - case ')': - case '{': - case '}': - case '.': - case '[': - case ']': - case '$': - case '^': - case '+': - case '|': - if (isEscaped) { - buffer.append("\\\\"); //$NON-NLS-1$ + quoted.append(c); isEscaped= false; } - buffer.append('\\'); - buffer.append(c); break; case '?': - if (isStringMatcher && !isEscaped) { + if (!isEscaped) { + if (quoted.length() > 0) { // flush quote + buffer.append(Pattern.quote(quoted.toString())); + quoted = new StringBuilder(); + } buffer.append('.'); } else { - buffer.append('\\'); - buffer.append(c); + quoted.append(c); isEscaped= false; } break; case '*': - if (isStringMatcher && !isEscaped) { + if (!isEscaped) { + if (quoted.length() > 0) { // flush quote + buffer.append(Pattern.quote(quoted.toString())); + quoted = new StringBuilder(); + } buffer.append(".*"); //$NON-NLS-1$ } else { - buffer.append('\\'); - buffer.append(c); + quoted.append(c); isEscaped= false; } break; default: if (isEscaped) { - buffer.append("\\\\"); //$NON-NLS-1$ + quoted.append("\\"); //$NON-NLS-1$ isEscaped= false; } - buffer.append(c); + quoted.append(c); break; } } @@ -250,6 +243,10 @@ public static StringBuilder appendAsRegEx(boolean isStringMatcher, String patter buffer.append("\\\\"); //$NON-NLS-1$ isEscaped= false; } + if (quoted.length() > 0) { // flush quote + buffer.append(Pattern.quote(quoted.toString())); + quoted = new StringBuilder(); + } return buffer; } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java index 492de0e..97677d0 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -20,11 +20,13 @@ import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,8 +84,12 @@ public class TextSearchVisitor { public static final boolean TRACING= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.search/perf")); //$NON-NLS-1$ //$NON-NLS-2$ private static final int NUMBER_OF_LOGICAL_THREADS= Runtime.getRuntime().availableProcessors(); - private static final int FILES_PER_JOB= 50; - private static final int MAX_JOBS_COUNT= 100; + + /** + * Queue of files to be searched. IFile pointing to the same local file are + * grouped together + **/ + private final Queue> fileBatches; public static class ReusableMatchAccess extends TextSearchMatchAccess { @@ -149,32 +155,22 @@ protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFaile * A job to find matches in a set of files. */ private class TextSearchJob extends Job { - private final IFile[] fFiles; - private final int fBegin; - private final int fEnd; private final Map fDocumentsInEditors; private FileCharSequenceProvider fileCharSequenceProvider; - - private IPath previousLocationFromFile; - // occurences need to be passed to FileSearchResultCollector with growing offset - private List occurencesForPreviousLocation; - private CharSequence charsequenceForPreviousLocation; - + private final int jobCount; /** - * Searches for matches in a set of files. + * Searches for matches in the files. * - * @param files an array of IFiles, a portion of which is to be processed - * @param begin the first element in the file array to process - * @param end one past the last element in the array to process - * @param documentsInEditors a map from IFile to IDocument for all open, dirty editors + * @param documentsInEditors + * a map from IFile to IDocument for all open, dirty editors + * @param jobCount + * number of Jobs */ - public TextSearchJob(IFile[] files, int begin, int end, Map documentsInEditors) { - super(files[begin].getName()); + public TextSearchJob(Map documentsInEditors, int jobCount) { + super("File Search Worker"); //$NON-NLS-1$ + this.jobCount = jobCount; setSystem(true); - fFiles= files; - fBegin= begin; - fEnd= end; fDocumentsInEditors= documentsInEditors; } @@ -182,75 +178,74 @@ public TextSearchJob(IFile[] files, int begin, int end, Map do protected IStatus run(IProgressMonitor inner) { MultiStatus multiStatus= new MultiStatus(NewSearchUI.PLUGIN_ID, IStatus.OK, SearchMessages.TextSearchEngine_statusMessage, null); - SubMonitor subMonitor= SubMonitor.convert(inner, fEnd - fBegin); + SubMonitor subMonitor = SubMonitor.convert(inner, fileBatches.size() / jobCount); // approximate this.fileCharSequenceProvider= new FileCharSequenceProvider(); - for (int i= fBegin; i < fEnd && !fFatalError; i++) { - IStatus status= processFile(fFiles[i], subMonitor.split(1)); + List sameFiles; + while (((sameFiles = fileBatches.poll()) != null) && !fFatalError && !fProgressMonitor.isCanceled()) { + IStatus status = processFile(sameFiles, subMonitor.split(1)); // Only accumulate interesting status if (!status.isOK()) multiStatus.add(status); // Group cancellation is propagated to this job's monitor. // Stop processing and return the status for the completed jobs. } - if (charsequenceForPreviousLocation != null) { - try { - fileCharSequenceProvider.releaseCharSequence(charsequenceForPreviousLocation); - } catch (IOException e) { - SearchPlugin.log(e); - } finally { - charsequenceForPreviousLocation= null; - } - } fileCharSequenceProvider= null; - previousLocationFromFile= null; - occurencesForPreviousLocation= null; + synchronized (fLock) { + fLock.notify(); + } return multiStatus; } - public IStatus processFile(IFile file, IProgressMonitor monitor) { + public IStatus processFile(List sameFiles, IProgressMonitor monitor) { // A natural cleanup after the change to use JobGroups is accepted would be to move these // methods to the TextSearchJob class. Matcher matcher= fSearchPattern.pattern().isEmpty() ? null : fSearchPattern.matcher(""); //$NON-NLS-1$ - + IFile file = sameFiles.remove(0); + monitor.setTaskName(file.getFullPath().toString()); try { if (!fCollector.acceptFile(file) || matcher == null) { return Status.OK_STATUS; } + List occurences; + CharSequence charsequence; + IDocument document= getOpenDocument(file, getDocumentsInEditors()); if (document != null) { - DocumentCharSequence documentCharSequence= new DocumentCharSequence(document); + charsequence = new DocumentCharSequence(document); // assume all documents are non-binary - locateMatches(file, documentCharSequence, matcher, monitor); - } else if (previousLocationFromFile != null && previousLocationFromFile.equals(file.getLocation()) && !occurencesForPreviousLocation.isEmpty()) { + occurences = locateMatches(file, charsequence, matcher, monitor); + } else { + try { + charsequence = fileCharSequenceProvider.newCharSequence(file); + if (hasBinaryContent(charsequence, file) && !fCollector.reportBinaryFile(file)) { + return Status.OK_STATUS; + } + occurences = locateMatches(file, charsequence, matcher, monitor); + } catch (FileCharSequenceProvider.FileCharSequenceException e) { + throw (RuntimeException) e.getCause(); + } + } + fCollector.flushMatches(file); + + for (IFile duplicateFiles : sameFiles) { // reuse previous result ReusableMatchAccess matchAccess= new ReusableMatchAccess(); - for (TextSearchMatchAccess occurence : occurencesForPreviousLocation) { - matchAccess.initialize(file, occurence.getMatchOffset(), occurence.getMatchLength(), charsequenceForPreviousLocation); + for (TextSearchMatchAccess occurence : occurences) { + matchAccess.initialize(duplicateFiles, occurence.getMatchOffset(), occurence.getMatchLength(), + charsequence); boolean goOn= fCollector.acceptPatternMatch(matchAccess); if (!goOn) { break; } } - } else { - if (charsequenceForPreviousLocation != null) { - try { - fileCharSequenceProvider.releaseCharSequence(charsequenceForPreviousLocation); - charsequenceForPreviousLocation= null; - } catch (IOException e) { - SearchPlugin.log(e); - } - } + fCollector.flushMatches(duplicateFiles); + } + if (document == null) { try { - charsequenceForPreviousLocation= fileCharSequenceProvider.newCharSequence(file); - if (hasBinaryContent(charsequenceForPreviousLocation, file) && !fCollector.reportBinaryFile(file)) { - occurencesForPreviousLocation= Collections.emptyList(); - return Status.OK_STATUS; - } - occurencesForPreviousLocation= locateMatches(file, charsequenceForPreviousLocation, matcher, monitor); - previousLocationFromFile= file.getLocation(); - } catch (FileCharSequenceProvider.FileCharSequenceException e) { - e.throwWrappedException(); + fileCharSequenceProvider.releaseCharSequence(charsequence); + } catch (IOException e) { + SearchPlugin.log(e); } } } catch (UnsupportedCharsetException e) { @@ -282,6 +277,10 @@ public IStatus processFile(IFile file, IProgressMonitor monitor) { fNumberOfScannedFiles++; } } + if (monitor.isCanceled()) { + fFatalError = true; + return Status.CANCEL_STATUS; + } return Status.OK_STATUS; } @@ -295,17 +294,16 @@ public Map getDocumentsInEditors() { private final TextSearchRequestor fCollector; private final Pattern fSearchPattern; - private IProgressMonitor fProgressMonitor; + private volatile IProgressMonitor fProgressMonitor; - private int fNumberOfFilesToScan; private int fNumberOfScannedFiles; // Protected by fLock private IFile fCurrentFile; // Protected by fLock - private Object fLock= new Object(); + private final Object fLock = new Object(); private final MultiStatus fStatus; private volatile boolean fFatalError; // If true, terminates the search. - private boolean fIsLightweightAutoRefresh; + private volatile boolean fIsLightweightAutoRefresh; public TextSearchVisitor(TextSearchRequestor collector, Pattern searchPattern) { fCollector= collector; @@ -314,117 +312,109 @@ public TextSearchVisitor(TextSearchRequestor collector, Pattern searchPattern) { fSearchPattern= searchPattern; fIsLightweightAutoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null); + fileBatches = new ConcurrentLinkedQueue<>(); } public IStatus search(IFile[] files, IProgressMonitor monitor) { if (files.length == 0) { return fStatus; } - fProgressMonitor= monitor == null ? new NullProgressMonitor() : monitor; - fNumberOfScannedFiles= 0; - fNumberOfFilesToScan= files.length; - fCurrentFile= null; - int maxThreads= fCollector.canRunInParallel() ? NUMBER_OF_LOGICAL_THREADS : 1; - int jobCount= 1; - if (maxThreads > 1) { - jobCount= (files.length + FILES_PER_JOB - 1) / FILES_PER_JOB; + fProgressMonitor = monitor == null ? new NullProgressMonitor() : monitor; + synchronized (fLock) { + fNumberOfScannedFiles = 0; + fCurrentFile = null; } - // Too many job references can cause OOM, see bug 514961 - if (jobCount > MAX_JOBS_COUNT) { - jobCount= MAX_JOBS_COUNT; - } - - // Seed count over 1 can cause endless waits, see bug 543629 comment 2 - // TODO use seed = jobCount after the bug 543660 in JobGroup is fixed - final int seed = 1; - final JobGroup jobGroup = new TextSearchJobGroup("Text Search", maxThreads, seed); //$NON-NLS-1$ + int threadsNeeded = Math.min(files.length, NUMBER_OF_LOGICAL_THREADS); + // All but 1 threads should search. 1 thread does the UI updates: + int jobCount = fCollector.canRunInParallel() && threadsNeeded > 1 ? threadsNeeded - 1 : 1; long startTime= TRACING ? System.currentTimeMillis() : 0; - Job monitorUpdateJob= new Job(SearchMessages.TextSearchVisitor_progress_updating_job) { - private int fLastNumberOfScannedFiles= 0; + try { + String taskName= fSearchPattern.pattern().isEmpty() + ? SearchMessages.TextSearchVisitor_filesearch_task_label + : ""; //$NON-NLS-1$ + try { + fCollector.beginReporting(); + if (fProgressMonitor.isCanceled()) { + throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); + } + + Map documentsInEditors= PlatformUI.isWorkbenchRunning() ? evalNonFileBufferDocuments() : Collections.emptyMap(); - @Override - public IStatus run(IProgressMonitor inner) { - while (!inner.isCanceled()) { - // Propagate user cancellation to the JobGroup. - if (fProgressMonitor.isCanceled()) { - jobGroup.cancel(); - break; - } + // group files with same content together: + Map> localFilesByLocation = new LinkedHashMap<>(); + Map> remoteFilesByLocation = new LinkedHashMap<>(); + + for (IFile file : files) { + IPath path = file.getLocation(); + String key = path == null ? file.getLocationURI().toString() : path.toString(); + Map> filesByLocation = (path != null) ? localFilesByLocation + : remoteFilesByLocation; + filesByLocation.computeIfAbsent(key, k -> new ArrayList<>()).add(file); + } + localFilesByLocation.values().forEach(fileBatches::offer); + remoteFilesByLocation.values().forEach(fileBatches::offer); + int numberOfFilesToScan = fileBatches.size(); + fProgressMonitor.beginTask(taskName, numberOfFilesToScan); + + // Seed count over 1 can cause endless waits, see bug 543629 + // comment 2 + // TODO use seed = jobCount after the bug 543660 in JobGroup is + // fixed + + final int seed = 1; + final JobGroup jobGroup = new TextSearchJobGroup("Text Search", jobCount, seed); //$NON-NLS-1$ + for (int i = 0; i < jobCount; i++) { + Job job = new TextSearchJob(documentsInEditors, jobCount); + job.setJobGroup(jobGroup); + job.schedule(); + } + // update progress until finished or canceled: + int numberOfScannedFiles = 0; + int lastNumberOfScannedFiles = 0; + while (!fProgressMonitor.isCanceled() && !jobGroup.getActiveJobs().isEmpty() + && numberOfScannedFiles != numberOfFilesToScan) { IFile file; - int numberOfScannedFiles; synchronized (fLock) { - file= fCurrentFile; - numberOfScannedFiles= fNumberOfScannedFiles; + try { + // time only relevant on how often progress is + // updated, but cancel is notified immediately: + fLock.wait(100); + } catch (InterruptedException e) { + fProgressMonitor.setCanceled(true); + break; + } + file = fCurrentFile; + numberOfScannedFiles = fNumberOfScannedFiles; } if (file != null) { - String fileName= file.getName(); - Object[] args= { fileName, Integer.valueOf(numberOfScannedFiles), Integer.valueOf(fNumberOfFilesToScan)}; + String fileName = file.getName(); + Object[] args = { fileName, Integer.valueOf(numberOfScannedFiles), + Integer.valueOf(numberOfFilesToScan) }; fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args)); - int steps= numberOfScannedFiles - fLastNumberOfScannedFiles; + int steps = numberOfScannedFiles - lastNumberOfScannedFiles; fProgressMonitor.worked(steps); - fLastNumberOfScannedFiles += steps; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return Status.OK_STATUS; + lastNumberOfScannedFiles += steps; } } - return Status.OK_STATUS; - } - }; - - try { - String taskName= fSearchPattern.pattern().isEmpty() - ? SearchMessages.TextSearchVisitor_filesearch_task_label - : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, fSearchPattern.pattern()); - fProgressMonitor.beginTask(taskName, fNumberOfFilesToScan); - monitorUpdateJob.setSystem(true); - monitorUpdateJob.schedule(); - try { - fCollector.beginReporting(); - Map documentsInEditors= PlatformUI.isWorkbenchRunning() ? evalNonFileBufferDocuments() : Collections.emptyMap(); - int filesPerJob = Math.max(1, files.length / jobCount); - IFile[] filesByLocation= new IFile[files.length]; - System.arraycopy(files, 0, filesByLocation, 0, files.length); - // Sorting files to search by location allows to more easily reuse - // search results from one file to the other when they have same location - Arrays.sort(filesByLocation, (o1, o2) -> { - if (o1 == o2) { - return 0; - } - if (o1.getLocation() == o2.getLocation()) { - return 0; - } - if (o1.getLocation() == null) { - return +1; - } - if (o2.getLocation() == null) { - return -1; - } - return o1.getLocation().toString().compareTo(o2.getLocation().toString()); - }); - for (int first= 0; first < filesByLocation.length; first += filesPerJob) { - int end= Math.min(filesByLocation.length, first + filesPerJob); - Job job= new TextSearchJob(filesByLocation, first, end, documentsInEditors); - job.setJobGroup(jobGroup); - job.schedule(); + if (fProgressMonitor.isCanceled()) { + jobGroup.cancel(); } - - // The monitorUpdateJob is managing progress and cancellation, - // so it is ok to pass a null monitor into the job group. + // no need to pass progressMonitor (which would show wrong + // progress) but null because jobGroup was already finished / + // canceled anyway: jobGroup.join(0, null); - if (fProgressMonitor.isCanceled()) + if (fProgressMonitor.isCanceled()) { throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); + } fStatus.addAll(jobGroup.getResult()); return fStatus; } catch (InterruptedException e) { throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); } finally { - monitorUpdateJob.cancel(); + fileBatches.clear(); } } finally { fProgressMonitor.done(); @@ -488,6 +478,12 @@ private void evaluateTextEditor(Map result, IEditorPart ep) { } private boolean hasBinaryContent(CharSequence seq, IFile file) throws CoreException { + if (seq instanceof String) { + if (!((String) seq).contains("\0")) { //$NON-NLS-1$ + // fail fast to avoid file.getContentDescription(): + return false; + } + } IContentDescription desc= file.getContentDescription(); if (desc != null) { IContentType contentType= desc.getContentType(); @@ -506,6 +502,7 @@ private boolean hasBinaryContent(CharSequence seq, IFile file) throws CoreExcept } } } catch (IndexOutOfBoundsException e) { + // ignored } catch (FileCharSequenceException ex) { if (ex.getCause() instanceof CharConversionException) return true; @@ -534,7 +531,7 @@ private List locateMatches(IFile file, CharSequence searc } } // Periodically check for cancellation and quit working on the current file if the job has been cancelled. - if (++k % 20 == 0 && monitor.isCanceled()) { + if (k++ % 20 == 0 && monitor.isCanceled()) { break; } } @@ -574,4 +571,3 @@ private String getCharSetName(IFile file) { } } - diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/GotoMarkerAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/GotoMarkerAction.java deleted file mode 100644 index 813d561..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/GotoMarkerAction.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -/** - * @deprecated old search - */ -@Deprecated -class GotoMarkerAction extends Action { - - private SearchResultViewer fViewer; - - public GotoMarkerAction(SearchResultViewer viewer) { - super(SearchMessages.SearchResultView_gotoMarker_text); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_GOTO); - setToolTipText(SearchMessages.SearchResultView_gotoMarker_tooltip); - fViewer= viewer; - } - - @Override - public void run() { - fViewer.showResult(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllResultsAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllResultsAction.java deleted file mode 100644 index c8144f7..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllResultsAction.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -/** - * @deprecated old search - */ -@Deprecated -class RemoveAllResultsAction extends Action { - - public RemoveAllResultsAction() { - super(SearchMessages.SearchResultView_removeAllResults_text); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_REM_ALL); - setToolTipText(SearchMessages.SearchResultView_removeAllResults_tooltip); - } - - @Override - public void run() { - SearchManager.getDefault().removeAllResults(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllSearchesAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllSearchesAction.java deleted file mode 100644 index f25f1e7..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveAllSearchesAction.java +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -/** - * @deprecated old search - */ -@Deprecated -class RemoveAllSearchesAction extends Action { - - public RemoveAllSearchesAction() { - super(SearchMessages.SearchResultView_removeAllSearches_text); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_REM_ALL); - setToolTipText(SearchMessages.SearchResultView_removeAllSearches_tooltip); - } - - @Override - public void run() { - SearchManager.getDefault().removeAllSearches(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveMatchAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveMatchAction.java deleted file mode 100644 index e9ffee5..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveMatchAction.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.core.runtime.CoreException; - -import org.eclipse.core.resources.IMarker; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.IStructuredSelection; - -import org.eclipse.search.internal.ui.util.ExceptionHandler; -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * @deprecated old search - */ -@Deprecated -class RemoveMatchAction extends Action { - - private ISelectionProvider fSelectionProvider; - - public RemoveMatchAction(ISelectionProvider provider) { - super(SearchMessages.SearchResultView_removeMatch_text); - setToolTipText(SearchMessages.SearchResultView_removeMatch_tooltip); - fSelectionProvider= provider; - } - - @Override - public void run() { - IMarker[] markers= getMarkers(fSelectionProvider.getSelection()); - if (markers != null) - try { - SearchPlugin.getWorkspace().deleteMarkers(markers); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } - } - - private IMarker[] getMarkers(ISelection s) { - if (! (s instanceof IStructuredSelection) || s.isEmpty()) - return null; - - IStructuredSelection selection= (IStructuredSelection)s; - int size= selection.size(); - if (size != 1) - return null; - if (selection.getFirstElement() instanceof ISearchResultViewEntry) { - IMarker marker= ((ISearchResultViewEntry)selection.getFirstElement()).getSelectedMarker(); - if (marker != null) - return new IMarker[] {marker}; - } - return null; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemovePotentialMatchesAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/RemovePotentialMatchesAction.java deleted file mode 100644 index 2089ea7..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemovePotentialMatchesAction.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import org.eclipse.core.runtime.CoreException; - -import org.eclipse.core.resources.IMarker; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; - -import org.eclipse.ui.IWorkbenchSite; - -import org.eclipse.search.internal.ui.util.ExceptionHandler; -import org.eclipse.search.ui.SearchUI; - -/** - * @deprecated old search - */ -@Deprecated -class RemovePotentialMatchesAction extends Action { - - private IWorkbenchSite fSite; - - public RemovePotentialMatchesAction(IWorkbenchSite site) { - fSite= site; - - if (usePluralLabel()) { - setText(SearchMessages.RemovePotentialMatchesAction_removePotentialMatches_text); - setToolTipText(SearchMessages.RemovePotentialMatchesAction_removePotentialMatches_tooltip); - } - else { - setText(SearchMessages.RemovePotentialMatchesAction_removePotentialMatch_text); - setToolTipText(SearchMessages.RemovePotentialMatchesAction_removePotentialMatch_tooltip); - } - } - - @Override - public void run() { - IMarker[] markers= getMarkers(); - if (markers != null) - try { - SearchPlugin.getWorkspace().deleteMarkers(markers); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } - else { - String title= SearchMessages.RemovePotentialMatchesAction_dialog_title; - String message= SearchMessages.RemovePotentialMatchesAction_dialog_message; - MessageDialog.openInformation(fSite.getShell(), title, message); - } - - // action only makes sense once - setEnabled(false); - } - - private IMarker[] getMarkers() { - - ISelection s= fSite.getSelectionProvider().getSelection(); - if (! (s instanceof IStructuredSelection)) - return null; - IStructuredSelection selection= (IStructuredSelection)s; - - int size= selection.size(); - if (size <= 0) - return null; - - ArrayList markers= new ArrayList<>(size * 3); - Iterator iter= selection.iterator(); - while (iter.hasNext()) { - SearchResultViewEntry entry= (SearchResultViewEntry)iter.next(); - Iterator entryIter= entry.getMarkers().iterator(); - while (entryIter.hasNext()) { - IMarker marker= entryIter.next(); - if (marker.getAttribute(SearchUI.POTENTIAL_MATCH, false)) - markers.add(marker); - } - } - return markers.toArray(new IMarker[markers.size()]); - } - - private boolean usePluralLabel() { - ISelection s= fSite.getSelectionProvider().getSelection(); - - if (! (s instanceof IStructuredSelection) || s.isEmpty()) - return false; - - IStructuredSelection selection= (IStructuredSelection)s; - int size= selection.size(); - if (size <= 0) - return false; - - int markerCount= 0; - Iterator iter= selection.iterator(); - while (iter.hasNext()) { - SearchResultViewEntry entry= (SearchResultViewEntry)iter.next(); - Iterator entryIter= entry.getMarkers().iterator(); - while (entryIter.hasNext()) { - IMarker marker= entryIter.next(); - if (marker.getAttribute(SearchUI.POTENTIAL_MATCH, false)) { - markerCount++; - } - if (markerCount > 1) - return true; - } - } - return false; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveResultAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveResultAction.java deleted file mode 100644 index 6677d5d..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/RemoveResultAction.java +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import org.eclipse.swt.custom.BusyIndicator; - -import org.eclipse.core.runtime.CoreException; - -import org.eclipse.core.resources.IMarker; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.jface.viewers.IStructuredSelection; - -import org.eclipse.search.internal.ui.util.ExceptionHandler; -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * @deprecated old search - */ -@Deprecated -class RemoveResultAction extends Action { - - private ISelectionProvider fSelectionProvider; - - public RemoveResultAction(ISelectionProvider provider, boolean stringsDependOnMatchCount) { - fSelectionProvider= provider; - if (!stringsDependOnMatchCount || usePluralLabel()) { - setText(SearchMessages.SearchResultView_removeEntries_text); - setToolTipText(SearchMessages.SearchResultView_removeEntries_tooltip); - } - else { - setText(SearchMessages.SearchResultView_removeEntry_text); - setToolTipText(SearchMessages.SearchResultView_removeEntry_tooltip); - } - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_REM); - } - - @Override - public void run() { - final IMarker[] markers= getMarkers(fSelectionProvider.getSelection()); - if (markers != null) { - BusyIndicator.showWhile(SearchPlugin.getActiveWorkbenchShell().getDisplay(), () -> { - try { - SearchPlugin.getWorkspace().deleteMarkers(markers); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } - }); - } - } - - private IMarker[] getMarkers(ISelection s) { - if (! (s instanceof IStructuredSelection) || s.isEmpty()) - return null; - - IStructuredSelection selection= (IStructuredSelection)s; - int size= selection.size(); - if (size <= 0) - return null; - ArrayList markers= new ArrayList<>(size * 3); - int markerCount= 0; - Iterator iter= selection.iterator(); - while (iter.hasNext()) { - SearchResultViewEntry entry= (SearchResultViewEntry)iter.next(); - markerCount += entry.getMatchCount(); - markers.addAll(entry.getMarkers()); - } - return markers.toArray(new IMarker[markerCount]); - } - - private boolean usePluralLabel() { - ISelection s= fSelectionProvider.getSelection(); - if (s == null || s.isEmpty() || !(s instanceof IStructuredSelection)) - return false; - IStructuredSelection selection= (IStructuredSelection)s; - - if (selection.size() != 1) - return true; - - Object firstElement= selection.getFirstElement(); - if (firstElement instanceof ISearchResultViewEntry) - return ((ISearchResultViewEntry)firstElement).getMatchCount() > 1; - return false; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ResourceToItemsMapper.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ResourceToItemsMapper.java deleted file mode 100644 index 3c5605b..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ResourceToItemsMapper.java +++ /dev/null @@ -1,175 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Stack; - -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Item; - -import org.eclipse.core.resources.IResource; - -import org.eclipse.jface.viewers.ContentViewer; -import org.eclipse.jface.viewers.ILabelProvider; - -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * Helper class for updating error markers and other decorators that work on resources. - * Items are mapped to their element's underlying resource. - * Method resourceChanged updates all items that are affected from the changed - * elements. - * @deprecated old search - */ -@Deprecated -class ResourceToItemsMapper { - - private static final int NUMBER_LIST_REUSE= 10; - - /** map from IResource to {@code Item | List} **/ - private HashMap fResourceToItem; - private Stack> fReuseLists; - - private ContentViewer fContentViewer; - - public ResourceToItemsMapper(ContentViewer viewer) { - fResourceToItem= new HashMap<>(); - fReuseLists= new Stack<>(); - - fContentViewer= viewer; - } - - /** - * Must be called from the UI thread. - * @param changedResource changed resources - */ - @SuppressWarnings("unchecked") - public void resourceChanged(IResource changedResource) { - Object obj= fResourceToItem.get(changedResource); - if (obj == null) { - // not mapped - } else if (obj instanceof Item) { - updateItem((Item) obj); - } else { // List of Items - for (Item element : (List) obj) { - updateItem(element); - } - } - } - - private void updateItem(Item item) { - if (!item.isDisposed()) { // defensive code - ILabelProvider lprovider= (ILabelProvider) fContentViewer.getLabelProvider(); - - Object data= item.getData(); - - String oldText= item.getText(); - String text= lprovider.getText(data); - if (text != null && !text.equals(oldText)) { - item.setText(text); - } - - Image oldImage= item.getImage(); - Image image= lprovider.getImage(data); - if (image != null && !image.equals(oldImage)) { - item.setImage(image); - } - } - } - - /** - * Adds a new item to the map. - * @param element Element to map - * @param item The item used for the element - */ - public void addToMap(Object element, Item item) { - IResource resource= ((ISearchResultViewEntry)element).getResource(); - if (resource != null) { - Object existingMapping= fResourceToItem.get(resource); - if (existingMapping == null) { - fResourceToItem.put(resource, item); - } else if (existingMapping instanceof Item) { - if (existingMapping != item) { - List list= getNewList(); - list.add((Item) existingMapping); - list.add(item); - fResourceToItem.put(resource, list); - } - } else { // List - @SuppressWarnings("unchecked") - List list= (List) existingMapping; - if (!list.contains(item)) { - list.add(item); - } - } - } - } - - /** - * Removes an element from the map. - * @param element element to remove - * @param item The item used for the element - */ - @SuppressWarnings("unlikely-arg-type") - public void removeFromMap(Object element, Item item) { - IResource resource= ((ISearchResultViewEntry)element).getResource(); - if (resource != null) { - Object existingMapping= fResourceToItem.get(resource); - if (existingMapping == null) { - return; - } else if (existingMapping instanceof Item) { - fResourceToItem.remove(resource); - } else { // List - @SuppressWarnings("unchecked") - List list= (List) existingMapping; - list.remove(item); - if (list.isEmpty()) { - fResourceToItem.remove(list); - releaseList(list); - } - } - } - } - - private List getNewList() { - if (!fReuseLists.isEmpty()) { - return fReuseLists.pop(); - } - return new ArrayList<>(2); - } - - private void releaseList(List list) { - if (fReuseLists.size() < NUMBER_LIST_REUSE) { - fReuseLists.push(list); - } - } - - /** - * Clears the map. - */ - public void clearMap() { - fResourceToItem.clear(); - } - - /** - * Tests if the map is empty - * @return returns if the map is empty - */ - public boolean isEmpty() { - return fResourceToItem.isEmpty(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ScopePart.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ScopePart.java index f07113f..e87aa34 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ScopePart.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/ScopePart.java @@ -434,7 +434,9 @@ public void widgetSelected(SelectionEvent e) { private boolean canSearchInSelection() { ISelection selection= fSearchDialog.getSelection(); - return (selection instanceof IStructuredSelection) && !selection.isEmpty() || fActiveEditorCanProvideScopeSelection && fSearchDialog.getActiveEditorInput() != null; + return (selection instanceof IStructuredSelection) && !selection.isEmpty() + && !selectedResourcesFromContainer(fSearchDialog).isEmpty() + || fActiveEditorCanProvideScopeSelection && fSearchDialog.getActiveEditorInput() != null; } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/Search.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/Search.java deleted file mode 100644 index 434a0fc..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/Search.java +++ /dev/null @@ -1,214 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.eclipse.swt.widgets.Shell; - -import org.eclipse.core.runtime.Assert; - -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.dialogs.ProgressMonitorDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ISelection; - -import org.eclipse.search.internal.ui.util.ExceptionHandler; -import org.eclipse.search.ui.IActionGroupFactory; -import org.eclipse.search.ui.IContextMenuContributor; -import org.eclipse.search.ui.IGroupByKeyComputer; -import org.eclipse.search.ui.ISearchResultViewEntry; -/** - * @deprecated old search - */ -@Deprecated -public class Search extends Object { - private String fPageId; - private String fSingularLabel; - private String fPluralLabelPattern; - private ImageDescriptor fImageDescriptor; - private ILabelProvider fLabelProvider; - private ISelection fSelection; - private ArrayList fResults; - private IAction fGotoMarkerAction; - private IContextMenuContributor fContextMenuContributor; - private IActionGroupFactory fActionGroupFactory; - private IGroupByKeyComputer fGroupByKeyComputer; - private IRunnableWithProgress fOperation; - - - public Search(String pageId, String singularLabel, String pluralLabelPattern, ILabelProvider labelProvider, ImageDescriptor imageDescriptor, IAction gotoMarkerAction, IActionGroupFactory groupFactory, IGroupByKeyComputer groupByKeyComputer, IRunnableWithProgress operation) { - fPageId= pageId; - fSingularLabel= singularLabel; - fPluralLabelPattern= pluralLabelPattern; - fImageDescriptor= imageDescriptor; - fLabelProvider= labelProvider; - fGotoMarkerAction= gotoMarkerAction; - fActionGroupFactory= groupFactory; - fGroupByKeyComputer= groupByKeyComputer; - fOperation= operation; - - if (fPluralLabelPattern == null) - fPluralLabelPattern= ""; //$NON-NLS-1$ - } - - public Search(String pageId, String singularLabel, String pluralLabelPattern, ILabelProvider labelProvider, ImageDescriptor imageDescriptor, IAction gotoMarkerAction, IContextMenuContributor contextMenuContributor, IGroupByKeyComputer groupByKeyComputer, IRunnableWithProgress operation) { - fPageId= pageId; - fSingularLabel= singularLabel; - fPluralLabelPattern= pluralLabelPattern; - fImageDescriptor= imageDescriptor; - fLabelProvider= labelProvider; - fGotoMarkerAction= gotoMarkerAction; - fContextMenuContributor= contextMenuContributor; - fGroupByKeyComputer= groupByKeyComputer; - fOperation= operation; - - if (fPluralLabelPattern == null) - fPluralLabelPattern= ""; //$NON-NLS-1$ - } - - /** - * Returns the full description of the search. - * The description set by the client where - * {0} will be replaced by the match count. - * @return the full description - */ - String getFullDescription() { - if (fSingularLabel != null && getItemCount() == 1) - return fSingularLabel; - - // try to replace "{0}" with the match count - int i= fPluralLabelPattern.lastIndexOf("{0}"); //$NON-NLS-1$ - if (i < 0) - return fPluralLabelPattern; - return fPluralLabelPattern.substring(0, i) + getItemCount()+ fPluralLabelPattern.substring(Math.min(i + 3, fPluralLabelPattern.length())); - } - - /** - * Returns a short description of the search. - * Cuts off after 30 characters and adds ... - * The description set by the client where - * {0} will be replaced by the match count. - * @return the short description - */ - String getShortDescription() { - String text= getFullDescription(); - int separatorPos= text.indexOf(" - "); //$NON-NLS-1$ - if (separatorPos < 1) - return text.substring(0, Math.min(50, text.length())) + "..."; // use first 50 characters //$NON-NLS-1$ - if (separatorPos < 30) - return text; // don't cut - if (text.charAt(0) == '"') - return text.substring(0, Math.min(30, text.length())) + "...\" - " + text.substring(Math.min(separatorPos + 3, text.length())); //$NON-NLS-1$ - return text.substring(0, Math.min(30, text.length())) + "... - " + text.substring(Math.min(separatorPos + 3, text.length())); //$NON-NLS-1$ - } - /** - * Image used when search is displayed in a list - * @return the image descriptor - */ - ImageDescriptor getImageDescriptor() { - return fImageDescriptor; - } - - int getItemCount() { - int count= 0; - Iterator iter= getResults().iterator(); - while (iter.hasNext()) - count += ((ISearchResultViewEntry)iter.next()).getMatchCount(); - return count; - } - - List getResults() { - if (fResults == null) - return new ArrayList<>(); - return fResults; - } - - ILabelProvider getLabelProvider() { - return fLabelProvider; - } - - void searchAgain() { - if (fOperation == null) - return; - Shell shell= SearchPlugin.getActiveWorkbenchShell(); - boolean isAutoBuilding= SearchPlugin.setAutoBuilding(false); - try { - new ProgressMonitorDialog(shell).run(true, true, fOperation); - } catch (InvocationTargetException ex) { - ExceptionHandler.handle(ex, shell, SearchMessages.Search_Error_search_title, SearchMessages.Search_Error_search_message); - } catch(InterruptedException e) { - } finally { - SearchPlugin.setAutoBuilding(isAutoBuilding); - } - } - - boolean isSameSearch(Search search) { - return search != null && search.getOperation() == fOperation && fOperation != null; - } - - void backupMarkers() { - Iterator iter= getResults().iterator(); - while (iter.hasNext()) { - iter.next().backupMarkers(); - } - } - - String getPageId() { - return fPageId; - } - - IGroupByKeyComputer getGroupByKeyComputer() { - return fGroupByKeyComputer; - } - - public IRunnableWithProgress getOperation() { - return fOperation; - } - - IAction getGotoMarkerAction() { - return fGotoMarkerAction; - } - - IContextMenuContributor getContextMenuContributor() { - return fContextMenuContributor; - } - - IActionGroupFactory getActionGroupFactory() { - return fActionGroupFactory; - } - - public void removeResults() { - fResults= null; - } - - void setResults(ArrayList results) { - Assert.isNotNull(results); - fResults= results; - } - - ISelection getSelection() { - return fSelection; - } - - void setSelection(ISelection selection) { - fSelection= selection; - } -} - diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchAgainAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchAgainAction.java deleted file mode 100644 index 5264218..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchAgainAction.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -/** - * @deprecated old search - */ -@Deprecated -class SearchAgainAction extends Action { - - public SearchAgainAction() { - super(SearchMessages.SearchResultView_searchAgain_text); - setToolTipText(SearchMessages.SearchResultView_searchAgain_tooltip); - } - - @Override - public void run() { - Search selected= SearchManager.getDefault().getCurrentSearch(); - if (selected != null) - selected.searchAgain(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDropDownAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDropDownAction.java deleted file mode 100644 index 6401687..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDropDownAction.java +++ /dev/null @@ -1,103 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.Iterator; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.MenuItem; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IMenuCreator; - -/** - * @deprecated old search - */ -@Deprecated -class SearchDropDownAction extends Action implements IMenuCreator { - - - public static final int RESULTS_IN_DROP_DOWN= 10; - - private Menu fMenu; - - public SearchDropDownAction() { - setText(SearchMessages.SearchResultView_previousSearches_text); - setToolTipText(SearchMessages.SearchResultView_previousSearches_tooltip); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_HISTORY); - setMenuCreator(this); - } - - @Override - public void dispose() { - if (fMenu != null) { - fMenu.dispose(); - fMenu= null; - } - } - - @Override - public Menu getMenu(Menu parent) { - return null; - } - - @Override - public Menu getMenu(Control parent) { - if (fMenu != null) - fMenu.dispose(); - - fMenu= new Menu(parent); - boolean checkedOne= false; - Iterator iter= SearchManager.getDefault().getPreviousSearches().iterator(); - Search selected= SearchManager.getDefault().getCurrentSearch(); - int i= 0; - while (iter.hasNext() && i++ < RESULTS_IN_DROP_DOWN) { - Search search= iter.next(); - ShowSearchAction action= new ShowSearchAction(search); - action.setChecked(search.equals(selected)); - if (search.equals(selected)) - checkedOne= true; - addActionToMenu(fMenu, action); - } - new MenuItem(fMenu, SWT.SEPARATOR); - if (iter.hasNext()) { - Action others= new ShowSearchesAction(); - others.setChecked(!checkedOne); - addActionToMenu(fMenu, others); - } - addActionToMenu(fMenu, new RemoveAllSearchesAction()); - return fMenu; - } - - protected void addActionToMenu(Menu parent, Action action) { - ActionContributionItem item= new ActionContributionItem(action); - item.fill(parent, -1); - } - - @Override - public void run() { - new ShowSearchesAction().run(true); - } - - /** - * Get's rid of the menu, because the menu hangs on to - * the searches, etc. - */ - void clear() { - dispose(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchManager.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchManager.java deleted file mode 100644 index e5744ce..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchManager.java +++ /dev/null @@ -1,501 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Map; - -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; - -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.ProgressMonitorDialog; -import org.eclipse.jface.viewers.Viewer; - -import org.eclipse.ui.actions.WorkspaceModifyOperation; - -import org.eclipse.search.internal.ui.util.ExceptionHandler; -import org.eclipse.search.ui.IGroupByKeyComputer; -import org.eclipse.search.ui.SearchUI; - - -/** - * Manage search results - * @deprecated old search - */ -@Deprecated -public class SearchManager implements IResourceChangeListener { - - static SearchManager fgDefault; - - Search fCurrentSearch= null; - - private SearchManager() { - SearchPlugin.getWorkspace().addResourceChangeListener(this); - } - - private HashSet fListeners= new HashSet<>(); - private LinkedList fPreviousSearches= new LinkedList<>(); - private boolean fIsRemoveAll= false; - - public static synchronized SearchManager getDefault() { - if (fgDefault == null) - fgDefault= new SearchManager(); - return fgDefault; - } - - public void dispose() { - SearchPlugin.getWorkspace().removeResourceChangeListener(this); - } - - /** - * Returns the list with previous searches (ISearch). - * @return previous searches - */ - LinkedList getPreviousSearches() { - return fPreviousSearches; - } - /** - * Returns the list with current (last) results - * @return the current results - */ - ArrayList getCurrentResults() { - if (fCurrentSearch == null) - return new ArrayList<>(0); - return (ArrayList)fCurrentSearch.getResults(); - } - - public Search getCurrentSearch() { - return fCurrentSearch; - } - - void removeAllSearches() { - SearchPlugin.getWorkspace().removeResourceChangeListener(this); - WorkspaceModifyOperation op= new WorkspaceModifyOperation(null) { - @Override - protected void execute(IProgressMonitor monitor) throws CoreException { - monitor.beginTask(SearchMessages.SearchManager_updating, 100); - SearchPlugin.getWorkspace().getRoot().deleteMarkers(SearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE); - monitor.worked(100); - monitor.done(); - } - }; - boolean isAutoBuilding= SearchPlugin.setAutoBuilding(false); - try { - ProgressMonitorDialog dialog= new ProgressMonitorDialog(getShell()); - dialog.run(true, true, op); - } catch (InvocationTargetException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } catch (InterruptedException e) { - // Do nothing. Operation has been canceled. - } finally { - SearchPlugin.getWorkspace().addResourceChangeListener(this); - SearchPlugin.setAutoBuilding(isAutoBuilding); - } - - // clear searches - fPreviousSearches= new LinkedList<>(); - fCurrentSearch= null; - - // update viewers - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) { - SearchResultViewer viewer= iter.next(); - handleAllSearchesRemoved(viewer); - } - } - - private void handleAllSearchesRemoved(SearchResultViewer viewer) { - viewer.handleAllSearchesRemoved(); - } - - void setCurrentSearch(final Search search) { - if (fCurrentSearch == search) - return; - - SearchPlugin.getWorkspace().removeResourceChangeListener(this); - WorkspaceModifyOperation op= new WorkspaceModifyOperation(null) { - @Override - protected void execute(IProgressMonitor monitor) throws CoreException { - internalSetCurrentSearch(search, monitor); - } - }; - boolean isAutoBuilding= SearchPlugin.setAutoBuilding(false); - try { - ProgressMonitorDialog dialog= new ProgressMonitorDialog(getShell()); - dialog.run(true, true, op); - } catch (InvocationTargetException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_switchSearch_title, SearchMessages.Search_Error_switchSearch_message); - } catch (InterruptedException e) { - // Do nothing. Operation has been canceled. - } finally { - SearchPlugin.setAutoBuilding(isAutoBuilding); - } - - getPreviousSearches().remove(search); - getPreviousSearches().addFirst(search); - } - - void internalSetCurrentSearch(final Search search, IProgressMonitor monitor) { - if (fCurrentSearch != null) - fCurrentSearch.backupMarkers(); - - final Search previousSearch= fCurrentSearch; - fCurrentSearch= search; - monitor.beginTask(SearchMessages.SearchManager_updating, getCurrentResults().size() + 20); - - // remove current search markers - try { - SearchPlugin.getWorkspace().getRoot().deleteMarkers(SearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } - monitor.worked(10); - - // add search markers - Iterator iter= getCurrentResults().iterator(); - ArrayList emptyEntries= new ArrayList<>(10); - boolean filesChanged= false; - boolean filesDeleted= false; - IGroupByKeyComputer groupByKeyComputer= getCurrentSearch().getGroupByKeyComputer(); - while (iter.hasNext()) { - monitor.worked(1); - SearchResultViewEntry entry= iter.next(); - Iterator> attrPerMarkerIter= entry.getAttributesPerMarker().iterator(); - entry.clearMarkerList(); - if (entry.getResource() == null || !entry.getResource().exists()) { - emptyEntries.add(entry); - filesDeleted= true; - continue; - } - while (attrPerMarkerIter.hasNext()) { - IMarker newMarker= null; - try { - newMarker= entry.getResource().createMarker(entry.getMarkerType()); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_createMarker_title, SearchMessages.Search_Error_createMarker_message); - continue; - } - try { - newMarker.setAttributes(attrPerMarkerIter.next()); - if (groupByKeyComputer !=null && groupByKeyComputer.computeGroupByKey(newMarker) == null) { - filesDeleted= true; - newMarker.delete(); - continue; - } - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_markerAttributeAccess_title, SearchMessages.Search_Error_markerAttributeAccess_message); - } - entry.add(newMarker); - } - if (entry.getMatchCount() == 0) - emptyEntries.add(entry); - else if (!filesChanged && entry.getResource().getModificationStamp() != entry.getModificationStamp()) - filesChanged= true; - } - getCurrentResults().removeAll(emptyEntries); - monitor.worked(10); - - String warningMessage= null; - Display display= getDisplay(); - - if (filesChanged) - warningMessage= SearchMessages.SearchManager_resourceChanged; - if (filesDeleted) { - if (warningMessage == null) - warningMessage= ""; //$NON-NLS-1$ - else - warningMessage += "\n"; //$NON-NLS-1$ - warningMessage += SearchMessages.SearchManager_resourceDeleted; - } - if (warningMessage != null) { - if (display != null && !display.isDisposed()) { - final String warningTitle= SearchMessages.SearchManager_resourceChangedWarning; - final String warningMsg= warningMessage; - display.syncExec(() -> MessageDialog.openWarning(getShell(), warningTitle, warningMsg)); - } - } - - // update viewers - Iterator iter2= fListeners.iterator(); - if (display != null && !display.isDisposed()) { - final Viewer visibleViewer= ((SearchResultView)SearchUI.getSearchResultView()).getViewer(); - while (iter2.hasNext()) { - final SearchResultViewer viewer= iter2.next(); - display.syncExec(() -> { - if (previousSearch != null && viewer == visibleViewer) - previousSearch.setSelection(viewer.getSelection()); - viewer.setInput(null); - viewer.setPageId(search.getPageId()); - viewer.setGotoMarkerAction(search.getGotoMarkerAction()); - viewer.setContextMenuTarget(search.getContextMenuContributor()); - viewer.setActionGroupFactory(null); - viewer.setInput(getCurrentResults()); - viewer.setActionGroupFactory(search.getActionGroupFactory()); - viewer.setSelection(fCurrentSearch.getSelection(), true); - }); - } - } - monitor.done(); - } - - /** - * Returns the number of matches - * @return the number of matches - */ - int getCurrentItemCount() { - if (fCurrentSearch != null) - return fCurrentSearch.getItemCount(); - return 0; - } - - void removeAllResults() { - fIsRemoveAll= true; - try { - SearchPlugin.getWorkspace().getRoot().deleteMarkers(SearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - fIsRemoveAll= false; - } - } - - void addNewSearch(final Search newSearch) { - - SearchPlugin.getWorkspace().removeResourceChangeListener(this); - - // Clear the viewers - Iterator iter= fListeners.iterator(); - Display display= getDisplay(); - if (display != null && !display.isDisposed()) { - final Viewer visibleViewer= ((SearchResultView)SearchUI.getSearchResultView()).getViewer(); - while (iter.hasNext()) { - final SearchResultViewer viewer= iter.next(); - display.syncExec(() -> { - if (fCurrentSearch != null && viewer == visibleViewer) - fCurrentSearch.setSelection(viewer.getSelection()); - setNewSearch(viewer, newSearch); - }); - } - } - - if (fCurrentSearch != null) { - if (fCurrentSearch.isSameSearch(newSearch)) - getPreviousSearches().remove(fCurrentSearch); - else - fCurrentSearch.backupMarkers(); - } - fCurrentSearch= newSearch; - getPreviousSearches().addFirst(fCurrentSearch); - - // Remove the markers - try { - SearchPlugin.getWorkspace().getRoot().deleteMarkers(SearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE); - } catch (CoreException ex) { - ExceptionHandler.handle(ex, SearchMessages.Search_Error_deleteMarkers_title, SearchMessages.Search_Error_deleteMarkers_message); - } - } - - void searchFinished(ArrayList results) { - Assert.isNotNull(results); - getCurrentSearch().setResults(results); - - Display display= getDisplay(); - if (display == null || display.isDisposed()) - return; - - if (Thread.currentThread() == display.getThread()) - handleNewSearchResult(); - else { - display.syncExec(this::handleNewSearchResult); - } - SearchPlugin.getWorkspace().addResourceChangeListener(this); - } - - //--- Change event handling ------------------------------------------------- - - void addSearchChangeListener(SearchResultViewer viewer) { - fListeners.add(viewer); - } - - void removeSearchChangeListener(SearchResultViewer viewer) { - Assert.isNotNull(viewer); - fListeners.remove(viewer); - } - - private final void handleSearchMarkersChanged(IMarkerDelta[] markerDeltas) { - if (fIsRemoveAll) { - handleRemoveAll(); - fIsRemoveAll= false; - return; - } - - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().getControl().setRedraw(false); - - for (IMarkerDelta markerDelta : markerDeltas) { - handleSearchMarkerChanged(markerDelta); - } - - iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().getControl().setRedraw(true); - - } - - private void handleSearchMarkerChanged(IMarkerDelta markerDelta) { - int kind= markerDelta.getKind(); - // don't listen for adds will be done by ISearchResultView.addMatch(...) - if (((kind & IResourceDelta.REMOVED) != 0)) - handleRemoveMatch(markerDelta.getMarker()); - else if ((kind & IResourceDelta.CHANGED) != 0) - handleUpdateMatch(markerDelta.getMarker()); - } - - private void handleRemoveAll() { - if (fCurrentSearch != null) - fCurrentSearch.removeResults(); - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().handleRemoveAll(); - } - - private void handleNewSearchResult() { - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) { - SearchResultViewer viewer= iter.next(); - viewer.setInput(getCurrentResults()); - } - } - - private void setNewSearch(SearchResultViewer viewer, Search search) { - viewer.setInput(null); - viewer.clearTitle(); - viewer.setPageId(search.getPageId()); - viewer.setGotoMarkerAction(search.getGotoMarkerAction()); - viewer.setContextMenuTarget(search.getContextMenuContributor()); - viewer.setActionGroupFactory(search.getActionGroupFactory()); - } - - private void handleRemoveMatch(IMarker marker) { - SearchResultViewEntry entry= findEntry(marker); - if (entry != null) { - entry.remove(marker); - if (entry.getMatchCount() == 0) { - getCurrentResults().remove(entry); - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().handleRemoveMatch(entry); - } - else { - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().handleUpdateMatch(entry, true); - } - } - } - - private void handleUpdateMatch(IMarker marker) { - SearchResultViewEntry entry= findEntry(marker); - if (entry != null) { - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) - iter.next().handleUpdateMatch(entry, false); - } - } - - private SearchResultViewEntry findEntry(IMarker marker) { - Iterator entries= getCurrentResults().iterator(); - while (entries.hasNext()) { - SearchResultViewEntry entry= entries.next(); - if (entry.contains(marker)) - return entry; - } - return null; - } - - /** - * Received a resource event. Since the delta could be created in a - * separate thread this methods post the event into the viewer's - * display thread. - * @param event the event - */ - @Override - public final void resourceChanged(final IResourceChangeEvent event) { - if (event == null) - return; - - final IMarkerDelta[] markerDeltas= event.findMarkerDeltas(SearchUI.SEARCH_MARKER, true); - if (markerDeltas == null || markerDeltas.length < 1) - return; - - Display display= getDisplay(); - if (display == null || display.isDisposed()) - return; - - Runnable runnable= () -> { - if (getCurrentSearch() != null) { - handleSearchMarkersChanged(markerDeltas); - // update title and actions - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) { - SearchResultViewer viewer= iter.next(); - viewer.enableActions(); - viewer.updateTitle(); - } - } - }; - display.syncExec(runnable); - } - /** - * Find and return a valid display - * @return the display - */ - private Display getDisplay() { - Iterator iter= fListeners.iterator(); - while (iter.hasNext()) { - Control control= ((Viewer)iter.next()).getControl(); - if (control != null && !control.isDisposed()) { - Display display= control.getDisplay(); - if (display != null && !display.isDisposed()) - return display; - } - } - return null; - } - /** - * Find and return a valid shell - * @return the shell - */ - private Shell getShell() { - return SearchPlugin.getActiveWorkbenchShell(); - } -} - diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java index e29d18a..ab1bd61 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.java @@ -134,7 +134,6 @@ private SearchMessages() { public static String TextSearchPage_searchBinary_label; public static String TextSearchVisitor_filesearch_task_label; public static String TextSearchVisitor_patterntoocomplex0; - public static String TextSearchVisitor_progress_updating_job; public static String TextSearchVisitor_scanning; public static String TextSearchVisitor_error; public static String TextSearchVisitor_canceled; diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties index 458c61c..6486299 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchMessages.properties @@ -106,9 +106,8 @@ TextSearchVisitor_error= File ''{1}'' has been skipped, problem while reading: ( TextSearchVisitor_canceled= Operation Canceled TextSearchVisitor_unsupportedcharset=File ''{1}'' has been skipped: Unsupported encoding ''{0}''. TextSearchVisitor_patterntoocomplex0=Search pattern is too complex. Search canceled. -TextSearchVisitor_progress_updating_job=Search progress polling TextSearchVisitor_filesearch_task_label=Searching for files... -TextSearchVisitor_textsearch_task_label=Searching for pattern ''{0}''... +TextSearchVisitor_textsearch_task_label=Searching ''{0}'' TextSearchVisitor_illegalcharset=File ''{1}'' has been skipped: Illegal encoding ''{0}''. SortDropDownAction_label= S&ort By @@ -210,12 +209,12 @@ ExceptionDialog_seeErrorLogMessage= See error log for details SearchPreferencePage_emphasizePotentialMatches= &Emphasize potential matches SearchPreferencePage_potentialMatchFgColor= &Foreground color for potential matches: SearchPreferencePage_reuseEditor= &Reuse editors to show matches -SearchPreferencePage_bringToFront= &Bring Search view to front after search -SearchPreferencePage_defaultPerspective= Default &perspective for the Search view: +SearchPreferencePage_bringToFront= &Bring 'Search' view to front after search +SearchPreferencePage_defaultPerspective= Default &perspective for the 'Search' view: SearchPreferencePage_textSearchEngine=Text Search Engine to be used: SearchPreferencePage_defaultPerspective_none= None SearchPreferencePage_ignorePotentialMatches= &Ignore potential matches -SearchPreferencePage_rememberLastUsedPage= Remember &last used page in the Search dialog +SearchPreferencePage_rememberLastUsedPage= Remember &last used page in the 'Search' dialog ReplaceAction_label_all= Re&place All... ReplaceAction_title_all=Replace Text Matches diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchPlugin.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchPlugin.java index 022ad03..5d28ef4 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchPlugin.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchPlugin.java @@ -208,20 +208,10 @@ public void start(BundleContext context) throws Exception { @Override public void stop(BundleContext context) throws Exception { InternalSearchUI.shutdown(); - disposeOldSearchManager(); super.stop(context); fgSearchPlugin= null; } - /** - * @deprecated old search - */ - @Deprecated - private void disposeOldSearchManager() { - if (SearchManager.fgDefault != null) - SearchManager.fgDefault.dispose(); - } - /** * @return Returns all search pages contributed to the workbench. */ @@ -248,32 +238,6 @@ public List getEnabledSearchPageDescriptors(String pageId) return enabledDescriptors; } - /** - * @return Returns the help context ID for the Search view - * as provided by the current search page extension. - * - * @since 3.0 - * @deprecated old search - */ - @Deprecated - public String getSearchViewHelpContextId() { - Search currentSearch= SearchManager.getDefault().getCurrentSearch(); - if (currentSearch != null) { - String pageId= currentSearch.getPageId(); - Iterator iter= getSearchPageDescriptors().iterator(); - while (iter.hasNext()) { - SearchPageDescriptor desc= iter.next(); - if (desc.getId().equals(pageId)) { - String helpId= desc.getSearchViewHelpContextId(); - if (helpId == null) - return ISearchHelpContextIds.SEARCH_VIEW; - return desc.getSearchViewHelpContextId(); - } - } - } - return ISearchHelpContextIds.SEARCH_VIEW; - } - /** * Creates all necessary search page nodes. * @param elements the configuration elements diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultLabelProvider.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultLabelProvider.java deleted file mode 100644 index 6ac45c4..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultLabelProvider.java +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.swt.graphics.Image; - -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.LabelProvider; - -import org.eclipse.ui.PlatformUI; - -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * @deprecated old search - */ -@Deprecated -class SearchResultLabelProvider extends LabelProvider { - - private static final String MATCHES_POSTFIX= " " + SearchMessages.SearchResultView_matches + ")"; //$NON-NLS-1$ //$NON-NLS-2$ - - private ILabelProvider fLabelProvider; - - - SearchResultLabelProvider(ILabelProvider provider) { - fLabelProvider= provider; - } - - @Override - public String getText(Object element) { - StringBuilder buf= new StringBuilder(getLabelProvider().getText(element)); - int count= ((ISearchResultViewEntry)element).getMatchCount(); - if (count > 1) { - buf.append(" ("); //$NON-NLS-1$ - buf.append(count); - buf.append(MATCHES_POSTFIX); - } - return buf.toString(); - } - - @Override - public Image getImage(Object element) { - return fLabelProvider.getImage(element); - } - - // Don't dispose since label providers are reused. - @Override - public void dispose() { - } - - ILabelProvider getLabelProvider() { - return fLabelProvider; - } - - @Override - public void addListener(ILabelProviderListener listener) { - super.addListener(listener); - fLabelProvider.addListener(listener); - PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator().addListener(listener); - } - - @Override - public boolean isLabelProperty(Object element, String property) { - return fLabelProvider.isLabelProperty(element, property); - } - - @Override - public void removeListener(ILabelProviderListener listener) { - super.removeListener(listener); - fLabelProvider.removeListener(listener); - PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator().removeListener(listener); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultView.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultView.java deleted file mode 100644 index 0628784..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultView.java +++ /dev/null @@ -1,341 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - * Michael Fraenkel (fraenkel@us.ibm.com) - contributed a fix for: - * o New search view sets incorrect title - * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=60966) - *******************************************************************************/ -package org.eclipse.search.internal.ui; - - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; - -import org.eclipse.core.runtime.Assert; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IResource; - -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IToolBarManager; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.util.IPropertyChangeListener; -import org.eclipse.jface.viewers.IBaseLabelProvider; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ISelection; - -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IMemento; -import org.eclipse.ui.IViewSite; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.part.CellEditorActionHandler; -import org.eclipse.ui.part.ViewPart; - -import org.eclipse.search.ui.IActionGroupFactory; -import org.eclipse.search.ui.IContextMenuContributor; -import org.eclipse.search.ui.IGroupByKeyComputer; -import org.eclipse.search.ui.ISearchResultView; - -/** - * @deprecated old search - */ -@Deprecated -public class SearchResultView extends ViewPart implements ISearchResultView { - - - private static Map fgLabelProviders= new HashMap<>(5); - - private SearchResultViewer fViewer; - private Map fResponse; - private IMemento fMemento; - private IPropertyChangeListener fPropertyChangeListener; - private CellEditorActionHandler fCellEditorActionHandler; - private SelectAllAction fSelectAllAction; - - /* - * Implements method from IViewPart. - */ - @Override - public void init(IViewSite site, IMemento memento) throws PartInitException { - super.init(site, memento); - fMemento= memento; - } - - /* - * Implements method from IViewPart. - */ - @Override - public void saveState(IMemento memento) { - if (fViewer == null) { - // part has not been created - if (fMemento != null) //Keep the old state; - memento.putMemento(fMemento); - return; - } - fViewer.saveState(memento); - } - - /** - * Creates the search list inner viewer. - * @param parent the parent - */ - @Override - public void createPartControl(Composite parent) { - Assert.isTrue(fViewer == null); - fViewer= new SearchResultViewer(this, parent); - if (fMemento != null) - fViewer.restoreState(fMemento); - fMemento= null; - SearchManager.getDefault().addSearchChangeListener(fViewer); - fViewer.init(); - - // Add selectAll action handlers. - fCellEditorActionHandler = new CellEditorActionHandler(getViewSite().getActionBars()); - fSelectAllAction= new SelectAllAction(); - fSelectAllAction.setViewer(fViewer); - fCellEditorActionHandler.setSelectAllAction(fSelectAllAction); - - fillActionBars(getViewSite().getActionBars()); - - fPropertyChangeListener= event -> { - if (SearchPreferencePage.POTENTIAL_MATCH_FG_COLOR.equals(event.getProperty()) || SearchPreferencePage.EMPHASIZE_POTENTIAL_MATCHES.equals(event.getProperty())) - if (fViewer != null) - fViewer.updatedPotentialMatchFgColor(); - }; - - SearchPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(fPropertyChangeListener); - - PlatformUI.getWorkbench().getHelpSystem().setHelp(fViewer.getControl(), SearchPlugin.getDefault().getSearchViewHelpContextId()); - } - - /** - * Returns the search result viewer. - * @return the search result viewer. - */ - public SearchResultViewer getViewer() { - return fViewer; - } - - //---- IWorkbenchPart ------------------------------------------------------ - - - @Override - public void setFocus() { - fViewer.getControl().setFocus(); - } - - @Override - public void dispose() { - if (fViewer != null) { - SearchManager.getDefault().removeSearchChangeListener(fViewer); - fViewer= null; - } - if (fPropertyChangeListener != null) - SearchPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(fPropertyChangeListener); - if (fCellEditorActionHandler != null) { - fCellEditorActionHandler.dispose(); - fCellEditorActionHandler= null; - } - super.dispose(); - } - - @Override - protected void setContentDescription(String title) { - super.setContentDescription(title); - } - - @Override - protected void setTitleToolTip(String text) { - super.setTitleToolTip(text); - } - - //---- Adding Action to Toolbar ------------------------------------------- - - private void fillActionBars(IActionBars actionBars) { - IToolBarManager toolBar= actionBars.getToolBarManager(); - fillToolBar(toolBar); - actionBars.updateActionBars(); - - // Add selectAll action handlers. - actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), fSelectAllAction); - } - - private void fillToolBar(IToolBarManager tbm) { - fViewer.fillToolBar(tbm); - } - - ILabelProvider getLabelProvider(String pageId) { - if (pageId != null) - return fgLabelProviders.get(pageId); - return null; - } - - @Override - public ILabelProvider getLabelProvider() { - if (fViewer == null) - return null; - IBaseLabelProvider labelProvider= fViewer.getLabelProvider(); - if (labelProvider == null) - return null; - - return ((SearchResultLabelProvider)labelProvider).getLabelProvider(); - } - - private void setGotoMarkerAction(final IAction gotoMarkerAction) { - // Make sure we are doing it in the right thread. - getDisplay().syncExec(() -> getViewer().setGotoMarkerAction(gotoMarkerAction)); - } - - - Display getDisplay() { - return fViewer.getControl().getDisplay(); - } - - - //---- ISearchResultView -------------------------------------------------- - - - /* - * Implements method from ISearchResultView - */ - @Override - public ISelection getSelection() { - return fViewer.getSelection(); - } - - /* - * Implements method from ISearchResultView - */ - @Override - public void searchStarted( - IActionGroupFactory groupFactory, - String singularLabel, - String pluralLabelPattern, - ImageDescriptor imageDescriptor, - String pageId, - ILabelProvider labelProvider, - IAction gotoAction, - IGroupByKeyComputer groupByKeyComputer, - IRunnableWithProgress operation) { - - - Assert.isNotNull(pageId); - Assert.isNotNull(pluralLabelPattern); - Assert.isNotNull(gotoAction); - - fResponse= new HashMap<>(500); - setGotoMarkerAction(gotoAction); - - ILabelProvider oldLabelProvider= fgLabelProviders.get(pageId); - if (oldLabelProvider != null) - oldLabelProvider.dispose(); - fgLabelProviders.put(pageId, labelProvider); - - SearchManager.getDefault().addNewSearch( - new Search( - pageId, - singularLabel, - pluralLabelPattern, - null, - imageDescriptor, - fViewer.getGotoMarkerAction(), - groupFactory, - groupByKeyComputer, - operation)); - } - - @Override - public void searchStarted( - String pageId, - String label, - ImageDescriptor imageDescriptor, - IContextMenuContributor contributor, - ILabelProvider labelProvider, - IAction gotoAction, - IGroupByKeyComputer groupByKeyComputer, - IRunnableWithProgress operation) { - - searchStarted(pageId, null, label, imageDescriptor, contributor, labelProvider, gotoAction, groupByKeyComputer, operation); - } - - /* - * Implements method from ISearchResultView - * @deprecated As of build > 20020514 - */ - @Override - public void searchStarted( - String pageId, - String singularLabel, - String pluralLabelPattern, - ImageDescriptor imageDescriptor, - IContextMenuContributor contributor, - ILabelProvider labelProvider, - IAction gotoAction, - IGroupByKeyComputer groupByKeyComputer, - IRunnableWithProgress operation) { - - - Assert.isNotNull(pageId); - Assert.isNotNull(pluralLabelPattern); - Assert.isNotNull(gotoAction); - - fResponse= new HashMap<>(500); - setGotoMarkerAction(gotoAction); - - ILabelProvider oldLabelProvider= fgLabelProviders.get(pageId); - if (oldLabelProvider != null) - oldLabelProvider.dispose(); - fgLabelProviders.put(pageId, labelProvider); - - SearchManager.getDefault().addNewSearch( - new Search( - pageId, - singularLabel, - pluralLabelPattern, - null, - imageDescriptor, - fViewer.getGotoMarkerAction(), - contributor, - groupByKeyComputer, - operation)); - } - - /* - * Implements method from ISearchResultView - */ - @Override - public void addMatch(String description, Object groupByKey, IResource resource, IMarker marker) { - SearchResultViewEntry entry= fResponse.get(groupByKey); - if (entry == null) { - entry= new SearchResultViewEntry(groupByKey, resource); - fResponse.put(groupByKey, entry); - } - entry.add(marker); - } - - - /* - * Implements method from ISearchResultView - */ - @Override - public void searchFinished() { - SearchManager.getDefault().searchFinished(new ArrayList<>(fResponse.values())); - fResponse= null; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntry.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntry.java deleted file mode 100644 index 02fd436..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntry.java +++ /dev/null @@ -1,211 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.PlatformObject; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IResource; - -import org.eclipse.search.ui.ISearchResultViewEntry; -import org.eclipse.search.ui.SearchUI; - -/** - * Represents an entry in the search result view - * @deprecated old search - */ -@Deprecated -public class SearchResultViewEntry extends PlatformObject implements ISearchResultViewEntry { - - private Object fGroupByKey= null; - private IResource fResource= null; - private IMarker fMarker= null; - private ArrayList fMarkers= null; - private ArrayList> fAttributes; - private int fSelectedMarkerIndex; - private long fModificationStamp= IResource.NULL_STAMP; - private String fMarkerType; - - public SearchResultViewEntry(Object groupByKey, IResource resource) { - fGroupByKey= groupByKey; - fResource= resource; - if (fResource != null) - fModificationStamp= fResource.getModificationStamp(); - } - - //---- Accessors ------------------------------------------------ - @Override - public Object getGroupByKey() { - return fGroupByKey; - } - - void setGroupByKey(Object groupByKey) { - fGroupByKey= groupByKey; - } - - @Override - public IResource getResource() { - return fResource; - } - - @Override - public int getMatchCount() { - if (fMarkers != null) - return fMarkers.size(); - if (fMarkers == null && fMarker != null) - return 1; - return 0; - } - - boolean isPotentialMatch() { - if (fMarker != null) - return fMarker.getAttribute(SearchUI.POTENTIAL_MATCH, false); - return false; - } - - List> getAttributesPerMarker() { - if (fAttributes == null) - return new ArrayList<>(0); - return fAttributes; - } - - public long getModificationStamp() { - return fModificationStamp; - } - - void clearMarkerList() { - fMarker= null; - if (fMarkers != null) - fMarkers.clear(); - } - - void add(IMarker marker) { - if (marker != null && fMarkerType == null) { - try { - fMarkerType= marker.getType(); - } catch (CoreException ex) { - // will default to org.eclipse.search.searchmarker - } - } - - if (fMarker == null) { - fMarker= marker; - if (fMarkers != null) - fMarkers.add(marker); - return; - } - if (fMarkers == null) { - fMarkers= new ArrayList<>(10); - addByStartpos(fMarkers, fMarker); - } - addByStartpos(fMarkers, marker); - } - - void setSelectedMarkerIndex(int index) { - fSelectedMarkerIndex= index; - } - - @Override - public IMarker getSelectedMarker() { - fSelectedMarkerIndex= Math.min(fSelectedMarkerIndex, getMatchCount() - 1); - if (fMarkers == null && fMarker == null) - return null; - if (fMarkers != null && fSelectedMarkerIndex >= 0) - return fMarkers.get(fSelectedMarkerIndex); - return fMarker; - } - - public List getMarkers() { - if (fMarkers == null && fMarker == null) - return new ArrayList<>(0); - else if (fMarkers == null && fMarker != null) { - List markers= new ArrayList<>(1); - markers.add(fMarker); - return markers; - } - return fMarkers; - } - - String getMarkerType() { - if (fMarkerType == null) - return SearchUI.SEARCH_MARKER; - return fMarkerType; - } - - boolean contains(IMarker marker) { - if (fMarkers == null && fMarker == null) - return false; - if (fMarkers == null) - return fMarker.equals(marker); - return fMarkers.contains(marker); - } - - void remove(IMarker marker) { - if (marker == null) - return; - - if (fMarkers == null) { - if (fMarker != null && fMarker.equals(marker)) - fMarker= null; - } - else { - fMarkers.remove(marker); - if (fMarkers.size() == 1) { - fMarker= fMarkers.get(0); - fMarkers= null; - } - } - } - - void backupMarkers() { - if (fResource != null) - fModificationStamp= fResource.getModificationStamp(); - List markers= getMarkers(); - fAttributes= new ArrayList<>(markers.size()); - Iterator iter= markers.iterator(); - while (iter.hasNext()) { - IMarker marker= iter.next(); - Map attributes= null; - try { - attributes= marker.getAttributes(); - } catch (CoreException ex) { - // don't backup corrupt marker - continue; - } - fAttributes.add(attributes); - } - } - - private void addByStartpos(ArrayList markers, IMarker marker) { - int startPos= marker.getAttribute(IMarker.CHAR_START, -1); - int i= 0; - int markerCount= markers.size(); - while (i < markerCount && startPos >= markers.get(i).getAttribute(IMarker.CHAR_START, -1)) - i++; - markers.add(i, marker); - if (i == 0) - fMarker= marker; - } - - @Override - public T getAdapter(Class adapter) { - return super.getAdapter(adapter); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntryAdapterFactory.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntryAdapterFactory.java deleted file mode 100644 index f82258e..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewEntryAdapterFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ - -package org.eclipse.search.internal.ui; - - -import org.eclipse.core.runtime.IAdapterFactory; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IResource; - -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * Implements basic UI support for Java elements. - * Implements handle to persistent support for Java elements. - * @deprecated old search - */ -@Deprecated -public class SearchResultViewEntryAdapterFactory implements IAdapterFactory { - - private static Class[] PROPERTIES= new Class[] { - IResource.class, IMarker.class, - }; - - - @Override - public Class[] getAdapterList() { - return PROPERTIES; - } - - @SuppressWarnings("unchecked") - @Override - public T getAdapter(Object element, Class key) { - - ISearchResultViewEntry entry= (ISearchResultViewEntry) element; - - if (IMarker.class.equals(key)) { - return (T) entry.getSelectedMarker(); - } - if (IResource.class.equals(key)) { - IResource resource= entry.getResource(); - /* - * This is a trick to filter out dummy markers that - * have been attached to a project because there is no - * corresponding resource in the workspace. - */ - int type= resource.getType(); - if (type != IResource.PROJECT && type != IResource.ROOT) - return (T) resource; - } - return null; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewer.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewer.java deleted file mode 100644 index 5817e22..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchResultViewer.java +++ /dev/null @@ -1,721 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - * Michael Fraenkel (fraenkel@us.ibm.com) - contributed a fix for: - * o New search view sets incorrect title - * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=60966) - *******************************************************************************/ -package org.eclipse.search.internal.ui; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.KeyAdapter; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Item; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Widget; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IPath; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IResource; - -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IToolBarManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.IBaseLabelProvider; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.LabelProviderChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; - -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IMemento; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.actions.ActionContext; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.actions.ActionGroup; - -import org.eclipse.search.internal.ui.util.FileLabelProvider; -import org.eclipse.search.ui.IActionGroupFactory; -import org.eclipse.search.ui.IContextMenuConstants; -import org.eclipse.search.ui.IContextMenuContributor; -import org.eclipse.search.ui.ISearchResultViewEntry; -import org.eclipse.search.ui.SearchUI; - - -/** - * A special viewer to present search results. The viewer implements an - * optimized adding and removing strategy. Furthermore it manages - * contributions for search result types. For example the viewer's context - * menu differs if the search result has been generated by a text or - * a java search. - * @deprecated old search - */ -@Deprecated -public class SearchResultViewer extends TableViewer { - - private SearchResultView fOuterPart; - private ShowNextResultAction fShowNextResultAction; - private ShowPreviousResultAction fShowPreviousResultAction; - private GotoMarkerAction fGotoMarkerActionProxy; - private SearchAgainAction fSearchAgainAction; - private RemoveResultAction fRemoveSelectedMatchesAction; - private RemoveAllResultsAction fRemoveAllResultsAction; - private SortDropDownAction fSortDropDownAction; - private SearchDropDownAction fSearchDropDownAction; - private CopyToClipboardAction fCopyToClipboardAction; - private int fMarkerToShow; - private boolean fHandleSelectionChangedEvents= true; - private ISelection fLastSelection; - private boolean fCurrentMatchRemoved= false; - private Color fPotentialMatchFgColor; - private ActionGroup fActionGroup; - private IContextMenuContributor fContextMenuContributor; - private IAction fGotoMarkerAction; - - private ResourceToItemsMapper fResourceToItemsMapper; - private String fCurrentPageId= null; - - public SearchResultViewer(SearchResultView outerPart, Composite parent) { - super(new Table(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION)); - - fResourceToItemsMapper= new ResourceToItemsMapper(this); - - fOuterPart= outerPart; - Assert.isNotNull(fOuterPart); - - if (SearchPreferencePage.arePotentialMatchesEmphasized()) - fPotentialMatchFgColor= new Color(SearchPlugin.getActiveWorkbenchShell().getDisplay(), SearchPreferencePage.getPotentialMatchForegroundColor()); - - setUseHashlookup(true); - setContentProvider(ArrayContentProvider.getInstance()); - - ILabelProvider labelProvider= new SearchResultLabelProvider(new FileLabelProvider(FileLabelProvider.SHOW_LABEL)); - setLabelProvider(labelProvider); - - Search currentSearch= SearchManager.getDefault().getCurrentSearch(); - boolean hasSearch= currentSearch != null; - boolean hasSearchOperation= currentSearch != null && currentSearch.getOperation() != null; - - fShowNextResultAction= new ShowNextResultAction(this); - fShowNextResultAction.setEnabled(false); - fShowPreviousResultAction= new ShowPreviousResultAction(this); - fShowPreviousResultAction.setEnabled(false); - fGotoMarkerActionProxy= new GotoMarkerAction(this); - fGotoMarkerActionProxy.setEnabled(false); - fRemoveSelectedMatchesAction= new RemoveResultAction(this, false); - fRemoveSelectedMatchesAction.setEnabled(false); - fRemoveAllResultsAction= new RemoveAllResultsAction(); - fRemoveAllResultsAction.setEnabled(false); - fSearchAgainAction= new SearchAgainAction(); - fSearchAgainAction.setEnabled(hasSearchOperation); - fSortDropDownAction = new SortDropDownAction(this); - fSortDropDownAction.setEnabled(getItemCount() > 0); - fSearchDropDownAction= new SearchDropDownAction(); - fSearchDropDownAction.setEnabled(hasSearch); - fCopyToClipboardAction= new CopyToClipboardAction(this); - - addSelectionChangedListener( - event -> { - if (fLastSelection == null || !fLastSelection.equals(event.getSelection())) { - fLastSelection= event.getSelection(); - handleSelectionChanged(); - } - } - ); - - addOpenListener(event -> showResult()); - - MenuManager menuMgr= new MenuManager("#PopUp"); //$NON-NLS-1$ - menuMgr.setRemoveAllWhenShown(true); - menuMgr.addMenuListener( - mgr -> { - SearchPlugin.createStandardGroups(mgr); - fillContextMenu(mgr); - }); - Menu menu= menuMgr.createContextMenu(getTable()); - getTable().setMenu(menu); - - // Register menu - fOuterPart.getSite().registerContextMenu(menuMgr, this); - - IActionBars actionBars= fOuterPart.getViewSite().getActionBars(); - if (actionBars != null) { - actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fShowNextResultAction); - actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fShowPreviousResultAction); - } - - fOuterPart.getSite().setSelectionProvider(this); - } - - void init() { - Search search= SearchManager.getDefault().getCurrentSearch(); - if (search != null) { - setGotoMarkerAction(search.getGotoMarkerAction()); - setContextMenuTarget(search.getContextMenuContributor()); - setActionGroupFactory(null); - setActionGroupFactory(search.getActionGroupFactory()); - setPageId(search.getPageId()); - setInput(search.getResults()); - } - } - - @Override - protected void doUpdateItem(Widget item, Object element, boolean fullMap) { - super.doUpdateItem(item, element, fullMap); - if (((SearchResultViewEntry)element).isPotentialMatch()) { - TableItem ti = (TableItem) item; - ti.setForeground(fPotentialMatchFgColor); - } - } - - private void handleSelectionChanged() { - int selectionCount= getSelectedEntriesCount(); - boolean hasSingleSelection= selectionCount == 1; - boolean hasElements= getItemCount() > 0; - fShowNextResultAction.setEnabled(hasSingleSelection || (hasElements && selectionCount == 0)); - fShowPreviousResultAction.setEnabled(hasSingleSelection || (hasElements && selectionCount == 0)); - fGotoMarkerActionProxy.setEnabled(hasSingleSelection); - fRemoveSelectedMatchesAction.setEnabled(selectionCount > 0); - - if (fHandleSelectionChangedEvents) { - fMarkerToShow= -1; - fCurrentMatchRemoved= false; - } else - fHandleSelectionChangedEvents= true; - - updateStatusLine(); - } - - void updateStatusLine() { - boolean hasSingleSelection= getSelectedEntriesCount() == 1; - String location= ""; //$NON-NLS-1$ - if (hasSingleSelection) { - ISearchResultViewEntry entry= (ISearchResultViewEntry)getTable().getItem(getTable().getSelectionIndex()).getData(); - IPath path= entry.getResource().getFullPath(); - if (path != null) - location= path.makeRelative().toString(); - } - setStatusLineMessage(location); - } - - void enableActions() { - /* - * Note: The check before each set operation reduces flickering - */ - boolean state= getItemCount() > 0; - if (state != fShowNextResultAction.isEnabled()) - fShowNextResultAction.setEnabled(state); - if (state != fShowPreviousResultAction.isEnabled()) - fShowPreviousResultAction.setEnabled(state); - if (state != fSortDropDownAction.isEnabled()) - fSortDropDownAction.setEnabled(state); - if (state != fRemoveAllResultsAction.isEnabled()) - fRemoveAllResultsAction.setEnabled(state); - - Search currentSearch= SearchManager.getDefault().getCurrentSearch(); - state= currentSearch != null; - boolean operationState= currentSearch != null && currentSearch.getOperation() != null; - if (state != fSearchDropDownAction.isEnabled()) - fSearchDropDownAction.setEnabled(state); - if (operationState != fSearchAgainAction.isEnabled()) - fSearchAgainAction.setEnabled(operationState); - - state= !getSelection().isEmpty(); - if (state != fGotoMarkerActionProxy.isEnabled()) - fGotoMarkerActionProxy.setEnabled(state); - if (state != fRemoveSelectedMatchesAction.isEnabled()) - fRemoveSelectedMatchesAction.setEnabled(state); - } - - - @Override - protected void inputChanged(Object input, Object oldInput) { - fLastSelection= null; - getTable().removeAll(); - super.inputChanged(input, oldInput); - fMarkerToShow= -1; - fCurrentMatchRemoved= false; - updateTitle(); - enableActions(); - if (getItemCount() > 0) - selectResult(0); - - PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), SearchPlugin.getDefault().getSearchViewHelpContextId()); - } - - protected int getSelectedEntriesCount() { - ISelection s= getSelection(); - if (s == null || s.isEmpty() || !(s instanceof IStructuredSelection)) - return 0; - IStructuredSelection selection= (IStructuredSelection)s; - return selection.size(); - } - - //--- Contribution management ----------------------------------------------- - - - protected boolean enableRemoveMatchMenuItem() { - if (getSelectedEntriesCount() != 1) - return false; - Table table= getTable(); - int index= table.getSelectionIndex(); - SearchResultViewEntry entry= null; - if (index > -1) - entry= (SearchResultViewEntry)table.getItem(index).getData(); - return (entry != null && entry.getMatchCount() > 1); - - } - - void fillContextMenu(IMenuManager menu) { - ISelection selection= getSelection(); - - if (fActionGroup != null) { - ActionContext context= new ActionContext(selection); - context.setInput(getInput()); - fActionGroup.setContext(context); - fActionGroup.fillContextMenu(menu); - fActionGroup.setContext(null); - } - - if (fContextMenuContributor != null) - fContextMenuContributor.fill(menu, this); - - if (!selection.isEmpty()) { - menu.appendToGroup(IContextMenuConstants.GROUP_REORGANIZE, fCopyToClipboardAction); - menu.appendToGroup(IContextMenuConstants.GROUP_GOTO, fGotoMarkerActionProxy); - if (enableRemoveMatchMenuItem()) - menu.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, new RemoveMatchAction(this)); - menu.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, new RemoveResultAction(this, true)); - - if (isPotentialMatchSelected()) - menu.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, new RemovePotentialMatchesAction(fOuterPart.getViewSite())); - } - - // If we have elements - if (getItemCount() > 0) - menu.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, new RemoveAllResultsAction()); - - menu.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fSearchAgainAction); - if (getItemCount() > 0) { - fSortDropDownAction= fSortDropDownAction.renew(); - if (fSortDropDownAction.getSorterCount() > 1) - menu.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fSortDropDownAction); - } - } - - private boolean isPotentialMatchSelected() { - if (getSelectedEntriesCount() == 0) - return false; - - Iterator iter= Collections.emptyList().iterator(); - ISelection selection= getSelection(); - if (selection instanceof IStructuredSelection) - iter= ((IStructuredSelection)selection).iterator(); - - while (iter.hasNext()) { - Object entry= iter.next(); - if (entry instanceof ISearchResultViewEntry) { - IMarker marker= ((ISearchResultViewEntry)entry).getSelectedMarker(); - if (marker != null && marker.getAttribute(SearchUI.POTENTIAL_MATCH, false)) - return true; - } - } - - return false; - } - - IAction getGotoMarkerAction() { - // null as return value is covered (no action will take place) - return fGotoMarkerAction; - } - - void setGotoMarkerAction(IAction gotoMarkerAction) { - fGotoMarkerAction= gotoMarkerAction; - } - - - void setContextMenuTarget(IContextMenuContributor contributor) { - fContextMenuContributor= contributor; - } - - void setActionGroupFactory(IActionGroupFactory groupFactory) { - IActionBars actionBars= fOuterPart.getViewSite().getActionBars(); - if (fActionGroup != null) { - fActionGroup.dispose(); - fActionGroup= null; - } - - if (groupFactory != null) { - fActionGroup= groupFactory.createActionGroup(fOuterPart); - if (actionBars != null) - fActionGroup.fillActionBars(actionBars); - } - if (actionBars != null) - actionBars.updateActionBars(); - } - - void setPageId(String pageId) { - if (fCurrentPageId != null && fCurrentPageId.equals(pageId)) - return; - - fCurrentPageId= pageId; - ILabelProvider labelProvider= fOuterPart.getLabelProvider(pageId); - if (labelProvider != null) - internalSetLabelProvider(labelProvider); - fSortDropDownAction.setPageId(pageId); - } - - void fillToolBar(IToolBarManager tbm) { - tbm.add(fShowNextResultAction); - tbm.add(fShowPreviousResultAction); -// tbm.add(fGotoMarkerAction); see bug 15275 - tbm.add(fRemoveSelectedMatchesAction); - tbm.add(fRemoveAllResultsAction); - tbm.add(new Separator()); - tbm.add(new OpenSearchDialogAction()); - tbm.add(fSearchDropDownAction); - - // need to hook F5 to table - getTable().addKeyListener(new KeyAdapter() { - @Override - public void keyReleased(KeyEvent e) { - if (e.keyCode == SWT.F5) { - fSearchAgainAction.run(); - return; // performance - } - if (e.character == SWT.DEL) { - new RemoveResultAction(SearchResultViewer.this, true).run(); - return; // performance - } - } - }); - } - - int getItemCount() { - return SearchManager.getDefault().getCurrentItemCount(); - } - - void internalSetLabelProvider(ILabelProvider provider) { - setLabelProvider(new SearchResultLabelProvider(provider)); - } - - /** - * Makes the first marker of the current result entry - * visible in an editor. If no result - * is visible, this method does nothing. - */ - public void showResult() { - Table table= getTable(); - if (!canDoShowResult(table)) - return; - - - int index= table.getSelectionIndex(); - if (index < 0) - return; - SearchResultViewEntry entry= (SearchResultViewEntry)getTable().getItem(index).getData(); - - - fMarkerToShow= 0; - fCurrentMatchRemoved= false; - entry.setSelectedMarkerIndex(0); - openCurrentSelection(); - } - - - /** - * Makes the next result (marker) visible in an editor. If no result - * is visible, this method makes the first result visible. - */ - public void showNextResult() { - Table table= getTable(); - if (!canDoShowResult(table)) - return; - - int index= table.getSelectionIndex(); - SearchResultViewEntry entry= null; - if (index > -1) - entry= (SearchResultViewEntry)table.getItem(index).getData(); - - if (fCurrentMatchRemoved) - fCurrentMatchRemoved= false; - else - fMarkerToShow++; - if (entry == null || fMarkerToShow >= entry.getMatchCount()) { - // move selection - if (index == -1) { - index= 0; - } else { - index++; - if (index >= table.getItemCount()) - index= 0; - } - fMarkerToShow= 0; - entry= (SearchResultViewEntry)getTable().getItem(index).getData(); - selectResult(index); - } - entry.setSelectedMarkerIndex(fMarkerToShow); - openCurrentSelection(); - updateStatusLine(); - } - - - /** - * Makes the previous result (marker) visible. If there isn't any - * visible result, this method makes the last result visible. - */ - public void showPreviousResult() { - fCurrentMatchRemoved= false; - Table table= getTable(); - if (!canDoShowResult(table)) - return; - - int index= table.getSelectionIndex(); - SearchResultViewEntry entry; - - - fMarkerToShow--; - if (fMarkerToShow >= 0) - entry= (SearchResultViewEntry)getTable().getItem(getTable().getSelectionIndex()).getData(); - else { - // move selection - int count= table.getItemCount(); - if (index == -1) { - index= count - 1; - } else { - index--; - if (index < 0) - index= count - 1; - } - entry= (SearchResultViewEntry)getTable().getItem(index).getData(); - fMarkerToShow= entry.getMatchCount() - 1; - selectResult(index); - } - entry.setSelectedMarkerIndex(fMarkerToShow); - openCurrentSelection(); - updateStatusLine(); - } - - private boolean canDoShowResult(Table table) { - if (table == null || getItemCount() == 0) - return false; - return true; - } - - private void selectResult(int index) { - fHandleSelectionChangedEvents= false; - Object element= getElementAt(index); - if (element != null) - setSelection(new StructuredSelection(getElementAt(index)), true); - else - setSelection(StructuredSelection.EMPTY); - } - - private void openCurrentSelection() { - IAction action= getGotoMarkerAction(); - if (action != null) - action.run(); - } - - /** - * Updates the foreground color for potential matches. - */ - void updatedPotentialMatchFgColor() { - fPotentialMatchFgColor= null; - if (SearchPreferencePage.arePotentialMatchesEmphasized()) - fPotentialMatchFgColor= new Color(SearchPlugin.getActiveWorkbenchShell().getDisplay(), SearchPreferencePage.getPotentialMatchForegroundColor()); - refresh(); - } - - /** - * Update the title - */ - protected void updateTitle() { - boolean hasCurrentSearch= SearchManager.getDefault().getCurrentSearch() != null; - String title; - if (hasCurrentSearch) { - String description= SearchManager.getDefault().getCurrentSearch().getFullDescription(); - title= Messages.format(SearchMessages.SearchResultView_titleWithDescription, description); - } else - title= SearchMessages.SearchResultView_title; - if (title == null || !title.equals(fOuterPart.getContentDescription())) - fOuterPart.setContentDescription(title); - } - - /** - * Clear the title - */ - protected void clearTitle() { - String title= SearchMessages.SearchResultView_title; - if (!title.equals(fOuterPart.getContentDescription())) - fOuterPart.setContentDescription(title); - } - - /** - * Sets the message text to be displayed on the status line. - * The image on the status line is cleared. - * @param message the message - */ - private void setStatusLineMessage(String message) { - fOuterPart.getViewSite().getActionBars().getStatusLineManager().setMessage(message); - } - - - @Override - protected void handleDispose(DisposeEvent event) { - fLastSelection= null; - Menu menu= getTable().getMenu(); - if (menu != null) - menu.dispose(); - if (fPotentialMatchFgColor != null) - fPotentialMatchFgColor.dispose(); - if (fActionGroup != null) { - fActionGroup.dispose(); - fActionGroup= null; - } - super.handleDispose(event); - } - - //--- Change event handling ------------------------------------------------- - - /** - * Handle a single add. - * @param entry the entry to add - */ - protected void handleAddMatch(ISearchResultViewEntry entry) { - insert(entry, -1); - } - - /** - * Handle a single remove. - * @param entry the entry to remove - */ - protected void handleRemoveMatch(ISearchResultViewEntry entry) { - Widget item= findItem(entry); - if (entry.getMatchCount() == 0) - remove(entry); - else - updateItem(item, entry); - updateStatusLine(); - } - - /** - * Handle remove all. - */ - protected void handleRemoveAll() { - setContextMenuTarget(null); - setActionGroupFactory(null); - setInput(null); - } - - /** - * Handle an update of an entry. - * - * @param entry the entry - * @param matchRemoved true if a match got removed - */ - protected void handleUpdateMatch(ISearchResultViewEntry entry, boolean matchRemoved) { - Widget item= findItem(entry); - updateItem(item, entry); - if (matchRemoved && getSelectionFromWidget().contains(entry)) - fCurrentMatchRemoved= true; - } - - //--- Persistency ------------------------------------------------- - - void restoreState(IMemento memento) { - fSortDropDownAction.restoreState(memento); - } - - void saveState(IMemento memento) { - fSortDropDownAction.saveState(memento); - } - - @Override - protected void handleLabelProviderChanged(LabelProviderChangedEvent event) { - Object[] changed= event.getElements(); - if (changed != null && !fResourceToItemsMapper.isEmpty()) { - ArrayList others= new ArrayList<>(changed.length); - for (Object curr : changed) { - if (curr instanceof IResource) - fResourceToItemsMapper.resourceChanged((IResource) curr); - else if (curr instanceof IAdaptable) { - IResource resource= ((IAdaptable)curr).getAdapter(IResource.class); - if (resource != null) - fResourceToItemsMapper.resourceChanged(resource); - } else - others.add(curr); - } - if (others.isEmpty()) { - return; - } - event= new LabelProviderChangedEvent((IBaseLabelProvider) event.getSource(), others.toArray()); - } - super.handleLabelProviderChanged(event); - } - - @Override - protected void mapElement(Object element, Widget item) { - super.mapElement(element, item); - if (item instanceof Item) { - fResourceToItemsMapper.addToMap(element, (Item)item); - } - } - - @Override - protected void unmapElement(Object element, Widget item) { - if (item instanceof Item) { - fResourceToItemsMapper.removeFromMap(element, (Item)item); - } - super.unmapElement(element, item); - } - - @Override - protected void unmapAllElements() { - fResourceToItemsMapper.clearMap(); - super.unmapAllElements(); - } - - @Override - protected void internalRefresh(Object element, boolean updateLabels) { - // see bug 44891 - getTable().setRedraw(false); - super.internalRefresh(element, updateLabels); - getTable().setRedraw(true); - } - - void handleAllSearchesRemoved() { - setContextMenuTarget(null); - setActionGroupFactory(null); - setInput(null); - fSearchDropDownAction.clear(); - } - -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowNextResultAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowNextResultAction.java deleted file mode 100644 index 43f566a..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowNextResultAction.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -import org.eclipse.ui.IWorkbenchCommandConstants; - -/** - * @deprecated old search - */ -@Deprecated -class ShowNextResultAction extends Action { - - private SearchResultViewer fViewer; - - public ShowNextResultAction(SearchResultViewer viewer) { - super(SearchMessages.SearchResultView_showNext_text); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_NEXT); - setToolTipText(SearchMessages.SearchResultView_showNext_tooltip); - fViewer= viewer; - setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_NEXT); - } - - @Override - public void run() { - fViewer.showNextResult(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowPreviousResultAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowPreviousResultAction.java deleted file mode 100644 index 9b461d4..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowPreviousResultAction.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -import org.eclipse.ui.IWorkbenchCommandConstants; - -/** - * @deprecated old search - */ -@Deprecated -class ShowPreviousResultAction extends Action { - - private SearchResultViewer fViewer; - - public ShowPreviousResultAction(SearchResultViewer viewer) { - super(SearchMessages.SearchResultView_showPrev_text); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_PREV); - setToolTipText(SearchMessages.SearchResultView_showPrev_tooltip); - setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_PREVIOUS); - fViewer= viewer; - } - - @Override - public void run() { - fViewer.showPreviousResult(); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchAction.java deleted file mode 100644 index ef92309..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchAction.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import org.eclipse.jface.action.Action; - -/** - * @deprecated old search - */ -@Deprecated -class ShowSearchAction extends Action { - private Search fSearch; - - /** - * Create a new instance of this class. - * - * @param search the search - */ - public ShowSearchAction(Search search) { - fSearch= search; - String desc= search.getShortDescription(); - setText(desc); - setToolTipText(desc); - setImageDescriptor(search.getImageDescriptor()); - } - /** - * Invoke the resource wizard selection wizard - */ - @Override - public void run() { - if (fSearch != SearchManager.getDefault().getCurrentSearch()) - SearchManager.getDefault().setCurrentSearch(fSearch); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchesAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchesAction.java deleted file mode 100644 index 16889b7..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/ShowSearchesAction.java +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2009 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import org.eclipse.swt.graphics.Image; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.window.Window; - -import org.eclipse.ui.dialogs.ListDialog; - - -/** - * Invoke the resource creation wizard selection Wizard. - * This action will retarget to the active view. - * @deprecated old search - */ -@Deprecated -class ShowSearchesAction extends Action { - - private static final class SearchesLabelProvider extends LabelProvider { - - private ArrayList fImages= new ArrayList<>(); - - @Override - public String getText(Object element) { - if (!(element instanceof ShowSearchAction)) - return ""; //$NON-NLS-1$ - return ((ShowSearchAction)element).getText(); - } - @Override - public Image getImage(Object element) { - if (!(element instanceof ShowSearchAction)) - return null; - - ImageDescriptor imageDescriptor= ((ShowSearchAction)element).getImageDescriptor(); - if (imageDescriptor == null) - return null; - - Image image= imageDescriptor.createImage(); - fImages.add(image); - - return image; - } - - @Override - public void dispose() { - Iterator iter= fImages.iterator(); - while (iter.hasNext()) - iter.next().dispose(); - - fImages= null; - } - } - - /** - * Create a new instance of this class - */ - public ShowSearchesAction() { - super(SearchMessages.ShowOtherSearchesAction_label); - setToolTipText(SearchMessages.ShowOtherSearchesAction_tooltip); - } - /* - * Overrides method from Action - */ - @Override - public void run() { - run(false); - } - - public void run(boolean showAll) { - Iterator iter= SearchManager.getDefault().getPreviousSearches().iterator(); - int cutOffSize; - if (showAll) - cutOffSize= 0; - else - cutOffSize= SearchDropDownAction.RESULTS_IN_DROP_DOWN; - int size= SearchManager.getDefault().getPreviousSearches().size() - cutOffSize; - Search selectedSearch= SearchManager.getDefault().getCurrentSearch(); - Action selectedAction = null; - ArrayList input= new ArrayList<>(size); - int i= 0; - while (iter.hasNext()) { - Search search= iter.next(); - if (i++ < cutOffSize) - continue; - Action action= new ShowSearchAction(search); - input.add(action); - if (selectedSearch == search) - selectedAction= action; - } - - // Open a list dialog. - String title; - String message; - if (showAll) { - title= SearchMessages.PreviousSearchesDialog_title; - message= SearchMessages.PreviousSearchesDialog_message; - } - else { - title= SearchMessages.OtherSearchesDialog_title; - message= SearchMessages.OtherSearchesDialog_message; - } - - LabelProvider labelProvider=new SearchesLabelProvider(); - - ListDialog dlg= new ListDialog(SearchPlugin.getActiveWorkbenchShell()); - dlg.setInput(input); - dlg.setTitle(title); - dlg.setContentProvider(ArrayContentProvider.getInstance()); - dlg.setLabelProvider(labelProvider); - dlg.setMessage(message); - if (selectedAction != null) { - Object[] selected= new Object[1]; - selected[0]= selectedAction; - dlg.setInitialSelections(selected); - } - if (dlg.open() == Window.OK) { - List result= Arrays.asList(dlg.getResult()); - if (result != null && result.size() == 1) { - ((ShowSearchAction)result.get(0)).run(); - } - } - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/SortDropDownAction.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/SortDropDownAction.java deleted file mode 100644 index 6d843df..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/SortDropDownAction.java +++ /dev/null @@ -1,226 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; - -import org.eclipse.swt.custom.BusyIndicator; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Menu; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ActionContributionItem; -import org.eclipse.jface.action.IMenuCreator; -import org.eclipse.jface.viewers.ViewerSorter; - -import org.eclipse.ui.IMemento; -import org.eclipse.ui.model.WorkbenchViewerSorter; - -/** - * Drop down action that holds the currently registered sort actions. - * @deprecated old search - */ -@Deprecated -class SortDropDownAction extends Action implements IMenuCreator { - - // Persistance tags. - private static final String TAG_SORTERS= "sorters"; //$NON-NLS-1$ - private static final String TAG_DEFAULT_SORTERS= "defaultSorters"; //$NON-NLS-1$ - private static final String TAG_ELEMENT= "element"; //$NON-NLS-1$ - private static final String TAG_PAGE_ID= "pageId"; //$NON-NLS-1$ - private static final String TAG_SORTER_ID= "sorterId"; //$NON-NLS-1$ - - private static Map fgLastCheckedForType= new HashMap<>(5); - - private SearchResultViewer fViewer; - private String fPageId; - private Menu fMenu; - private Map fLastCheckedForType; - - public SortDropDownAction(SearchResultViewer viewer) { - super(SearchMessages.SortDropDownAction_label); - SearchPluginImages.setImageDescriptors(this, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_SORT); - fViewer= viewer; - setToolTipText(SearchMessages.SortDropDownAction_tooltip); - setMenuCreator(this); - fLastCheckedForType= new HashMap<>(5); - } - - @Override - public void dispose() { - if (fMenu != null && !fMenu.isDisposed()) - fMenu.dispose(); - fMenu= null; - } - - @Override - public Menu getMenu(Control parent) { - return null; - } - - void setPageId(String pageId) { - fPageId= pageId; - SorterDescriptor sorterDesc= fLastCheckedForType.get(pageId); - if (sorterDesc == null) - sorterDesc= fgLastCheckedForType.get(pageId); - if (sorterDesc == null) - sorterDesc= findSorter(fPageId); - if (sorterDesc != null) { - setChecked(sorterDesc); - fViewer.setSorter(sorterDesc.createObject()); - } else { - // Use default sort workbench viewer sorter - fViewer.setSorter(new WorkbenchViewerSorter()); - } - } - - @Override - public Menu getMenu(final Menu parent) { - dispose(); // ensure old menu gets disposed - - fMenu= new Menu(parent); - - Iterator iter= SearchPlugin.getDefault().getSorterDescriptors().iterator(); - while (iter.hasNext()) { - Object value= fLastCheckedForType.get(fPageId); - final String checkedId; - if (value instanceof SorterDescriptor) - checkedId= ((SorterDescriptor)value).getId(); - else - checkedId= ""; //$NON-NLS-1$ - - final SorterDescriptor sorterDesc= iter.next(); - if (!sorterDesc.getPageId().equals(fPageId) && !sorterDesc.getPageId().equals("*")) //$NON-NLS-1$ - continue; - final ViewerSorter sorter= sorterDesc.createObject(); - if (sorter != null) { - final Action action= new Action() { - @Override - public void run() { - if (!checkedId.equals(sorterDesc.getId())) { - SortDropDownAction.this.setChecked(sorterDesc); - BusyIndicator.showWhile(parent.getDisplay(), () -> fViewer.setSorter(sorter)); - } - } - }; - action.setText(sorterDesc.getLabel()); - action.setImageDescriptor(sorterDesc.getImage()); - action.setToolTipText(sorterDesc.getToolTipText()); - action.setChecked(checkedId.equals(sorterDesc.getId())); - addActionToMenu(fMenu, action); - } - } - return fMenu; - } - - protected void addActionToMenu(Menu parent, Action action) { - ActionContributionItem item= new ActionContributionItem(action); - item.fill(parent, -1); - } - - @Override - public void run() { - // nothing to do - } - - private SorterDescriptor findSorter(String pageId) { - Iterator iter= SearchPlugin.getDefault().getSorterDescriptors().iterator(); - while (iter.hasNext()) { - SorterDescriptor sorterDesc= iter.next(); - if (sorterDesc.getPageId().equals(pageId) || sorterDesc.getPageId().equals("*")) //$NON-NLS-1$ - return sorterDesc; - } - return null; - } - - private SorterDescriptor getSorter(String sorterId) { - Iterator iter= SearchPlugin.getDefault().getSorterDescriptors().iterator(); - while (iter.hasNext()) { - SorterDescriptor sorterDesc= iter.next(); - if (sorterDesc.getId().equals(sorterId)) - return sorterDesc; - } - return null; - } - - private void setChecked(SorterDescriptor sorterDesc) { - fLastCheckedForType.put(fPageId, sorterDesc); - fgLastCheckedForType.put(fPageId, sorterDesc); - } - - /** - * Disposes this action's menu and returns a new unused instance. - * @return the action - */ - SortDropDownAction renew() { - SortDropDownAction action= new SortDropDownAction(fViewer); - action.fLastCheckedForType= fLastCheckedForType; - action.fPageId= fPageId; - dispose(); - return action; - } - - //--- Persistency ------------------------------------------------- - - void restoreState(IMemento memento) { - if (fLastCheckedForType.isEmpty()) - restoreState(memento, fLastCheckedForType, TAG_SORTERS); - if (fgLastCheckedForType.isEmpty()) - restoreState(memento, fgLastCheckedForType, TAG_DEFAULT_SORTERS); - } - - private void restoreState(IMemento memento, Map map, String mapName) { - memento= memento.getChild(mapName); - if (memento == null) - return; - IMemento[] mementoElements= memento.getChildren(TAG_ELEMENT); - for (IMemento mementoElement : mementoElements) { - String pageId= mementoElement.getString(TAG_PAGE_ID); - String sorterId= mementoElement.getString(TAG_SORTER_ID); - SorterDescriptor sorterDesc= getSorter(sorterId); - if (sorterDesc != null) - map.put(pageId, sorterDesc); - } - } - - void saveState(IMemento memento) { - saveState(memento, fgLastCheckedForType, TAG_DEFAULT_SORTERS); - saveState(memento, fLastCheckedForType, TAG_SORTERS); - } - - private void saveState(IMemento memento, Map map, String mapName) { - Iterator> iter= map.entrySet().iterator(); - memento= memento.createChild(mapName); - while (iter.hasNext()) { - IMemento mementoElement= memento.createChild(TAG_ELEMENT); - Entry entry= iter.next(); - mementoElement.putString(TAG_PAGE_ID, entry.getKey()); - mementoElement.putString(TAG_SORTER_ID, entry.getValue().getId()); - } - } - - int getSorterCount() { - int count= 0; - Iterator iter= SearchPlugin.getDefault().getSorterDescriptors().iterator(); - while (iter.hasNext()) { - SorterDescriptor sorterDesc= iter.next(); - if (sorterDesc.getPageId().equals(fPageId) || sorterDesc.getPageId().equals("*")) //$NON-NLS-1$ - count++; - } - return count; - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchPage.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchPage.java index 232b682..2e22552 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchPage.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchPage.java @@ -17,6 +17,8 @@ package org.eclipse.search.internal.ui.text; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -30,6 +32,11 @@ import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.ITextFileBufferManager; +import org.eclipse.core.filebuffers.LocationKind; + import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.dialogs.ErrorDialog; @@ -224,7 +231,7 @@ private void autoExpand(TreeViewer viewer, Object toExpand) { @Override protected void showMatch(Match match, int offset, int length, boolean activate) throws PartInitException { - IFile file= (IFile) match.getElement(); + IFile file = mostNestedEquivalent((IFile) match.getElement()); IWorkbenchPage page= getSite().getPage(); if (offset >= 0 && length != 0) { openAndSelect(page, file, offset, length, activate); @@ -240,7 +247,7 @@ protected void handleOpen(OpenEvent event) { if (firstElement instanceof IFile) { if (getDisplayedMatchCount(firstElement) == 0) { try { - open(getSite().getPage(), (IFile)firstElement, false); + open(getSite().getPage(), mostNestedEquivalent((IFile) firstElement), false); } catch (PartInitException e) { ErrorDialog.openError(getSite().getShell(), SearchMessages.FileSearchPage_open_file_dialog_title, SearchMessages.FileSearchPage_open_file_failed, e.getStatus()); } @@ -263,6 +270,21 @@ protected void handleOpen(OpenEvent event) { } } + private IFile mostNestedEquivalent(IFile resource) { + if (resource == null || resource.getLocationURI() == null) { + return resource; + } + ITextFileBufferManager textFileBufferManager = FileBuffers.getTextFileBufferManager(); + ITextFileBuffer textFileBuffer = textFileBufferManager.getTextFileBuffer(resource.getFullPath(), + LocationKind.IFILE); + return Arrays.stream(resource.getWorkspace().getRoot().findFilesForLocationURI(resource.getLocationURI())) // + .sorted(Comparator.comparingInt(aFile -> aFile.getFullPath().segments().length)) // + .findFirst() // + .filter(aFile -> textFileBufferManager.getTextFileBuffer(aFile.getFullPath(), + LocationKind.IFILE) == textFileBuffer) + .orElse(resource); + } + @Override protected void fillContextMenu(IMenuManager mgr) { super.fillContextMenu(mgr); @@ -453,7 +475,10 @@ public Match[] getDisplayedMatches(Object element) { protected void evaluateChangedElements(Match[] matches, Set changedElements) { if (showLineMatches()) { for (Match match : matches) { - changedElements.add(((FileMatch) match).getLineElement()); + LineElement lineElement = ((FileMatch) match).getLineElement(); + if (lineElement != null) { + changedElements.add(lineElement); + } } } else { super.evaluateChangedElements(matches, changedElements); diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java index 721662c..a3fe734 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java @@ -19,9 +19,8 @@ package org.eclipse.search.internal.ui.text; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import org.eclipse.core.runtime.CoreException; @@ -55,15 +54,15 @@ private final static class TextSearchResultCollector extends TextSearchRequestor private final boolean fSearchInBinaries; private final boolean fIsLightweightAutoRefresh; - private Map> fCachedMatches; - private Object fLock= new Object(); + private final ConcurrentHashMap> fCachedMatches; + private volatile boolean stop; private TextSearchResultCollector(AbstractTextSearchResult result, boolean isFileSearchOnly, boolean searchInBinaries) { fResult= result; fIsFileSearchOnly= isFileSearchOnly; fSearchInBinaries= searchInBinaries; fIsLightweightAutoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null); - + fCachedMatches = new ConcurrentHashMap<>(); } @Override @@ -77,11 +76,8 @@ public boolean acceptFile(IFile file) throws CoreException { return false; if (fIsFileSearchOnly) { - synchronized (fLock) { - fResult.addMatch(new FileMatch(file)); - } + fResult.addMatch(new FileMatch(file)); } - flushMatches(); return true; } @@ -92,45 +88,23 @@ public boolean reportBinaryFile(IFile file) { @Override public boolean acceptPatternMatch(TextSearchMatchAccess matchRequestor) throws CoreException { - ArrayList matches; - synchronized(fLock) { - // fCachedMatches is set to null when the caller invokes endReporting(), - // indicating that no further results are desired/expected, so discard - // any additional results. - if (fCachedMatches == null) { - return false; - } - matches= fCachedMatches.get(matchRequestor.getFile()); + if (stop) { + return false; } - - int matchOffset= matchRequestor.getMatchOffset(); - - /* - * Another job may call flushCaches() at any time, which will clear the cached matches. - * Any addition of matches to the cache needs to be protected against the flushing of - * the cache by other jobs. It is OK to call getLineElement() with an unprotected local - * reference to the matches for this file, because getLineElement() uses previous matches - * as an optimization when creating new matches but doesn't update the cache directly - * (and because each file is processed by at most one job). - */ - LineElement lineElement= getLineElement(matchOffset, matchRequestor, matches); - if (lineElement != null) { - FileMatch fileMatch= new FileMatch(matchRequestor.getFile(), matchOffset, matchRequestor.getMatchLength(), lineElement); - synchronized(fLock) { - // fCachedMatches is set to null when the caller invokes endReporting(), - // indicating that no further results are desired/expected, so discard - // any additional results. - if (fCachedMatches == null) { - return false; - } - matches= fCachedMatches.get(matchRequestor.getFile()); + fCachedMatches.compute(matchRequestor.getFile(), (f, matches) -> { + // each file is processed by at most one job + int matchOffset = matchRequestor.getMatchOffset(); + LineElement lineElement = getLineElement(matchOffset, matchRequestor, matches); + if (lineElement != null) { + FileMatch fileMatch = new FileMatch(matchRequestor.getFile(), matchOffset, + matchRequestor.getMatchLength(), lineElement); if (matches == null) { - matches= new ArrayList<>(); - fCachedMatches.put(matchRequestor.getFile(), matches); + matches = new ArrayList<>(); } matches.add(fileMatch); } - } + return matches; + }); return true; } @@ -191,28 +165,32 @@ private static String getContents(TextSearchMatchAccess matchRequestor, int star @Override public void beginReporting() { - fCachedMatches= new HashMap<>(); + stop = false; } @Override public void endReporting() { + stop = true; flushMatches(); - synchronized (fLock) { - fCachedMatches= null; + fCachedMatches.clear(); + } + + @Override + public void flushMatches(IFile file) { + List matches = fCachedMatches.remove(file); + if (matches != null && !matches.isEmpty()) { + fResult.addMatches(matches.toArray(new Match[matches.size()])); } } private void flushMatches() { - synchronized (fLock) { - if (fCachedMatches != null && !fCachedMatches.isEmpty()) { - Iterator> it = fCachedMatches.values().iterator(); - while(it.hasNext()) { - ArrayList matches= it.next(); - fResult.addMatches(matches.toArray(new Match[matches.size()])); - } - fCachedMatches.clear(); + fCachedMatches.values().removeIf(matches -> { + if (matches != null && !matches.isEmpty()) { + fResult.addMatches(matches.toArray(new Match[matches.size()])); + return true; } - } + return false; + }); } } @@ -273,7 +251,9 @@ private boolean isScopeAllFileTypes() { @Override public String getLabel() { - return SearchMessages.FileSearchQuery_label; + Pattern searchPattern = getSearchPattern(); + return searchPattern.pattern().isEmpty() ? SearchMessages.FileSearchQuery_label + : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, getSearchString()); } public String getSearchString() { diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileTreeContentProvider.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileTreeContentProvider.java index e976f89..9b10aed 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileTreeContentProvider.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileTreeContentProvider.java @@ -16,10 +16,17 @@ *******************************************************************************/ package org.eclipse.search.internal.ui.text; +import java.util.Arrays; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -166,7 +173,7 @@ private void remove(Object element, boolean refreshViewer) { private boolean hasMatches(Object element) { if (element instanceof LineElement) { LineElement lineElement= (LineElement) element; - return lineElement.getNumberOfMatches(fResult) > 0; + return lineElement.hasMatches(fResult); } return fResult.getMatchCount(element) > 0; } @@ -192,9 +199,19 @@ public boolean hasChildren(Object element) { return getChildren(element).length > 0; } + static Stream toStream(Enumeration e) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(e.asIterator(), Spliterator.ORDERED), false); + } + @Override public synchronized void elementsChanged(Object[] updatedElements) { boolean singleElement = updatedElements.length == 1; + Set lineMatches = Arrays.stream(updatedElements).filter(LineElement.class::isInstance) + // only for distinct files: + .map(u -> ((LineElement) u).getParent()).distinct() + // query matches: + .map(fResult::getMatchSet).flatMap(FileTreeContentProvider::toStream) + .map(m -> ((FileMatch) m).getLineElement()).collect(Collectors.toSet()); try { for (Object updatedElement : updatedElements) { if (!(updatedElement instanceof LineElement)) { @@ -208,8 +225,8 @@ public synchronized void elementsChanged(Object[] updatedElements) { // change events to line elements are reported in text // search LineElement lineElement = (LineElement) updatedElement; - int nMatches = lineElement.getNumberOfMatches(fResult); - if (nMatches > 0) { + boolean hasMatches = lineMatches.contains(lineElement); + if (hasMatches) { if (singleElement && hasChild(lineElement.getParent(), lineElement)) { fTreeViewer.update(new Object[] { lineElement, lineElement.getParent() }, null); } else { diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/LineElement.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/LineElement.java index be7b807..7e86cd5 100644 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/LineElement.java +++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/LineElement.java @@ -15,6 +15,7 @@ package org.eclipse.search.internal.ui.text; import java.util.ArrayList; +import java.util.Enumeration; import org.eclipse.core.resources.IResource; @@ -66,9 +67,9 @@ public int getLength() { public FileMatch[] getMatches(AbstractTextSearchResult result) { ArrayList res= new ArrayList<>(); - Match[] matches= result.getMatches(fParent); - for (Match match : matches) { - FileMatch curr= (FileMatch) match; + Enumeration matches = result.getMatchSet(fParent); + while (matches.hasMoreElements()) { + FileMatch curr = (FileMatch) matches.nextElement(); if (curr.getLineElement() == this) { res.add(curr); } @@ -78,9 +79,9 @@ public FileMatch[] getMatches(AbstractTextSearchResult result) { public int getNumberOfMatches(AbstractTextSearchResult result) { int count= 0; - Match[] matches= result.getMatches(fParent); - for (Match match : matches) { - FileMatch curr= (FileMatch) match; + Enumeration matches = result.getMatchSet(fParent); + while (matches.hasMoreElements()) { + FileMatch curr = (FileMatch) matches.nextElement(); if (curr.getLineElement() == this) { count++; } @@ -88,5 +89,14 @@ public int getNumberOfMatches(AbstractTextSearchResult result) { return count; } - + public boolean hasMatches(AbstractTextSearchResult result) { + Enumeration matches = result.getMatchSet(fParent); + while (matches.hasMoreElements()) { + FileMatch curr = (FileMatch) matches.nextElement(); + if (curr.getLineElement() == this) { + return true; + } + } + return false; + } } diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/util/FileLabelProvider.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/util/FileLabelProvider.java deleted file mode 100644 index 73fa651..0000000 --- a/org.eclipse.search/search/org/eclipse/search/internal/ui/util/FileLabelProvider.java +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.search.internal.ui.util; - - -import java.text.MessageFormat; - -import org.eclipse.swt.graphics.Image; - -import org.eclipse.core.runtime.IPath; - -import org.eclipse.core.resources.IResource; - -import org.eclipse.jface.viewers.ILabelDecorator; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.LabelProvider; - -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.model.WorkbenchLabelProvider; - -import org.eclipse.search.internal.ui.SearchMessages; -import org.eclipse.search.ui.ISearchResultViewEntry; - -/** - * @deprecated Old search view - */ -@Deprecated -public class FileLabelProvider extends LabelProvider { - - public static final int SHOW_LABEL= 1; - public static final int SHOW_LABEL_PATH= 2; - public static final int SHOW_PATH_LABEL= 3; - public static final int SHOW_PATH= 4; - - private static final String fgSeparatorFormat= SearchMessages.FileLabelProvider_dashSeparated; - - private WorkbenchLabelProvider fLabelProvider; - private ILabelDecorator fDecorator; - - private int fOrder; - private Object[] fArgs= new String[2]; - - public FileLabelProvider(int orderFlag) { - fDecorator= PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator(); - fLabelProvider= new WorkbenchLabelProvider(); - fOrder= orderFlag; - } - - public void setOrder(int orderFlag) { - fOrder= orderFlag; - } - - @Override - public String getText(Object element) { - if (!(element instanceof ISearchResultViewEntry)) - return ""; //$NON-NLS-1$ - - IResource resource= ((ISearchResultViewEntry) element).getResource(); - String text= null; - - if (resource == null || !resource.exists()) - text= SearchMessages.SearchResultView_removed_resource; - - else { - IPath path= resource.getFullPath().removeLastSegments(1); - if (path.getDevice() == null) - path= path.makeRelative(); - if (fOrder == SHOW_LABEL || fOrder == SHOW_LABEL_PATH) { - text= fLabelProvider.getText(resource); - if (path != null && fOrder == SHOW_LABEL_PATH) { - fArgs[0]= text; - fArgs[1]= path.toString(); - text= MessageFormat.format(fgSeparatorFormat, fArgs); - } - } else { - if (path != null) - text= path.toString(); - else - text= ""; //$NON-NLS-1$ - if (fOrder == SHOW_PATH_LABEL) { - fArgs[0]= text; - fArgs[1]= fLabelProvider.getText(resource); - text= MessageFormat.format(fgSeparatorFormat, fArgs); - } - } - } - - // Do the decoration - if (fDecorator != null) { - String decoratedText= fDecorator.decorateText(text, resource); - if (decoratedText != null) - return decoratedText; - } - return text; - } - - @Override - public Image getImage(Object element) { - if (!(element instanceof ISearchResultViewEntry)) - return null; - - IResource resource= ((ISearchResultViewEntry) element).getResource(); - Image image= fLabelProvider.getImage(resource); - if (fDecorator != null) { - Image decoratedImage= fDecorator.decorateImage(image, resource); - if (decoratedImage != null) - return decoratedImage; - } - return image; - } - - @Override - public void dispose() { - super.dispose(); - fLabelProvider.dispose(); - } - - @Override - public boolean isLabelProperty(Object element, String property) { - return fLabelProvider.isLabelProperty(element, property); - } - - @Override - public void removeListener(ILabelProviderListener listener) { - super.removeListener(listener); - fLabelProvider.removeListener(listener); - } - - @Override - public void addListener(ILabelProviderListener listener) { - super.addListener(listener); - fLabelProvider.addListener(listener); - } -} diff --git a/org.eclipse.search/search/org/eclipse/search/ui/IActionGroupFactory.java b/org.eclipse.search/search/org/eclipse/search/ui/IActionGroupFactory.java index a6fbec4..b36f71d 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/IActionGroupFactory.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/IActionGroupFactory.java @@ -21,24 +21,34 @@ import org.eclipse.ui.actions.ActionGroup; /** - * Allows to specify an ActionGroup factory - * which will be used by the Search view to create an - * ActionGroup which is used to build the - * actions bars and the context menu. + * Allows to specify an ActionGroup factory which will be used by + * the Search view to create an ActionGroup which is used to build + * the actions bars and the context menu. *

* Note: Local tool bar contributions are not supported in 2.0. *

* - * Clients can implement this interface and pass an - * instance to the search result view. + * Clients can implement this interface and pass an instance to the search + * result view. * - * @see org.eclipse.ui.actions.ActionGroup - * @see ISearchResultView#searchStarted(IActionGroupFactory, String, String, ImageDescriptor, String, ILabelProvider, IAction, IGroupByKeyComputer, IRunnableWithProgress) - * @since 2.0 - * @deprecated Part of the old ('classic') search result view. Since 3.0 clients can create their own search result view pages (see {@link ISearchResultPage}), leaving it up to the page - * how to create actions. + * @see org.eclipse.ui.actions.ActionGroup + * @see ISearchResultView#searchStarted(IActionGroupFactory, String, String, + * ImageDescriptor, String, ILabelProvider, IAction, IGroupByKeyComputer, + * IRunnableWithProgress) + * @since 2.0 + * @deprecated Part of the old ('classic') search result view. Since 3.0 clients + * can create their own search result view pages (see + * {@link ISearchResultPage}), leaving it up to the page how to + * create actions. This class will be removed after 2023-09 release. + * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 for more + * information. + * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference + * @noextend + * @noimplement */ -@Deprecated +@Deprecated(forRemoval = true) public interface IActionGroupFactory { /** diff --git a/org.eclipse.search/search/org/eclipse/search/ui/IContextMenuContributor.java b/org.eclipse.search/search/org/eclipse/search/ui/IContextMenuContributor.java index 57e19ad..804dfd8 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/IContextMenuContributor.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/IContextMenuContributor.java @@ -21,17 +21,27 @@ import org.eclipse.jface.viewers.ILabelProvider; /** - * Specify how clients can add menu items - * to the context menu of the search result view. - * A class that contributes context menu items - * must implement this interface and pass an - * instance of itself to the search result view. + * Specify how clients can add menu items to the context menu of the search + * result view. A class that contributes context menu items must implement this + * interface and pass an instance of itself to the search result view. * - * @see ISearchResultView#searchStarted(IActionGroupFactory, String, String, ImageDescriptor, String, ILabelProvider, IAction, IGroupByKeyComputer, IRunnableWithProgress) - * @deprecated Part of the old ('classic') search result view. Since 3.0 clients can create their own search result view pages (see {@link ISearchResultPage}), leaving it up to the page - * how to create actions in context menus. + * @see ISearchResultView#searchStarted(IActionGroupFactory, String, String, + * ImageDescriptor, String, ILabelProvider, IAction, IGroupByKeyComputer, + * IRunnableWithProgress) + * @deprecated Part of the old ('classic') search result view. Since 3.0 clients + * can create their own search result view pages (see + * {@link ISearchResultPage}), leaving it up to the page how to + * create actions in context menus. This class will be removed after + * 2023-09 release. See + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 for more + * information. + * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference + * @noextend + * @noimplement */ -@Deprecated +@Deprecated(forRemoval = true) public interface IContextMenuContributor { /** diff --git a/org.eclipse.search/search/org/eclipse/search/ui/IGroupByKeyComputer.java b/org.eclipse.search/search/org/eclipse/search/ui/IGroupByKeyComputer.java index a8dcd16..ca94fee 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/IGroupByKeyComputer.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/IGroupByKeyComputer.java @@ -16,17 +16,25 @@ import org.eclipse.core.resources.IMarker; /** - * Computes the key by which the markers in the search result view - * are grouped. + * Computes the key by which the markers in the search result view are grouped. * *

* Clients may implement this interface. *

* - * @deprecated Part of the old ('classic') search result view. Since 3.0 clients can create their own search result view pages (see {@link ISearchResultPage}), leaving it up to the page - * how to group search results. + * @deprecated Part of the old ('classic') search result view. Since 3.0 clients + * can create their own search result view pages (see + * {@link ISearchResultPage}), leaving it up to the page how to + * group search results. This class will be removed after 2023-09 + * release. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 + * for more information. + * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference + * @noextend + * @noimplement */ -@Deprecated +@Deprecated(forRemoval = true) public interface IGroupByKeyComputer { /** diff --git a/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultView.java b/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultView.java index 180084c..9023d7f 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultView.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultView.java @@ -25,25 +25,32 @@ import org.eclipse.ui.IViewPart; /** - * Provides client access to the search result view. - * Each element in the view is a ISearchResultViewEntry, - * which groups markers based on the groupByKey provided - * by the client each time when adding a match. If every match should - * show up in the search result view then the match itself can be used - * as key. + * Provides client access to the search result view. Each element in the view is + * a ISearchResultViewEntry, which groups markers based on the + * groupByKey provided by the client each time when adding a match. + * If every match should show up in the search result view then the match itself + * can be used as key. *

- * The search result view has id "org.eclipse.search.SearchResultView". + * The search result view has id + * "org.eclipse.search.SearchResultView". *

*

* This interface is not intended to be implemented by clients. *

- * @deprecated Part of the old ('classic') search result view. Since 3.0 clients can create their own search result view pages. - * To access the parent view, {@link org.eclipse.search.ui.ISearchResultViewPart} is used instead. + * + * @deprecated Part of the old ('classic') search result view. Since 3.0 clients + * can create their own search result view pages. To access the + * parent view, {@link org.eclipse.search.ui.ISearchResultViewPart} + * is used instead. This class will be removed after 2023-09 + * release. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 + * for more information. * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. */ -@Deprecated +@Deprecated(forRemoval = true) public interface ISearchResultView extends IViewPart { /** diff --git a/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultViewEntry.java b/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultViewEntry.java index f7bb846..d71bc57 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultViewEntry.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/ISearchResultViewEntry.java @@ -17,9 +17,8 @@ import org.eclipse.core.resources.IResource; /** - * Specifies a search result view entry. - * This entry provides information about the markers - * it groups by a client defined key. Each entry in the search + * Specifies a search result view entry. This entry provides information about + * the markers it groups by a client defined key. Each entry in the search * result view corresponds to a different key. *

* The UI allows stepping through this entry's markers grouped by the key. @@ -27,13 +26,24 @@ *

* This interface is not intended to be implemented by clients. *

- * @deprecated Part of the old ('classic') search result view. Since 3.0 clients can create their own search result view pages (see {@link ISearchResultPage}), leaving it up to the search - * how to model search results. {@link org.eclipse.search.ui.text.AbstractTextSearchResult} and {@link org.eclipse.search.ui.text.Match} can be used to port old searches to the new API design. + * + * @deprecated Part of the old ('classic') search result view. Since 3.0 clients + * can create their own search result view pages (see + * {@link ISearchResultPage}), leaving it up to the search how to + * model search results. + * {@link org.eclipse.search.ui.text.AbstractTextSearchResult} and + * {@link org.eclipse.search.ui.text.Match} can be used to port old + * searches to the new API design. This class will be removed after + * 2023-09 release. See + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 for more + * information. * + * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. */ -@Deprecated +@Deprecated(forRemoval = true) public interface ISearchResultViewEntry { /** diff --git a/org.eclipse.search/search/org/eclipse/search/ui/SearchUI.java b/org.eclipse.search/search/org/eclipse/search/ui/SearchUI.java index 960f96b..31bbdd3 100644 --- a/org.eclipse.search/search/org/eclipse/search/ui/SearchUI.java +++ b/org.eclipse.search/search/org/eclipse/search/ui/SearchUI.java @@ -28,22 +28,30 @@ import org.eclipse.search.internal.ui.util.ExceptionHandler; /** - * The central class for access to the Search Plug-in's User Interface. - * This class cannot be instantiated; all functionality is provided by - * static methods. + * The central class for access to the Search Plug-in's User Interface. This + * class cannot be instantiated; all functionality is provided by static + * methods. * * Features provided: *
    *
  • convenient access to the search result view of the active workbench - * window.
  • + * window. *
* * @see ISearchResultView - * @deprecated Part of the old ('classic') search. Since 3.0 clients can create their own search result view pages and use {@link org.eclipse.search.ui.NewSearchUI} instead. + * @deprecated Part of the old ('classic') search. Since 3.0 clients can create + * their own search result view pages and use + * {@link org.eclipse.search.ui.NewSearchUI} instead. This class + * will be removed after 2023-09 release. See + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=487303 for more + * information. * * @noinstantiate This class is not intended to be instantiated by clients. + * @noreference + * @noextend + * @noimplement */ -@Deprecated +@Deprecated(forRemoval = true) public final class SearchUI { /** diff --git a/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF b/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF index ff643ae..da5861d 100644 --- a/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.text.quicksearch.tests -Bundle-Version: 1.1.100.qualifier +Bundle-Version: 1.1.300.qualifier Require-Bundle: org.eclipse.core.runtime, org.eclipse.text.quicksearch;bundle-version="1.0.300", org.eclipse.core.resources, @@ -15,3 +15,4 @@ Bundle-RequiredExecutionEnvironment: JavaSE-11 Bundle-Vendor: %providerName Export-Package: org.eclipse.text.quicksearch.tests Automatic-Module-Name: org.eclipse.text.quicksearch.tests +Bundle-Localization: plugin diff --git a/org.eclipse.text.quicksearch.tests/build.properties b/org.eclipse.text.quicksearch.tests/build.properties index 8762079..bc1708f 100644 --- a/org.eclipse.text.quicksearch.tests/build.properties +++ b/org.eclipse.text.quicksearch.tests/build.properties @@ -15,4 +15,11 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ about.html,\ - . + .,\ + plugin.properties + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.text.quicksearch.tests.*Test +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.text.quicksearch.tests/pom.xml b/org.eclipse.text.quicksearch.tests/pom.xml deleted file mode 100644 index 27c39a6..0000000 --- a/org.eclipse.text.quicksearch.tests/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.text - org.eclipse.text.quicksearch.tests - 1.1.100-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.text.quicksearch.tests.*Test - - - - - - org.eclipse.tycho - tycho-surefire-plugin - - true - true - - - - - diff --git a/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF b/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF index 47d4ab2..15ff697 100644 --- a/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF +++ b/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.text.quicksearch;singleton:=true -Bundle-Version: 1.1.200.qualifier +Bundle-Version: 1.1.400.qualifier Bundle-Activator: org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator Require-Bundle: org.eclipse.ui;bundle-version="[3.113.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.13.0,4.0.0)", diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index 873daad..0676815 100644 --- a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -31,6 +31,7 @@ import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.IHandler; import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.ICoreRunnable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; @@ -130,13 +131,8 @@ public class QuickSearchDialog extends SelectionStatusDialog { public static final Styler HIGHLIGHT_STYLE = org.eclipse.search.internal.ui.text.DecoratingFileSearchLabelProvider.HIGHLIGHT_STYLE; - private UIJob refreshJob = new UIJob(Messages.QuickSearchDialog_RefreshJob) { - @Override - public IStatus runInUIThread(IProgressMonitor monitor) { - refreshWidgets(); - return Status.OK_STATUS; - } - }; + private UIJob refreshJob = UIJob.create(Messages.QuickSearchDialog_RefreshJob, + (ICoreRunnable) m -> refreshWidgets()); protected void openSelection() { try { @@ -1115,7 +1111,7 @@ public void refreshWidgets() { //element is available in the list. openButton.setEnabled(itemCount>0); } - + refreshDetails(); } } diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties index 796a357..aafb997 100644 --- a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties @@ -1,13 +1,13 @@ QuickSearchPreferencesPage_Tooltip_Extensions=Enter a list of file extensions.\nElements in the list can be separated by commas or newlines. Any file or folder ending with one of the extensions will be ignored. QuickSearchPreferencesPage_Tooltip_Prefixes=Enter a list of file prefixes.\nElements in the list can be separated by commas or newlines. Any file or folder who's name begins with one of the prefixes will be ignored. QuickSearchPreferencesPage_Tooltip_Names=Enter a list of file names.\nElements in the list can be separated by commas or newlines. Any file or folder who's name equals one of the listed names will be ignored. -QuickSearchPreferencesPage_MaxLineLength=Max Line Length +QuickSearchPreferencesPage_MaxLineLength=Max line length QuickSearchPreferencesPage_Tooltip_MaxLineLength=When QuickSearch encounters a line of text longer than 'Max Line Length' it stops searching the current file.\nThis is meant to avoid searching in machine generated text files, such as minified JavaScript. QuickSearchPreferencesPage_Tooltip_MaxResults=If number of accumulated results reaches this limit the search will be suspended.\nNote that more results may still arrive beyond the limit since the searcher does suspend a search in the middle of a file. -QuickSearchPreferencesPage_MaxResults=Max Results -QuickSearchPreferencesPage_Ignored_Extensions=Ignored Extensions -QuickSearchPreferencesPage_Ignored_Prefixes=Ignored Prefixes -QuickSearchPreferencesPage_Ignored_Names=Ignored Names +QuickSearchPreferencesPage_MaxResults=Max results +QuickSearchPreferencesPage_Ignored_Extensions=Ignored extensions +QuickSearchPreferencesPage_Ignored_Prefixes=Ignored prefixes +QuickSearchPreferencesPage_Ignored_Names=Ignored names QuickSearchDialog_Open=&Open QuickSearchDialog_Refresh=&Refresh QuickSearchDialog_In=&in: diff --git a/org.eclipse.text.releng/platformText.setup b/org.eclipse.text.releng/platformText.setup index 74b0ed3..a686f8b 100644 --- a/org.eclipse.text.releng/platformText.setup +++ b/org.eclipse.text.releng/platformText.setup @@ -15,34 +15,28 @@ - eclipse.git.gerrit.remoteURIs + github.remoteURIs - Platform Text Git or Gerrit Repository + Platform Text Github Repository remoteURI - - - Platform Text @@ -68,7 +62,8 @@ + xsi:type="setup.workingsets:WorkingSetTask" + id="text.workingsets"> + excludedWorkingSet="//'text.workingsets'/@workingSets[name='Platform%20Text%20Examples'] //'text.workingsets'/@workingSets[name='Platform%20Text%20Tests']"/> - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.text - org.eclipse.text.tests - 3.13.100-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.text.tests.EclipseTextTestSuite - - diff --git a/org.eclipse.text/META-INF/MANIFEST.MF b/org.eclipse.text/META-INF/MANIFEST.MF index 63065ff..fa0a9a6 100644 --- a/org.eclipse.text/META-INF/MANIFEST.MF +++ b/org.eclipse.text/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.text -Bundle-Version: 3.12.0.qualifier +Bundle-Version: 3.12.300.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: diff --git a/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java b/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java index b13fa04..c177ed1 100644 --- a/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java +++ b/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java @@ -14,6 +14,7 @@ package org.eclipse.jface.text; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -80,25 +81,114 @@ public boolean isReplaceRequest() { return this.offset > -1 && this.length > -1; } } - + /** - * The active rewrite session. - * - * @since 3.1 - */ - private DocumentRewriteSession fActiveRewriteSession; - /** - * The list of pending requests. - * - * @since 3.1 + * Holder of the active {@link DocumentRewriteSession} with associated list of {@link Request} + * objects. + *

+ * On starting new {@link DocumentRewriteSession} or on the end of the active + * {@link DocumentRewriteSession} this object is being replaced by another one. + *

+ * + * @see AbstractLineTracker#startRewriteSession(DocumentRewriteSession) + * @see AbstractLineTracker#stopRewriteSession(DocumentRewriteSession, String) */ - private List fPendingRequests; + private static class SessionData { + + /** + * The active rewrite session. Not final, but can only be changed to null. + * + * @since 3.1 + */ + private volatile DocumentRewriteSession fActiveRewriteSession; + /** + * The list of pending requests. + * + * @since 3.1 + */ + private List fPendingRequests; + + /** + * @param activeRewriteSession may be null + */ + SessionData(DocumentRewriteSession activeRewriteSession){ + fActiveRewriteSession = activeRewriteSession; + if (activeRewriteSession != null) { + fPendingRequests = new ArrayList<>(20); + } else { + fPendingRequests = Collections.emptyList(); + } + } + + boolean isSessionActive() { + return fActiveRewriteSession != null; + } + + boolean setIfActive(String text) { + if (isSessionActive()) { + synchronized (this) { + if (!isSessionActive()) { + return false; + } + fPendingRequests.clear(); + fPendingRequests.add(new Request(text)); + } + return true; + } else { + return false; + } + } + + boolean addIfActive(int offset, int length, String text) { + if (isSessionActive()) { + synchronized (this) { + if (!isSessionActive()) { + return false; + } + fPendingRequests.add(new Request(offset, length, text)); + } + return true; + } else { + return false; + } + } + + Iterator flush() { + synchronized (this) { + fActiveRewriteSession = null; + Iterator requests = fPendingRequests.iterator(); + fPendingRequests = Collections.emptyList(); + return requests; + } + } + + boolean sameSession(DocumentRewriteSession session) { + return fActiveRewriteSession == session; + } + + @Override + public String toString() { + StringBuilder builder= new StringBuilder(); + builder.append("SessionData ["); //$NON-NLS-1$ + builder.append("activeRewriteSession="); //$NON-NLS-1$ + builder.append(fActiveRewriteSession); + builder.append(", "); //$NON-NLS-1$ + builder.append("pendingRequests="); //$NON-NLS-1$ + builder.append(fPendingRequests); + builder.append("]"); //$NON-NLS-1$ + return builder.toString(); + } + } + + private volatile SessionData sessionData; + private final Object sessionLock = new Object(); + /** * The implementation that this tracker delegates to. * * @since 3.2 */ - private ILineTracker fDelegate= new ListLineTracker() { + private volatile ILineTracker fDelegate= new ListLineTracker() { @Override public String[] getLegalLineDelimiters() { return AbstractLineTracker.this.getLegalLineDelimiters(); @@ -118,6 +208,7 @@ protected DelimiterInfo nextDelimiterInfo(String text, int offset) { * Creates a new line tracker. */ protected AbstractLineTracker() { + sessionData = new SessionData(null); } @Override @@ -179,9 +270,8 @@ public int getNumberOfLines(int offset, int length) throws BadLocationException @Override public void set(String text) { - if (hasActiveRewriteSession()) { - fPendingRequests.clear(); - fPendingRequests.add(new Request(text)); + boolean hasActiveRewriteSession = sessionData.setIfActive(text); + if (hasActiveRewriteSession) { return; } @@ -190,8 +280,8 @@ public void set(String text) { @Override public void replace(int offset, int length, String text) throws BadLocationException { - if (hasActiveRewriteSession()) { - fPendingRequests.add(new Request(offset, length, text)); + boolean hasActiveRewriteSession = sessionData.addIfActive(offset, length, text); + if (hasActiveRewriteSession) { return; } @@ -205,7 +295,7 @@ public void replace(int offset, int length, String text) throws BadLocationExcep * * @since 3.2 */ - private void checkImplementation() { + private synchronized void checkImplementation() { if (fNeedsConversion) { fNeedsConversion= false; fDelegate= new TreeLineTracker((ListLineTracker) fDelegate) { @@ -234,18 +324,21 @@ public String[] getLegalLineDelimiters() { @Override public final void startRewriteSession(DocumentRewriteSession session) { - if (fActiveRewriteSession != null) - throw new IllegalStateException(); - fActiveRewriteSession= session; - fPendingRequests= new ArrayList<>(20); + synchronized (sessionLock) { + if (sessionData.isSessionActive()){ + throw new IllegalStateException("Rewrite session is already active: " + sessionData); //$NON-NLS-1$ + } + sessionData = new SessionData(session); + } } @Override public final void stopRewriteSession(DocumentRewriteSession session, String text) { - if (fActiveRewriteSession == session) { - fActiveRewriteSession= null; - fPendingRequests= null; - set(text); + synchronized (sessionLock) { + if (sessionData.sameSession(session)) { + sessionData = new SessionData(null); + set(text); + } } } @@ -257,7 +350,7 @@ public final void stopRewriteSession(DocumentRewriteSession session, String text * @since 3.1 */ protected final boolean hasActiveRewriteSession() { - return fActiveRewriteSession != null; + return sessionData.isSessionActive(); } /** @@ -268,19 +361,16 @@ protected final boolean hasActiveRewriteSession() { */ protected final void flushRewriteSession() throws BadLocationException { if (DEBUG) - System.out.println("AbstractLineTracker: Flushing rewrite session: " + fActiveRewriteSession); //$NON-NLS-1$ - - Iterator e= fPendingRequests.iterator(); - - fPendingRequests= null; - fActiveRewriteSession= null; - - while (e.hasNext()) { - Request request= e.next(); - if (request.isReplaceRequest()) - replace(request.offset, request.length, request.text); - else - set(request.text); + System.out.println("AbstractLineTracker: Flushing rewrite session: " + sessionData); //$NON-NLS-1$ + synchronized (sessionData) { + Iterator e= sessionData.flush(); + while (e.hasNext()) { + Request request= e.next(); + if (request.isReplaceRequest()) + replace(request.offset, request.length, request.text); + else + set(request.text); + } } } diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java index 792a795..19be96d 100644 --- a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java @@ -45,9 +45,10 @@ public interface IDocumentPartitioningListener { *

* In version 2.0 this method has been replaces by * {@link IDocumentPartitioningListenerExtension#documentPartitioningChanged(IDocument, IRegion)}. + *

*

* In version 3.0 this method has been replaces by - * {@link IDocumentPartitioningListenerExtension2#documentPartitioningChanged(DocumentPartitioningChangedEvent)}

+ * {@link IDocumentPartitioningListenerExtension2#documentPartitioningChanged(DocumentPartitioningChangedEvent)}

* * @param document the document whose partitioning changed * diff --git a/org.eclipse.text/src/org/eclipse/jface/text/source/AnnotationModel.java b/org.eclipse.text/src/org/eclipse/jface/text/source/AnnotationModel.java index d32e000..4d24a5f 100644 --- a/org.eclipse.text/src/org/eclipse/jface/text/source/AnnotationModel.java +++ b/org.eclipse.text/src/org/eclipse/jface/text/source/AnnotationModel.java @@ -421,10 +421,11 @@ protected void replaceAnnotations(Annotation[] annotationsToRemove, Map deleted= new ArrayList<>(); Iterator e= getAnnotationMap().keySetIterator(); + IAnnotationMap annotations= getAnnotationMap(); while (e.hasNext()) { Annotation a= e.next(); - Position p= fAnnotations.get(a); + Position p= annotations.get(a); if (p == null || p.isDeleted()) deleted.add(a); } @@ -763,7 +765,7 @@ protected Iterator getAnnotationIterator(boolean cleanup) { @Override public Position getPosition(Annotation annotation) { - Position position= fAnnotations.get(annotation); + Position position= getAnnotationMap().get(annotation); if (position != null) return position; @@ -785,12 +787,12 @@ public void removeAllAnnotations() { * @param fireModelChanged indicates whether to notify all model listeners */ protected void removeAllAnnotations(boolean fireModelChanged) { - + IAnnotationMap annotations= getAnnotationMap(); if (fDocument != null) { Iterator e= getAnnotationMap().keySetIterator(); while (e.hasNext()) { Annotation a= e.next(); - Position p= fAnnotations.get(a); + Position p= annotations.get(a); removePosition(fDocument, p); // p.delete(); synchronized (getLockObject()) { @@ -799,7 +801,7 @@ protected void removeAllAnnotations(boolean fireModelChanged) { } } - fAnnotations.clear(); + annotations.clear(); fPositions.clear(); if (fireModelChanged) @@ -819,16 +821,17 @@ public void removeAnnotation(Annotation annotation) { * @param fireModelChanged indicates whether to notify all model listeners */ protected void removeAnnotation(Annotation annotation, boolean fireModelChanged) { - if (fAnnotations.containsKey(annotation)) { + IAnnotationMap annotations= getAnnotationMap(); + if (annotations.containsKey(annotation)) { Position p= null; - p= fAnnotations.get(annotation); + p= annotations.get(annotation); if (fDocument != null) { removePosition(fDocument, p); // p.delete(); } - fAnnotations.remove(annotation); + annotations.remove(annotation); fPositions.remove(p); synchronized (getLockObject()) { getAnnotationModelEvent().annotationRemoved(annotation, p); @@ -864,7 +867,7 @@ protected void modifyAnnotationPosition(Annotation annotation, Position position if (position == null) { removeAnnotation(annotation, fireModelChanged); } else { - Position p= fAnnotations.get(annotation); + Position p= getAnnotationMap().get(annotation); if (p != null) { if (position.getOffset() != p.getOffset() || position.getLength() != p.getLength()) { @@ -905,7 +908,7 @@ protected void modifyAnnotationPosition(Annotation annotation, Position position * @since 3.0 */ protected void modifyAnnotation(Annotation annotation, boolean fireModelChanged) { - if (fAnnotations.containsKey(annotation)) { + if (getAnnotationMap().containsKey(annotation)) { synchronized (getLockObject()) { getAnnotationModelEvent().annotationChanged(annotation); } diff --git a/org.eclipse.text/src/org/eclipse/jface/text/source/IAnnotationMap.java b/org.eclipse.text/src/org/eclipse/jface/text/source/IAnnotationMap.java index efb9646..6f4d2b0 100644 --- a/org.eclipse.text/src/org/eclipse/jface/text/source/IAnnotationMap.java +++ b/org.eclipse.text/src/org/eclipse/jface/text/source/IAnnotationMap.java @@ -35,7 +35,7 @@ * The returned collections of the methods values, * entrySet, and keySet are not synchronized on * the annotation map's lock object. - *

+ *

* * @see org.eclipse.jface.text.source.IAnnotationModel * @since 3.0 diff --git a/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF b/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF index 5ff9ddb..8fcb794 100644 --- a/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.ui.editors.tests;singleton:=true -Bundle-Version: 3.12.200.qualifier +Bundle-Version: 3.12.600.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: org.eclipse.ui.editors.tests @@ -11,7 +11,7 @@ Require-Bundle: org.junit;bundle-version="4.12.0", org.eclipse.jface;bundle-version="[3.5.0,4.0.0)", org.eclipse.text;bundle-version="[3.5.0,4.0.0)", - org.eclipse.ui.workbench.texteditor;bundle-version="[3.5.0,4.0.0)", + org.eclipse.ui.workbench.texteditor;bundle-version="[3.16.500,4.0.0)", org.eclipse.ui.editors;bundle-version="[3.5.0,4.0.0)", org.eclipse.ui.workbench;bundle-version="[3.5.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.14.0,4.0.0)", @@ -19,7 +19,10 @@ Require-Bundle: org.eclipse.ui.ide;bundle-version="[3.5.0,4.0.0)", org.eclipse.core.filesystem;bundle-version="1.7.0", org.eclipse.e4.ui.model.workbench;bundle-version="1.3.0", - org.eclipse.text.tests;bundle-version="[3.5.0,4.0.0)" + org.eclipse.text.tests;bundle-version="[3.5.0,4.0.0)", + org.eclipse.jface.text;bundle-version="3.19.0", + org.eclipse.ui;bundle-version="3.119.100", + org.eclipse.ui.tests.harness;bundle-version="1.8.0" Bundle-RequiredExecutionEnvironment: JavaSE-11 Eclipse-BundleShape: dir Bundle-ActivationPolicy: lazy diff --git a/org.eclipse.ui.editors.tests/build.properties b/org.eclipse.ui.editors.tests/build.properties index f59a82b..d92d8b0 100644 --- a/org.eclipse.ui.editors.tests/build.properties +++ b/org.eclipse.ui.editors.tests/build.properties @@ -22,3 +22,9 @@ bin.includes = plugin.properties,\ src.includes = about.html source.. = src/ + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.ui.editors.tests.EditorsTestSuite +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.ui.editors.tests/pom.xml b/org.eclipse.ui.editors.tests/pom.xml deleted file mode 100644 index f1d5b78..0000000 --- a/org.eclipse.ui.editors.tests/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.ui - org.eclipse.ui.editors.tests - 3.12.200-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.ui.editors.tests.EditorsTestSuite - - - - - org.eclipse.tycho - tycho-surefire-plugin - ${tycho.version} - - true - true - - - - eclipse-plugin - org.eclipse.equinox.event - 0.0.0 - - - - - - - diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/CaseActionTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/CaseActionTest.java new file mode 100644 index 0000000..40c42e9 --- /dev/null +++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/CaseActionTest.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Mickael Istria (Red Hat Inc.) + *******************************************************************************/ +package org.eclipse.ui.editors.tests; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.eclipse.core.runtime.NullProgressMonitor; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; + +import org.eclipse.ui.IEditorDescriptor; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.intro.IIntroPart; +import org.eclipse.ui.part.FileEditorInput; + +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.ITextEditorActionConstants; + +public class CaseActionTest { + + private static IProject project; + private static IFile file; + private AbstractTextEditor editor; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + project = ResourcesPlugin.getWorkspace().getRoot().getProject("test"); + project.create(new NullProgressMonitor()); + project.open(new NullProgressMonitor()); + file = project.getFile("foo.txt"); + file.create(new ByteArrayInputStream("bar".getBytes()), true, new NullProgressMonitor()); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + file.delete(true, new NullProgressMonitor()); + project.delete(true, new NullProgressMonitor()); + TestUtil.cleanUp(); + } + + @Before + public void setUp() throws Exception { + IIntroPart intro = PlatformUI.getWorkbench().getIntroManager().getIntro(); + if (intro != null) { + PlatformUI.getWorkbench().getIntroManager().closeIntro(intro); + } + + IEditorDescriptor desc = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(file.getName()); + editor = (AbstractTextEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow() + .getActivePage().openEditor(new FileEditorInput(file), desc.getId()); + editor.setFocus(); + // make sure we start from a clean state + } + + @After + public void tearDown() throws Exception { + editor.close(false); + editor= null; + } + + @Test + public void testMultiSelectionCase() { + IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + doc.set("foo bar foo"); + IRegion[] initialSelection = { new Region(0,3), new Region(8, 3) }; + editor.getSelectionProvider().setSelection(new MultiTextSelection(doc, initialSelection)); + editor.getAction(ITextEditorActionConstants.UPPER_CASE).run(); + assertEquals("FOO bar FOO", doc.get()); + assertArrayEquals(initialSelection, + ((IMultiTextSelection) editor.getSelectionProvider().getSelection()).getRegions()); + // + doc.set("foß bar fßo bar ßoo"); + editor.getSelectionProvider().setSelection( + new MultiTextSelection(doc, new IRegion[] { new Region(0, 3), new Region(8, 3), new Region(16, 3) })); + editor.getAction(ITextEditorActionConstants.UPPER_CASE).run(); + assertEquals("FOSS bar FSSO bar SSOO", doc.get()); + assertArrayEquals(new IRegion[] { // + new Region(0, 4), // + new Region(9, 4), // + new Region(18, 4) // + }, ((IMultiTextSelection) editor.getSelectionProvider().getSelection()).getRegions()); + } + +} diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java index 5438fe5..4dea731 100644 --- a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java +++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java @@ -1,5 +1,5 @@ -/******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. +/************************************************************************************************ + * Copyright (c) 2000, 2022 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -11,7 +11,8 @@ * Contributors: * IBM Corporation - initial API and implementation * Mickael Istria (Red Hat Inc.) - [484157] Add zoom test - *******************************************************************************/ + * Dirk Steinkamp - [576377] Add multi caret selection commands test + ************************************************************************************************/ package org.eclipse.ui.editors.tests; import org.junit.runner.RunWith; @@ -35,7 +36,10 @@ FileDocumentProviderTest.class, TextFileDocumentProviderTest.class, StatusEditorTest.class, - LargeFileTest.class + TextNavigationTest.class, + LargeFileTest.class, CaseActionTest.class, + TextMultiCaretNavigationTest.class, + TextMultiCaretSelectionCommandsTest.class, }) public class EditorsTestSuite { // see @SuiteClasses diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java new file mode 100644 index 0000000..eee388d --- /dev/null +++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +package org.eclipse.ui.editors.tests; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.core.filesystem.EFS; + +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; + +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; + +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; + +/* + * Note: this test would better fit in the org.eclipse.ui.workbench.texteditor bundle, but initializing + * and editor from this bundle is quite tricky without the IDE and EFS utils. + */ +public class TextMultiCaretNavigationTest { + + private static File file; + private static AbstractTextEditor editor; + private static StyledText widget; + + @Before + public void setUpBeforeClass() throws IOException, PartInitException, CoreException { + file = File.createTempFile(TextMultiCaretNavigationTest.class.getName(), ".txt"); + Files.write(file.toPath(), " abc\n 1234\nxyz".getBytes()); + editor = (AbstractTextEditor)IDE.openEditorOnFileStore(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), EFS.getStore(file.toURI())); + widget = (StyledText) editor.getAdapter(Control.class); + } + + @After + public void tearDown() { + editor.dispose(); + file.delete(); + } + + + @Test + public void testShiftHome() { + IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + editor.getSelectionProvider().setSelection(new MultiTextSelection(document, + new IRegion[] { new Region(5, 0), new Region(14, 0), new Region(18, 0), })); + assertEquals(5, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(2, 3), new Region(10, 4), new Region(15, 3) }, + selection.getRegions()); + assertEquals(2, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) }, + selection.getRegions()); + assertEquals(0, widget.getCaretOffset()); + } + + @Test + public void testShiftEnd() { + IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + editor.getSelectionProvider().setSelection(new MultiTextSelection(document, + new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0), })); + assertEquals(0, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run(); + IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) }, + selection.getRegions()); + assertEquals(5, widget.getCaretOffset()); + } + + @Test + public void testShiftEndHomeHome() { + IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + editor.getSelectionProvider().setSelection(new MultiTextSelection(document, + new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0), })); + assertEquals(0, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run(); + IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) }, + selection.getRegions()); + assertEquals(5, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(0, 2), new Region(6, 4), new Region(15, 0) }, + selection.getRegions()); + assertEquals(2, widget.getCaretOffset()); // Bug 577727 + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection(); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0) }, + selection.getRegions()); + assertEquals(0, widget.getCaretOffset()); + } + +} diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java new file mode 100644 index 0000000..85b4363 --- /dev/null +++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java @@ -0,0 +1,684 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.editors.tests; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collections; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.filesystem.EFS; + +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; + +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.ide.IDE; + +import org.eclipse.ui.texteditor.AbstractTextEditor; + +/* + * Note: this test would better fit in the org.eclipse.ui.workbench.texteditor bundle, but initializing + * an editor from this bundle is quite tricky without the IDE and EFS utils. + */ +public class TextMultiCaretSelectionCommandsTest { + private static final String MULTI_SELECTION_DOWN = "org.eclipse.ui.edit.text.select.selectMultiSelectionDown"; + private static final String ADD_ALL_MATCHES_TO_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection"; + private static final String MULTI_SELECTION_UP = "org.eclipse.ui.edit.text.select.selectMultiSelectionUp"; + private static final String STOP_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.stopMultiSelection"; + private static final String MULTI_CARET_DOWN = "org.eclipse.ui.edit.text.select.multiCaretDown"; + private static final String MULTI_CARET_UP = "org.eclipse.ui.edit.text.select.multiCaretUp"; + + private static final String LINE_1 = "private static String a;\n"; + private static final String LINE_2 = "private static String b; // this is a little longer\n"; + private static final String LINE_3 = "\t\tprivate static String c;\n"; + private static final String LINE_4 = "private static String d"; + + private static final int L1_LEN = LINE_1.length(); + private static final int L2_LEN = LINE_2.length(); + private static final int L3_LEN = LINE_3.length(); + private static final int L4_LEN = LINE_4.length(); + + private static File file; + private static AbstractTextEditor editor; + private static StyledText widget; + + @Before + public void setUpBeforeClass() throws IOException, PartInitException, CoreException { + file = File.createTempFile(TextMultiCaretSelectionCommandsTest.class.getName(), ".txt"); + Files.write(file.toPath(), (LINE_1 + LINE_2 + LINE_3 + LINE_4) // + .getBytes()); + editor = (AbstractTextEditor) IDE.openEditorOnFileStore( + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), EFS.getStore(file.toURI())); + widget = (StyledText) editor.getAdapter(Control.class); + } + + @After + public void tearDown() { + editor.dispose(); + file.delete(); + } + + @Test + public void testMultiSelectionDown_withFirstIdentifierSelected_addsIdenticalIdentifiersToSelection() + throws Exception { + setSelection(new IRegion[] { new Region(0, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }, getSelection()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withSecondIdentifierSelectedIdentifier_addsNextOccurenceToSelection() + throws Exception { + setSelection(new IRegion[] { new Region(8, 6) }); + assertEquals(14, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withSelectionInSecondRow_addsIdenticalIdentifierInThirdRowToSelection() + throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + 8, 6) }); + assertEquals(L1_LEN + 14, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(L1_LEN + 14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) }, + getSelection()); + } + + @Test + public void testMultiSelectionDown_withTwoSelectionsAndAnchorBelow_reducesSelection() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + 8, 6) }); + // It is important here to build up the selection in steps, so the + // handler can determine an anchor region + executeCommand(MULTI_SELECTION_UP); + assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withTwoSelectionsAndAnchorAbove_extendsSelection() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + 8, 6) }); + // It is important here to build up the selection in steps, so the + // handler can determine an anchor region + executeCommand(MULTI_SELECTION_DOWN); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) }, + getSelection()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6), + new Region(L1_LEN + L2_LEN + L3_LEN + 8, 6) }, getSelection()); + } + + // Caret-related tests for ADD_NEXT_MATCH_TO_MULTI_SELECTION + // that check how the selection is expanded + + @Test + public void testMultiSelectionDown_withCaretInFirstIdentifier_selectsFullIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(1, 0) }); + assertEquals(1, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withCaretInSecondIdentifier_selectsFullIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(11, 0) }); + assertEquals(11, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withCaretBetweenIdentifierCharAndNonIdentifierChar_selectsFullIdentifier() + throws Exception { + setSelection(new IRegion[] { new Region(23, 0) }); + assertEquals(23, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(23, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(22, 1) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withCaretInSecondRow_selectsFullIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + 11, 0) }); + assertEquals(L1_LEN + 11, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(L1_LEN + 14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withCaretInIdentifierWithNoFollowingMatch_selectsFullIdentifier() + throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 11, 0) }); + assertEquals(L1_LEN + L2_LEN + L3_LEN + 11, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(L1_LEN + L2_LEN + L3_LEN + 14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionDown_withCaretAtEndOfDocument_selectsFullIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }); + assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + + assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN - 1, 1) }, getSelection()); + } + + @Test + public void testAddAllMatches_withSingleSelection_selectsAllOccurences() throws Exception { + setSelection(new IRegion[] { new Region(0, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7), + new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection()); + } + + @Test + public void testAddAllMatches_withDoubleSelectionOfSameText_selectsAllOccurences() throws Exception { + setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7), + new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection()); + } + + @Test + public void testAddAllMatches_withDoubleSelectionOfDifferentTexts_doesNotChangeSelection() throws Exception { + setSelection(new IRegion[] { new Region(0, 7), new Region(8, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(8, 7) }, getSelection()); + } + + @Test + public void testAddAllMatches_withCaretInIdentifier_selectsAllOccurencesOfIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(2, 0) }); + assertEquals(2, widget.getCaretOffset()); + + executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION); + + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7), + new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withCaretInIdentifier_selectsFullIdentifier() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + 11, 0) }); + assertEquals(L1_LEN + 11, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_UP); + + assertEquals(L1_LEN + 8 + 6, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withSingleSelectionAndNoPreviousMatch_doesNothing() throws Exception { + setSelection(new IRegion[] { new Region(8, 6) }); + assertEquals(14, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_UP); + + assertEquals(14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withTwoSelections_removesSecondSelection() throws Exception { + setSelection(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }); + assertEquals(14, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_UP); + + assertEquals(14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withThreeSelections_removesThirdSelection() throws Exception { + setSelection( + new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) }); + assertEquals(14, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_UP); + + assertEquals(14, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withTwoSelectionsAndAnchorAbove_reducesSelection() throws Exception { + setSelection(new IRegion[] { new Region(8, 6) }); + // It is important here to build up the selection in steps, so the + // handler can determine an anchor region + executeCommand(MULTI_SELECTION_DOWN); + assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection()); + + executeCommand(MULTI_SELECTION_UP); + + assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection()); + } + + @Test + public void testMultiSelectionUp_withTwoSelectionsAndAnchorBelow_extendsSelection() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 10, 6) }); + // It is important here to build up the selection in steps, so the + // handler can determine an anchor region + executeCommand(MULTI_SELECTION_UP); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) }, + getSelection()); + + executeCommand(MULTI_SELECTION_UP); + + assertArrayEquals( + new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) }, + getSelection()); + } + + @Test + public void testStopMultiSelection_withSingleSelection_doesNotChangeSelectionOrCaretOffset() throws Exception { + setSelection(new IRegion[] { new Region(0, 7) }); + + assertEquals(7, widget.getCaretOffset()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 7) }, getSelection()); + } + + @Test + public void testStopMultiSelection_withMultiSelection_revokesSelectionAndKeepsFirstCaretOffset() throws Exception { + setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }); + + assertEquals(7, widget.getCaretOffset()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection()); + } + + @Test + public void testStopMultiSelection_withMultiSelectionAndCaretAtBeginning_revokesSelectionAndKeepsFirstCaretOffset() + throws Exception { + setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection()); + } + + @Test + public void testStopMultiSelection_withMultiSelectionAndCaretAfterLastSelection_revokesSelectionAndKeepsCaretOffset() + throws Exception { + setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7) }); + assertEquals(7, widget.getCaretOffset()); + + executeCommand(MULTI_SELECTION_DOWN); + executeCommand(MULTI_SELECTION_DOWN); + + // TODO How to place the caret at the end without dismissing the + // selection? Should rather be 57 + assertEquals(7, widget.getCaretOffset()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(7, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withCaret_addsCaretsInNextLines() throws Exception { + setSelection(new IRegion[] { new Region(0, 0) }); + assertEquals(0, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_DOWN); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withTwoCaretsAndAnchorRegionBelow_removesFirstCaret() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN, 0) }); + assertEquals(L1_LEN, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_UP); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(L1_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withSingleSelection_addsSelectionInNextLine() throws Exception { + setSelection(new IRegion[] { new Region(0, 3) }); + assertEquals(3, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_DOWN); + + assertEquals(3, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 3), new Region(L1_LEN, 3) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(3, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(3, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withSingleCaretAtEndOfLongerLine_addsCaretAtEndOfNextLine() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) }); + assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN, 0), new Region(L1_LEN + L2_LEN + L3_LEN, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withSingleCaretInLineAboveLineWithTabs_addsCaretInNextLineRespectingTabs() + throws Exception { + widget.setTabs(4); // Make default explicit + setSelection(new IRegion[] { new Region(L1_LEN + 8, 0) }); + assertEquals(L1_LEN + 8, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(L1_LEN + 8, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0), new Region(L1_LEN + L2_LEN + 2, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + 8, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withSingleCaretInLineWithTabs_addsCaretInNextLineRespectingTabs() throws Exception { + widget.setTabs(4); // Make default explicit + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }); + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + assertArrayEquals( + new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0), new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }, getSelection()); + } + + @Test + public void testMultiCaretDown_withSingleCaretAtEndOfText_doesNotChangeCaret() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }); + assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_DOWN); + + assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }, getSelection()); + } + + ///////////////////////////////////////////////////// + @Test + public void testMultiCaretUp_withCaret_addsCaretsInPreviousLines() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) }); + assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_UP); + + assertEquals(L1_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, getSelection()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withTwoCaretsAndAnchorRegionAbove_removesLastCaret() throws Exception { + setSelection(new IRegion[] { new Region(0, 0) }); + assertEquals(0, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_DOWN); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withSingleSelection_addsSelectionInPreviousLine() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN, 3) }); + assertEquals(L1_LEN + 3, widget.getCaretOffset()); + + executeCommand(MULTI_CARET_UP); + + assertEquals(3, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 3), new Region(L1_LEN, 3) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(3, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(3, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withSingleCaretAtEndOfLongerLine_addsCaretAtEndOfPreviousLine() throws Exception { + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) }); + assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(L1_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withSingleCaretInLineBelowLineWithTabs_addsCaretInPreviousLineRespectingTabs() + throws Exception { + widget.setTabs(4); // Make default explicit + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) }); + assertEquals(L1_LEN + L2_LEN + L3_LEN + 8, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + assertArrayEquals( + new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0), new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withSingleCaretInLineWithTabs_addsCaretInPreviousLineRespectingTabs() + throws Exception { + widget.setTabs(4); // Make default explicit + setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }); + assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(L1_LEN + 8, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0), new Region(L1_LEN + L2_LEN + 2, 0) }, + getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(L1_LEN + 8, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0) }, getSelection()); + } + + @Test + public void testMultiCaretUp_withSingleCaretAtBeginningOfText_doesNotChangeCaret() throws Exception { + setSelection(new IRegion[] { new Region(0, 0) }); + assertEquals(0, widget.getCaretOffset()); + + widget.setSize(800, 500); // make sure the widget is not size (0,0) + executeCommand(MULTI_CARET_UP); + + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + + executeCommand(STOP_MULTI_SELECTION); + assertEquals(0, widget.getCaretOffset()); + assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection()); + } + + // Helper methods + + private void executeCommand(String commandId) throws Exception { + Command command = PlatformUI.getWorkbench().getService(ICommandService.class).getCommand(commandId); + command.executeWithChecks(new ExecutionEvent(command, Collections.EMPTY_MAP, null, null)); + } + + private void setSelection(IRegion[] regions) { + IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + editor.getSelectionProvider().setSelection(new MultiTextSelection(document, regions)); + } + + private IRegion[] getSelection() { + return ((IMultiTextSelection) editor.getSelectionProvider().getSelection()).getRegions(); + } +} diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java new file mode 100644 index 0000000..0f59a09 --- /dev/null +++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ +package org.eclipse.ui.editors.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.core.filesystem.EFS; + +import org.eclipse.core.runtime.CoreException; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.preference.IPreferenceStore; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; + +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.internal.editors.text.EditorsPlugin; +import org.eclipse.ui.tests.harness.util.DisplayHelper; + +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; + +/* + * Note: this test would better fit in the org.eclipse.ui.workbench.texteditor bundle, but initializing + * and editor from this bundle is quite tricky without the IDE and EFS utils. + */ +public class TextNavigationTest { + + private File file; + private AbstractTextEditor editor; + private StyledText widget; + private IDocument fDocument; + + @Before + public void setUp() throws IOException, PartInitException, CoreException { + file = File.createTempFile(TextNavigationTest.class.getName(), ".txt"); + Files.write(file.toPath(), " abc".getBytes()); + editor = (AbstractTextEditor)IDE.openEditorOnFileStore(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), EFS.getStore(file.toURI())); + fDocument = editor.getDocumentProvider().getDocument(editor.getEditorInput()); + widget = (StyledText) editor.getAdapter(Control.class); + } + + @After + public void tearDown() { + editor.dispose(); + file.delete(); + } + + @Test + public void testHome() { + IPreferenceStore preferenceStore = EditorsPlugin.getDefault().getPreferenceStore(); + boolean previousPrefValue = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_NAVIGATION_SMART_HOME_END); + preferenceStore.setValue(AbstractTextEditor.PREFERENCE_NAVIGATION_SMART_HOME_END, false); + fDocument.set("line1\nline2"); + editor.selectAndReveal(fDocument.getLength(), 0); + editor.getAction(ITextEditorActionDefinitionIds.LINE_START).run(); + try { + assertEquals(6, ((ITextSelection) editor.getSelectionProvider().getSelection()).getOffset()); + editor.getAction(ITextEditorActionDefinitionIds.LINE_START).run(); + assertEquals(6, ((ITextSelection) editor.getSelectionProvider().getSelection()).getOffset()); + } finally { + preferenceStore.setValue(AbstractTextEditor.PREFERENCE_NAVIGATION_SMART_HOME_END, previousPrefValue); + } + } + + @Test + public void testShiftHome() { + editor.selectAndReveal(5, 0); + IAction action= editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START); + action.run(); + ITextSelection selection= (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(2, selection.getOffset()); + assertEquals(3, selection.getLength()); + assertEquals(2, widget.getCaretOffset()); + action.run(); + selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(5, selection.getLength()); + assertEquals(0, widget.getCaretOffset()); + } + + @Test + public void testShiftEnd() { + editor.getSelectionProvider().setSelection(new TextSelection(0, 0)); + IAction action= editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END); + action.run(); + ITextSelection selection= (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(5, selection.getLength()); + assertEquals(5, widget.getCaretOffset()); + } + + @Test + public void testShiftEndMultipleLines() { + fDocument.set("LINE 1\nLINE 2\n"); + editor.selectAndReveal(12, -7); + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run(); + ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(6, selection.getOffset()); + assertEquals(6, selection.getLength()); + assertEquals(6, widget.getCaretOffset()); + } + + @Test + public void testShiftEndHomeHome() { + editor.getSelectionProvider().setSelection(new TextSelection(0, 0)); + assertEquals(0, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run(); + ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(5, selection.getLength()); + assertEquals(5, widget.getCaretOffset()); + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(2, selection.getLength()); + assertEquals(2, widget.getCaretOffset()); // Bug 577727 + + editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run(); + selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(0, selection.getOffset()); + assertEquals(0, selection.getLength()); + assertEquals(0, widget.getCaretOffset()); + } + + @Test + public void testEndHomeRevealCaret() { + editor.getSelectionProvider().setSelection(new TextSelection(0, 0)); + fDocument.set(IntStream.range(0, 2000).mapToObj(i -> "a").collect(Collectors.joining())); + PlatformUI.getWorkbench().getIntroManager().closeIntro(PlatformUI.getWorkbench().getIntroManager().getIntro()); + assertTrue(DisplayHelper.waitForCondition(widget.getDisplay(), 2000, () -> widget.isVisible())); + int firstCharX = widget.getTextBounds(0, 0).x; + assertTrue(firstCharX >= 0 && firstCharX <= widget.getClientArea().width); + assertEquals(0, widget.getClientArea().x); + editor.getAction(ITextEditorActionDefinitionIds.LINE_END).run(); + ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); + assertEquals(fDocument.getLength(), selection.getOffset()); + int lastCharX = widget.getTextBounds(fDocument.getLength() - 1, fDocument.getLength() - 1).x; + assertTrue(lastCharX >= 0 && lastCharX <= widget.getClientArea().width); + editor.getAction(ITextEditorActionDefinitionIds.LINE_START).run(); + firstCharX = widget.getTextBounds(0, 0).x; + assertTrue(firstCharX >= 0 && firstCharX <= widget.getClientArea().width); + } +} diff --git a/org.eclipse.ui.editors/META-INF/MANIFEST.MF b/org.eclipse.ui.editors/META-INF/MANIFEST.MF index 5bacb1f..50b75cf 100644 --- a/org.eclipse.ui.editors/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.editors/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.editors; singleton:=true -Bundle-Version: 3.14.200.qualifier +Bundle-Version: 3.14.400.qualifier Bundle-Activator: org.eclipse.ui.internal.editors.text.EditorsPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName diff --git a/org.eclipse.ui.editors/plugin.properties b/org.eclipse.ui.editors/plugin.properties index f57a9cd..37fdae6 100644 --- a/org.eclipse.ui.editors/plugin.properties +++ b/org.eclipse.ui.editors/plugin.properties @@ -129,6 +129,7 @@ preferenceKeywords.accessibility= accessibility caret cursor quick diff text edi preferenceKeywords.spelling= spelling spell checking dictionary correction check text editor preferenceKeywords.linkedmode= editor linked mode template preferenceKeywords.hyperlinkDetectors= hyperlinking text editor on demand link navigation modifier key +preferenceKeywords.wordWrap= word wrap wordwrap wrapping wordwrapping soft text line break linebreak #--- linked mode annotations linked.focus.label= Current range diff --git a/org.eclipse.ui.editors/plugin.xml b/org.eclipse.ui.editors/plugin.xml index 7e8bc11..1e3e7cb 100644 --- a/org.eclipse.ui.editors/plugin.xml +++ b/org.eclipse.ui.editors/plugin.xml @@ -287,6 +287,7 @@ + + diff --git a/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/DocumentReader.java b/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/DocumentReader.java index 9fe66bf..95bfc4f 100644 --- a/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/DocumentReader.java +++ b/org.eclipse.ui.editors/src/org/eclipse/ui/editors/text/DocumentReader.java @@ -77,6 +77,12 @@ public CharSequence subSequence(int start, int end) { throw new IndexOutOfBoundsException(x.getLocalizedMessage()); } } + + /** @see CharSequence#toString **/ + @Override + public String toString() { + return fDocument.get(); + } } /** diff --git a/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/quickdiff/LastSaveReferenceProvider.java b/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/quickdiff/LastSaveReferenceProvider.java index b8f9705..18a2735 100644 --- a/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/quickdiff/LastSaveReferenceProvider.java +++ b/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/quickdiff/LastSaveReferenceProvider.java @@ -375,18 +375,21 @@ private static void setDocumentContent(IDocument document, IStorage storage, Str StringBuilder buffer= new StringBuilder(DEFAULT_FILE_SIZE); char[] readBuffer= new char[2048]; int n= in.read(readBuffer); - while (n > 0) { - if (monitor != null && monitor.isCanceled()) - return; + try { + while (n > 0) { + if (monitor != null && monitor.isCanceled()) + return; - buffer.append(readBuffer, 0, n); - n= in.read(readBuffer); + buffer.append(readBuffer, 0, n); + n= in.read(readBuffer); + } + } catch (OutOfMemoryError e) { + throw new IOException("OutOfMemoryError occurred while reading " + storage.getFullPath(), e); //$NON-NLS-1$ } - document.set(buffer.toString()); } catch (IOException x) { - throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, "Failed to access or read underlying storage", x)); //$NON-NLS-1$ + throw new CoreException(Status.error("Failed to access or read " + storage.getFullPath(), x)); //$NON-NLS-1$ } finally { try { if (in != null) diff --git a/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java b/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java index c881bfc..8b46109 100644 --- a/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java +++ b/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java @@ -852,11 +852,9 @@ public void widgetSelected(SelectionEvent e) { IntegerDomain tabWidthDomain= new IntegerDomain(1, 16); addTextField(appearanceComposite, tabWidth, tabWidthDomain, 15, 0); - if(isWordWrapPreferenceAllowed()){ - label= TextEditorMessages.TextEditorPreferencePage_enableWordWrap; - Preference enableWordWrap= new Preference(AbstractTextEditor.PREFERENCE_WORD_WRAP_ENABLED, label, null); - addCheckBox(appearanceComposite, enableWordWrap, new BooleanDomain(), 0); - } + label= TextEditorMessages.TextEditorPreferencePage_enableWordWrap; + Preference enableWordWrap= new Preference(AbstractTextEditor.PREFERENCE_WORD_WRAP_ENABLED, label, null); + addCheckBox(appearanceComposite, enableWordWrap, new BooleanDomain(), 0); label= TextEditorMessages.TextEditorPreferencePage_convertTabsToSpaces; Preference spacesForTabs= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS, label, null); @@ -1119,10 +1117,6 @@ public String getText(Object element) { fAppearanceColorTable.setLayoutData(gd); } - private boolean isWordWrapPreferenceAllowed() { - return Boolean.getBoolean("eclipse.show.wrapByDefaultPreference"); //$NON-NLS-1$ - } - @Override protected Control createContents(Composite parent) { initializeDefaultColors(); diff --git a/org.eclipse.ui.examples.javaeditor/Eclipse Java Editor Example/org/eclipse/ui/examples/javaeditor/util/JavaColorProvider.java b/org.eclipse.ui.examples.javaeditor/Eclipse Java Editor Example/org/eclipse/ui/examples/javaeditor/util/JavaColorProvider.java index d9843cb..cd8e7c1 100644 --- a/org.eclipse.ui.examples.javaeditor/Eclipse Java Editor Example/org/eclipse/ui/examples/javaeditor/util/JavaColorProvider.java +++ b/org.eclipse.ui.examples.javaeditor/Eclipse Java Editor Example/org/eclipse/ui/examples/javaeditor/util/JavaColorProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,7 +15,6 @@ import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import org.eclipse.swt.graphics.Color; @@ -40,15 +39,6 @@ public class JavaColorProvider { protected Map fColorTable= new HashMap<>(10); - /** - * Release all of the color resources held onto by the receiver. - */ - public void dispose() { - Iterator e= fColorTable.values().iterator(); - while (e.hasNext()) - e.next().dispose(); - } - /** * Return the color that is stored in the color table under the given RGB * value. diff --git a/org.eclipse.ui.examples.javaeditor/META-INF/MANIFEST.MF b/org.eclipse.ui.examples.javaeditor/META-INF/MANIFEST.MF index 99c3346..74329ae 100644 --- a/org.eclipse.ui.examples.javaeditor/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.examples.javaeditor/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.examples.javaeditor; singleton:=true -Bundle-Version: 3.3.0.qualifier +Bundle-Version: 3.3.100.qualifier Bundle-Activator: org.eclipse.ui.examples.javaeditor.JavaEditorExamplePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/ColorManager.java b/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/ColorManager.java index 0fea9b2..bfd27b6 100644 --- a/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/ColorManager.java +++ b/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/ColorManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2015 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,7 +14,6 @@ package org.eclipse.ui.examples.templateeditor.editors; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import org.eclipse.swt.graphics.Color; @@ -25,11 +24,6 @@ public class ColorManager { protected Map fColorTable = new HashMap<>(10); - public void dispose() { - Iterator e = fColorTable.values().iterator(); - while (e.hasNext()) - e.next().dispose(); - } public Color getColor(RGB rgb) { Color color = fColorTable.get(rgb); if (color == null) { diff --git a/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/TemplateEditor.java b/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/TemplateEditor.java index 50c463a..4ea7c2e 100644 --- a/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/TemplateEditor.java +++ b/org.eclipse.ui.examples.javaeditor/Template Editor Example/org/eclipse/ui/examples/templateeditor/editors/TemplateEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -35,12 +35,6 @@ public TemplateEditor() { setDocumentProvider(new XMLDocumentProvider()); } - @Override - public void dispose() { - colorManager.dispose(); - super.dispose(); - } - @Override protected void createActions() { super.createActions(); diff --git a/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF index bba00aa..4e76295 100644 --- a/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.genericeditor.examples/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Examples for Generic Editor Bundle-SymbolicName: org.eclipse.ui.genericeditor.examples;singleton:=true -Bundle-Version: 1.2.0.qualifier +Bundle-Version: 1.2.100.qualifier Bundle-Vendor: Eclipse.org Bundle-RequiredExecutionEnvironment: JavaSE-11 Require-Bundle: org.eclipse.ui.genericeditor;bundle-version="1.0.0", diff --git a/org.eclipse.ui.genericeditor.tests/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor.tests/META-INF/MANIFEST.MF index 36d6a46..5c98b0a 100644 --- a/org.eclipse.ui.genericeditor.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.genericeditor.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.ui.genericeditor.tests;singleton:=true -Bundle-Version: 1.2.0.qualifier +Bundle-Version: 1.2.200.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: org.eclipse.ui.genericeditor.tests, diff --git a/org.eclipse.ui.genericeditor.tests/build.properties b/org.eclipse.ui.genericeditor.tests/build.properties index 552c242..d213d1b 100644 --- a/org.eclipse.ui.genericeditor.tests/build.properties +++ b/org.eclipse.ui.genericeditor.tests/build.properties @@ -23,3 +23,9 @@ bin.includes = plugin.properties,\ src.includes = about.html source.. = src/ + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.ui.genericeditor.tests.GenericEditorTestSuite +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.ui.genericeditor.tests/pom.xml b/org.eclipse.ui.genericeditor.tests/pom.xml deleted file mode 100644 index fb63c76..0000000 --- a/org.eclipse.ui.genericeditor.tests/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.ui - org.eclipse.ui.genericeditor.tests - 1.2.0-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.ui.genericeditor.tests.GenericEditorTestSuite - - - - - org.eclipse.tycho - tycho-surefire-plugin - ${tycho.version} - - true - true - - - - eclipse-plugin - org.eclipse.equinox.event - 0.0.0 - - - - - - - diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java index b1d9b97..1295841 100644 --- a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/GenericEditorTestSuite.java @@ -25,6 +25,7 @@ ContextInfoTest.class, StylingTest.class, HoverTest.class, + ShowInformationTest.class, EditorTest.class, FoldingTest.class, AutoEditTest.class, diff --git a/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/ShowInformationTest.java b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/ShowInformationTest.java new file mode 100644 index 0000000..4d5da07 --- /dev/null +++ b/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/ShowInformationTest.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 +// * + * Contributors: + * Red Hat Inc. + *******************************************************************************/ +package org.eclipse.ui.genericeditor.tests; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import org.eclipse.core.runtime.Platform; + +import org.eclipse.text.tests.Accessor; + +import org.eclipse.jface.text.AbstractInformationControl; +import org.eclipse.jface.text.AbstractInformationControlManager; +import org.eclipse.jface.text.ITextOperationTarget; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.text.tests.util.DisplayHelper; + +import org.eclipse.ui.genericeditor.tests.contributions.AlrightyHoverProvider; + +import org.eclipse.ui.workbench.texteditor.tests.ScreenshotTest; + +import org.eclipse.ui.texteditor.AbstractTextEditor; + +/** + * @since 1.2 + */ +public class ShowInformationTest extends AbstratGenericEditorTest { + + @Rule + public TestName testName= new TestName(); + + @Before + public void skipOnNonLinux() { + Assume.assumeFalse("This test currently always fail on Windows (bug 505842), skipping", Platform.OS_WIN32.equals(Platform.getOS())); + Assume.assumeFalse("This test currently always fail on macOS (bug 505842), skipping", Platform.OS_MACOSX.equals(Platform.getOS())); + } + + @Test + public void testInformationControl() throws Exception { + Shell shell= getHoverShell(triggerCompletionAndRetrieveInformationControlManager(), true); + assertNotNull(findControl(shell, StyledText.class, AlrightyHoverProvider.LABEL)); + } + + private Shell getHoverShell(AbstractInformationControlManager manager, boolean failOnError) { + AbstractInformationControl[] control= { null }; + new DisplayHelper() { + @Override + protected boolean condition() { + control[0]= (AbstractInformationControl) new Accessor(manager, AbstractInformationControlManager.class).get("fInformationControl"); + return control[0] != null; + } + }.waitForCondition(this.editor.getSite().getShell().getDisplay(), 5000); + if (control[0] == null) { + if (failOnError) { + ScreenshotTest.takeScreenshot(getClass(), testName.getMethodName(), System.out); + fail(); + } else { + return null; + } + } + boolean[] result = {false}; + Shell shell= (Shell) new Accessor(control[0], AbstractInformationControl.class).get("fShell"); + new DisplayHelper() { + @Override + protected boolean condition() { + return (result[0] = shell.isVisible()); + } + }.waitForCondition(control[0].getShell().getDisplay(), 2000); + if (failOnError) { + assertTrue(shell.isVisible()); + } + return shell; + } + + private T findControl(Control control, Class controlType, String label) { + if (control.getClass() == controlType) { + @SuppressWarnings("unchecked") + T res= (T) control; + if (label == null) { + return res; + } + String controlLabel= null; + if (control instanceof Label) { + controlLabel= ((Label) control).getText(); + } else if (control instanceof Link) { + controlLabel= ((Link) control).getText(); + } else if (control instanceof Text) { + controlLabel= ((Text) control).getText(); + } else if (control instanceof StyledText) { + controlLabel= ((StyledText) control).getText(); + } + if (controlLabel != null && controlLabel.contains(label)) { + return res; + } + } else if (control instanceof Composite) { + for (Control child : ((Composite) control).getChildren()) { + T res= findControl(child, controlType, label); + if (res != null) { + return res; + } + } + } + return null; + } + + private Object getShowInformationData(AbstractInformationControlManager manager) { + return new Accessor(manager, AbstractInformationControlManager.class).get("fInformation"); + } + + private AbstractInformationControlManager triggerCompletionAndRetrieveInformationControlManager() { + final int caretLocation= 2; + this.editor.selectAndReveal(caretLocation, 0); + final StyledText editorTextWidget= (StyledText) this.editor.getAdapter(Control.class); + new DisplayHelper() { + @Override + protected boolean condition() { + return editorTextWidget.isFocusControl() && editorTextWidget.getSelection().x == caretLocation; + } + }.waitForCondition(editorTextWidget.getDisplay(), 3000); + // sending event to trigger hover computation + editorTextWidget.getShell().forceActive(); + editorTextWidget.getShell().setActive(); + editorTextWidget.getShell().setFocus(); + editorTextWidget.getShell().getDisplay().wake(); + + ITextViewer viewer= (ITextViewer) new Accessor(editor, AbstractTextEditor.class).invoke("getSourceViewer", new Object[0]); + + ITextOperationTarget textOperationTarget = (ITextOperationTarget)viewer; + assertTrue(textOperationTarget.canDoOperation(ISourceViewer.INFORMATION)); + textOperationTarget.doOperation(ISourceViewer.INFORMATION); + + AbstractInformationControlManager informationControlManager= (AbstractInformationControlManager) new Accessor(viewer, SourceViewer.class).get("fInformationPresenter"); + // retrieving hover content + new DisplayHelper() { + @Override + protected boolean condition() { + return getShowInformationData(informationControlManager) != null; + } + }.waitForCondition(editorTextWidget.getDisplay(), 6000); + return informationControlManager; + } +} diff --git a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF index 2619e89..aeaf84d 100644 --- a/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.ui.genericeditor;singleton:=true -Bundle-Version: 1.2.0.qualifier +Bundle-Version: 1.2.300.qualifier Bundle-Vendor: %Bundle-Vendor Bundle-RequiredExecutionEnvironment: JavaSE-11 Require-Bundle: org.eclipse.ui.workbench.texteditor;bundle-version="3.10.0", diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java index cbb910c..5cea4e4 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 Red Hat Inc. and others. + * Copyright (c) 2000, 2021 Red Hat Inc. and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,10 +18,19 @@ import java.util.List; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.hyperlink.HyperlinkManager; +import org.eclipse.jface.text.hyperlink.IHyperlink; +import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; +import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension2; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; @@ -37,7 +46,8 @@ import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; /** - * A generic code editor that is aimed at being extended by contributions. Behavior is supposed to be added via extensions, not by inheritance. + * A generic code editor that is aimed at being extended by contributions. + * Behavior is supposed to be added via extensions, not by inheritance. * * @since 1.0 */ @@ -66,26 +76,93 @@ public ExtensionBasedTextEditor() { /** * Initializes the key binding scopes of this generic code editor. */ - @Override protected void initializeKeyBindingScopes() { + @Override + protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { CONTEXT_ID }); } - @Override protected void doSetInput(IEditorInput input) throws CoreException { + @Override + protected void doSetInput(IEditorInput input) throws CoreException { super.doSetInput(input); configuration.watchDocument(getDocumentProvider().getDocument(input)); } - @Override protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { + @Override + protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { fAnnotationAccess = getAnnotationAccess(); fOverviewRuler = createOverviewRuler(getSharedColors()); - ProjectionViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); + ProjectionViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), + styles) { + + @Override + public void doOperation(int operation) { + if (HyperlinkManager.OPEN_HYPERLINK == operation) { + if (!openFirstHyperlink()) { + MessageDialog.openInformation(getControl().getShell(), + Messages.TextViewer_open_hyperlink_error_title, + Messages.TextViewer_open_hyperlink_error_message); + } + return; + } + + super.doOperation(operation); + } + + private boolean openFirstHyperlink() { + ITextSelection sel = (ITextSelection) this.getSelection(); + int offset = sel.getOffset(); + if (offset == -1) + return false; + + IRegion region = new Region(offset, 0); + IHyperlink hyperlink = findFirstHyperlink(region); + if (hyperlink != null) { + hyperlink.open(); + return true; + } + return false; + } + + private IHyperlink findFirstHyperlink(IRegion region) { + int activeHyperlinkStateMask = getSourceViewerConfiguration().getHyperlinkStateMask(this); + synchronized (fHyperlinkDetectors) { + for (IHyperlinkDetector detector : fHyperlinkDetectors) { + if (detector == null) + continue; + + if (detector instanceof IHyperlinkDetectorExtension2) { + int stateMask = ((IHyperlinkDetectorExtension2) detector).getStateMask(); + if (stateMask != -1 && stateMask != activeHyperlinkStateMask) + continue; + else if (stateMask == -1 && activeHyperlinkStateMask != fHyperlinkStateMask) + continue; + } else if (activeHyperlinkStateMask != fHyperlinkStateMask) + continue; + + boolean canShowMultipleHyperlinks = fHyperlinkPresenter.canShowMultipleHyperlinks(); + IHyperlink[] hyperlinks = detector.detectHyperlinks(this, region, canShowMultipleHyperlinks); + if (hyperlinks == null) + continue; + + Assert.isLegal(hyperlinks.length > 0); + + return hyperlinks[0]; + } + } + return null; + } + + }; + SourceViewerDecorationSupport support = getSourceViewerDecorationSupport(viewer); + configureCharacterPairMatcher(viewer, support); return viewer; } - @Override public void createPartControl(Composite parent) { + @Override + public void createPartControl(Composite parent) { super.createPartControl(parent); ProjectionViewer viewer = (ProjectionViewer) getSourceViewer(); @@ -94,26 +171,29 @@ public ExtensionBasedTextEditor() { computeImage(); } - @Override protected void initializeEditor() { + @Override + protected void initializeEditor() { super.initializeEditor(); - setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] { GenericEditorPreferenceConstants.getPreferenceStore(), EditorsUI.getPreferenceStore() })); + setPreferenceStore(new ChainedPreferenceStore(new IPreferenceStore[] { + GenericEditorPreferenceConstants.getPreferenceStore(), EditorsUI.getPreferenceStore() })); } /** - * Configure the {@link ICharacterPairMatcher} from the "org.eclipse.ui.genericeditor.characterPairMatchers" extension point. + * Configure the {@link ICharacterPairMatcher} from the + * "org.eclipse.ui.genericeditor.characterPairMatchers" extension point. * - * @param viewer - * the source viewer. + * @param viewer the source viewer. * - * @param support - * the source viewer decoration support. + * @param support the source viewer decoration support. */ private void configureCharacterPairMatcher(ISourceViewer viewer, SourceViewerDecorationSupport support) { - List matchers = GenericEditorPlugin.getDefault().getCharacterPairMatcherRegistry().getCharacterPairMatchers(viewer, this, configuration.getContentTypes(viewer)); + List matchers = GenericEditorPlugin.getDefault().getCharacterPairMatcherRegistry() + .getCharacterPairMatchers(viewer, this, configuration.getContentTypes(viewer.getDocument())); if (!matchers.isEmpty()) { ICharacterPairMatcher matcher = matchers.get(0); support.setCharacterPairMatcher(matcher); - support.setMatchingCharacterPainterPreferenceKeys(MATCHING_BRACKETS, MATCHING_BRACKETS_COLOR, HIGHLIGHT_BRACKET_AT_CARET_LOCATION, ENCLOSING_BRACKETS); + support.setMatchingCharacterPainterPreferenceKeys(MATCHING_BRACKETS, MATCHING_BRACKETS_COLOR, + HIGHLIGHT_BRACKET_AT_CARET_LOCATION, ENCLOSING_BRACKETS); } } @@ -133,7 +213,7 @@ private void computeImage() { private IContentType[] getContentTypes() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer != null) { - return configuration.getContentTypes(sourceViewer).toArray(new IContentType[] {}); + return configuration.getContentTypes(sourceViewer.getDocument()).toArray(new IContentType[] {}); } return new IContentType[] {}; } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java index 1d77bf7..2c14aa7 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/ExtensionBasedTextViewerConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2017 Red Hat Inc. and others. + * Copyright (c) 2016, 2021 Red Hat Inc. and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -20,37 +20,57 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.Set; +import java.util.stream.Collectors; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.AbstractReusableInformationControlCreator; import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.IAutoEditStrategy; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioningListener; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension; +import org.eclipse.jface.text.ITextHoverExtension2; import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.information.IInformationPresenter; +import org.eclipse.jface.text.information.IInformationProvider; +import org.eclipse.jface.text.information.IInformationProviderExtension; +import org.eclipse.jface.text.information.IInformationProviderExtension2; +import org.eclipse.jface.text.information.InformationPresenter; import org.eclipse.jface.text.presentation.IPresentationReconciler; import org.eclipse.jface.text.quickassist.IQuickAssistAssistant; import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; import org.eclipse.jface.text.quickassist.QuickAssistAssistant; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.internal.genericeditor.folding.DefaultFoldingReconciler; +import org.eclipse.ui.internal.genericeditor.hover.CompositeInformationControlCreator; import org.eclipse.ui.internal.genericeditor.hover.CompositeTextHover; import org.eclipse.ui.internal.genericeditor.markers.MarkerResoltionQuickAssistProcessor; import org.eclipse.ui.texteditor.ITextEditor; @@ -67,7 +87,8 @@ public final class ExtensionBasedTextViewerConfiguration extends TextSourceViewe implements IDocumentPartitioningListener { private ITextEditor editor; - private Set contentTypes; + private Set resolvedContentTypes; + private Set fallbackContentTypes = Set.of(); private IDocument document; private GenericEditorContentAssistant contentAssistant; @@ -82,39 +103,57 @@ public ExtensionBasedTextViewerConfiguration(ITextEditor editor, IPreferenceStor this.editor = editor; } - Set getContentTypes(ITextViewer viewer) { - if (this.contentTypes == null) { - this.contentTypes = new LinkedHashSet<>(); - String fileName = null; - fileName = getCurrentFileName(viewer); - if (fileName == null) { - return Collections.emptySet(); + public Set getContentTypes(IDocument document) { + if (this.resolvedContentTypes != null) { + return this.resolvedContentTypes; + } + this.resolvedContentTypes = new LinkedHashSet<>(); + ITextFileBuffer buffer = getCurrentBuffer(document); + if (buffer != null) { + try { + IContentType contentType = buffer.getContentType(); + if (contentType != null) { + this.resolvedContentTypes.add(contentType); + } + } catch (CoreException ex) { + GenericEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, GenericEditorPlugin.BUNDLE_ID, ex.getMessage(), ex)); } + } + String fileName = getCurrentFileName(document); + if (fileName != null) { Queue types = new LinkedList<>( Arrays.asList(Platform.getContentTypeManager().findContentTypesFor(fileName))); while (!types.isEmpty()) { IContentType type = types.poll(); - this.contentTypes.add(type); + this.resolvedContentTypes.add(type); IContentType parent = type.getBaseType(); if (parent != null) { types.add(parent); } } } - return this.contentTypes; + return this.resolvedContentTypes.isEmpty() ? fallbackContentTypes : resolvedContentTypes; + } + + private static ITextFileBuffer getCurrentBuffer(IDocument document) { + if (document != null) { + return FileBuffers.getTextFileBufferManager().getTextFileBuffer(document); + } + return null; } - private String getCurrentFileName(ITextViewer viewer) { + private String getCurrentFileName(IDocument document) { String fileName = null; if (this.editor != null) { fileName = editor.getEditorInput().getName(); } if (fileName == null) { - IDocument viewerDocument = viewer.getDocument(); - if (viewerDocument != null) { - ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(viewerDocument); - if (buffer != null) { - fileName = buffer.getLocation().lastSegment(); + ITextFileBuffer buffer = getCurrentBuffer(document); + if (buffer != null) { + IPath path = buffer.getLocation(); + if (path != null) { + fileName = path.lastSegment(); } } } @@ -124,7 +163,7 @@ private String getCurrentFileName(ITextViewer viewer) { @Override public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { List hovers = GenericEditorPlugin.getDefault().getHoverRegistry().getAvailableHovers(sourceViewer, - editor, getContentTypes(sourceViewer)); + editor, getContentTypes(sourceViewer.getDocument())); if (hovers == null || hovers.isEmpty()) { return null; } else if (hovers.size() == 1) { @@ -140,7 +179,7 @@ public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { ContentTypeRelatedExtensionTracker contentAssistProcessorTracker = new ContentTypeRelatedExtensionTracker<>( GenericEditorPlugin.getDefault().getBundle().getBundleContext(), IContentAssistProcessor.class, sourceViewer.getTextWidget().getDisplay()); - Set types = getContentTypes(sourceViewer); + Set types = getContentTypes(sourceViewer.getDocument()); contentAssistant = new GenericEditorContentAssistant(contentAssistProcessorTracker, registry.getContentAssistProcessors(sourceViewer, editor, types), types); if (this.document != null) { @@ -154,7 +193,7 @@ public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { PresentationReconcilerRegistry registry = GenericEditorPlugin.getDefault().getPresentationReconcilerRegistry(); List reconciliers = registry.getPresentationReconcilers(sourceViewer, editor, - getContentTypes(sourceViewer)); + getContentTypes(sourceViewer.getDocument())); if (!reconciliers.isEmpty()) { return reconciliers.get(0); } @@ -193,7 +232,7 @@ public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) List quickAssistProcessors = new ArrayList<>(); quickAssistProcessors.add(new MarkerResoltionQuickAssistProcessor()); quickAssistProcessors.addAll(GenericEditorPlugin.getDefault().getQuickAssistProcessorRegistry() - .getQuickAssistProcessors(sourceViewer, editor, getContentTypes(sourceViewer))); + .getQuickAssistProcessors(sourceViewer, editor, getContentTypes(sourceViewer.getDocument()))); CompositeQuickAssistProcessor compQuickAssistProcessor = new CompositeQuickAssistProcessor( quickAssistProcessors); quickAssistAssistant.setQuickAssistProcessor(compQuickAssistProcessor); @@ -207,10 +246,11 @@ public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) @Override public IReconciler getReconciler(ISourceViewer sourceViewer) { ReconcilerRegistry registry = GenericEditorPlugin.getDefault().getReconcilerRegistry(); - List reconcilers = registry.getReconcilers(sourceViewer, editor, getContentTypes(sourceViewer)); + List reconcilers = registry.getReconcilers(sourceViewer, editor, + getContentTypes(sourceViewer.getDocument())); // Fill with highlight reconcilers List highlightReconcilers = registry.getHighlightReconcilers(sourceViewer, editor, - getContentTypes(sourceViewer)); + getContentTypes(sourceViewer.getDocument())); if (!highlightReconcilers.isEmpty()) { reconcilers.addAll(highlightReconcilers); } else { @@ -218,7 +258,7 @@ public IReconciler getReconciler(ISourceViewer sourceViewer) { } // Fill with folding reconcilers List foldingReconcilers = registry.getFoldingReconcilers(sourceViewer, editor, - getContentTypes(sourceViewer)); + getContentTypes(sourceViewer.getDocument())); if (!foldingReconcilers.isEmpty()) { reconcilers.addAll(foldingReconcilers); } else { @@ -235,7 +275,7 @@ public IReconciler getReconciler(ISourceViewer sourceViewer) { public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) { AutoEditStrategyRegistry registry = GenericEditorPlugin.getDefault().getAutoEditStrategyRegistry(); List editStrategies = registry.getAutoEditStrategies(sourceViewer, editor, - getContentTypes(sourceViewer)); + getContentTypes(sourceViewer.getDocument())); if (!editStrategies.isEmpty()) { return editStrategies.toArray(new IAutoEditStrategy[editStrategies.size()]); } @@ -249,4 +289,116 @@ protected Map getHyperlinkDetectorTargets(ISourceViewer sour return targets; } + @Override + public IInformationPresenter getInformationPresenter(ISourceViewer sourceViewer) { + // Register information provider + List hovers = GenericEditorPlugin.getDefault().getHoverRegistry().getAvailableHovers(sourceViewer, + editor, getContentTypes(sourceViewer.getDocument())); + + InformationPresenter presenter = new InformationPresenter(new CompositeInformationControlCreator(hovers)); + // By default the InformationPresented is set to take the focus when visible, + // which makes the Browser to overtake all the focus/mouse etc. control over the + // 'org.eclipse.jface.text.information.InformationPresenter.Closer`. + // As we want to make t possible to close the information presenter by clicking + // outside of the information control or resizing the editor etc. - we need to + // disable such focus overtake by calling `takesFocusWhenVisible(false)` on the + // presenter. + // + presenter.takesFocusWhenVisible(false); + presenter.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); + + IInformationProvider provider = new ExtensionBaseInformationProvider(hovers); + // Register information provider + if (hovers != null && !hovers.isEmpty()) { + for (String contentType : getConfiguredContentTypes(sourceViewer)) { + presenter.setInformationProvider(provider, contentType); + } + } + + // sizes: see org.eclipse.jface.text.TextViewer.TEXT_HOVER_*_CHARS + presenter.setSizeConstraints(100, 12, false, true); + return presenter; + } + + class ExtensionBaseInformationProvider + implements IInformationProvider, IInformationProviderExtension, IInformationProviderExtension2 { + List fHovers; + private LinkedHashMap currentHovers; + + ExtensionBaseInformationProvider(List hovers) { + this.fHovers = hovers; + } + + @Override + public Object getInformation2(ITextViewer textViewer, IRegion subject) { + currentHovers = new LinkedHashMap<>(); + for (ITextHover hover : this.fHovers) { + Object res = hover instanceof ITextHoverExtension2 + ? ((ITextHoverExtension2) hover).getHoverInfo2(textViewer, subject) + : hover.getHoverInfo(textViewer, subject); + if (res != null) { + currentHovers.put(hover, res); + } + } + if (currentHovers.isEmpty()) { + return null; + } else if (currentHovers.size() == 1) { + return currentHovers.values().iterator().next(); + } + return currentHovers; + } + + @Override + public IRegion getSubject(ITextViewer textViewer, int offset) { + IRegion res = null; + for (ITextHover hover : this.fHovers) { + IRegion region = hover.getHoverRegion(textViewer, offset); + if (region != null) { + if (res == null) { + res = region; + } else { + int startOffset = Math.max(res.getOffset(), region.getOffset()); + int endOffset = Math.min(res.getOffset() + res.getLength(), + region.getOffset() + region.getLength()); + res = new Region(startOffset, endOffset - startOffset); + } + } + } + return res; + } + + @Override + public String getInformation(ITextViewer textViewer, IRegion subject) { + return this.fHovers.stream().map(hover -> hover.getHoverInfo(textViewer, subject)).filter(Objects::nonNull) + .collect(Collectors.joining("\n")); //$NON-NLS-1$ + } + + @Override + public IInformationControlCreator getInformationPresenterControlCreator() { + if (this.currentHovers == null || this.currentHovers.isEmpty()) { + return null; + } else if (currentHovers.size() == 1) { + ITextHover hover = this.currentHovers.keySet().iterator().next(); + return hover instanceof ITextHoverExtension ? ((ITextHoverExtension) hover).getHoverControlCreator() + : new AbstractReusableInformationControlCreator() { + @Override + protected IInformationControl doCreateInformationControl(Shell parent) { + return new DefaultInformationControl(parent); + }; + }; + } else { + return new CompositeInformationControlCreator(new ArrayList<>(this.currentHovers.keySet())); + } + } + } + + /** + * Set content-types that will be considered is no content-type can be deduced + * from the document (eg document is not backed by a FileBuffer) + * + * @param contentTypes + */ + public void setFallbackContentTypes(Set contentTypes) { + this.fallbackContentTypes = (contentTypes == null ? Set.of() : contentTypes); + } } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java index 62953a9..fcd3797 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/GenericEditorContentAssistant.java @@ -74,6 +74,7 @@ public GenericEditorContentAssistant( setAutoActivationDelay(0); enableColoredLabels(true); enableAutoActivation(true); + enableAutoActivateCompletionOnType(true); setInformationControlCreator(new AbstractReusableInformationControlCreator() { @Override protected IInformationControl doCreateInformationControl(Shell parent) { diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/Messages.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/Messages.java index 07e7cb7..a2c5474 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/Messages.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/Messages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Red Hat Inc. and others. + * Copyright (c) 2018, 2021 Red Hat Inc. and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,6 +18,10 @@ public class Messages extends NLS { private static final String BUNDLE_NAME = "org.eclipse.ui.internal.genericeditor.messages"; //$NON-NLS-1$ public static String DefaultWordHighlightStrategy_OccurrencesOf; + + public static String TextViewer_open_hyperlink_error_title; + public static String TextViewer_open_hyperlink_error_message; + static { // initialize resource bundle NLS.initializeMessages(BUNDLE_NAME, Messages.class); diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java index 296876c..c6a3845 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/compare/GenericEditorMergeViewer.java @@ -13,40 +13,59 @@ *******************************************************************************/ package org.eclipse.ui.internal.genericeditor.compare; +import java.util.LinkedHashSet; +import java.util.Set; + import org.eclipse.compare.CompareConfiguration; import org.eclipse.compare.contentmergeviewer.TextMergeViewer; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextInputListener; import org.eclipse.jface.text.TextViewer; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextViewerConfiguration; import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; +import org.eclipse.ui.texteditor.ChainedPreferenceStore; public class GenericEditorMergeViewer extends TextMergeViewer { + private final Set fallbackContentTypes = new LinkedHashSet<>(); + public GenericEditorMergeViewer(Composite parent, CompareConfiguration configuration) { super(parent, configuration); } - @Override protected SourceViewer createSourceViewer(Composite parent, int textOrientation) { + @Override + protected SourceViewer createSourceViewer(Composite parent, int textOrientation) { SourceViewer res = super.createSourceViewer(parent, textOrientation); res.addTextInputListener(new ITextInputListener() { - @Override public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + @Override + public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + fallbackContentTypes + .addAll(new ExtensionBasedTextViewerConfiguration(null, null).getContentTypes(newInput)); configureTextViewer(res); } - @Override public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + @Override + public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { // Nothing to do } }); return res; } - @Override protected void configureTextViewer(TextViewer textViewer) { + @Override + protected void configureTextViewer(TextViewer textViewer) { if (textViewer.getDocument() != null && textViewer instanceof ISourceViewer) { - ((ISourceViewer) textViewer).configure(new ExtensionBasedTextViewerConfiguration(null, GenericEditorPlugin.getDefault().getPreferenceStore())); + ExtensionBasedTextViewerConfiguration configuration = new ExtensionBasedTextViewerConfiguration(null, + new ChainedPreferenceStore(new IPreferenceStore[] { EditorsUI.getPreferenceStore(), + GenericEditorPlugin.getDefault().getPreferenceStore() })); + configuration.setFallbackContentTypes(fallbackContentTypes); + ((ISourceViewer) textViewer).configure(configuration); } } diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java index 16d4f40..201aa5b 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/folding/IndentFoldingStrategy.java @@ -158,7 +158,7 @@ public void projectionEnabled() { } } - private class LineIndent { + private static class LineIndent { public int line; public final int indent; diff --git a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/messages.properties b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/messages.properties index 5f8e219..1356d14 100644 --- a/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/messages.properties +++ b/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/messages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2018 Red Hat Inc. and others. +# Copyright (c) 2018, 2021 Red Hat Inc. and others. # # This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 @@ -12,3 +12,6 @@ # Alexander Kurtakov (Red Hat Inc.) - initial API and implementation ############################################################################### DefaultWordHighlightStrategy_OccurrencesOf=Occurrence of ''{0}'' + +TextViewer_open_hyperlink_error_title=Open Hyperlink +TextViewer_open_hyperlink_error_message=The operation is not applicable to the current selection. Select a hyperlink target. diff --git a/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF b/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF index 2d767e3..ed2503f 100644 --- a/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor.tests -Bundle-Version: 3.13.100.qualifier +Bundle-Version: 3.13.200.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: diff --git a/org.eclipse.ui.workbench.texteditor.tests/build.properties b/org.eclipse.ui.workbench.texteditor.tests/build.properties index 4dff298..d1d25b4 100644 --- a/org.eclipse.ui.workbench.texteditor.tests/build.properties +++ b/org.eclipse.ui.workbench.texteditor.tests/build.properties @@ -21,3 +21,9 @@ bin.includes = plugin.properties,\ src.includes = about.html source.. = src/ + +# Maven/Tycho pom model adjustments +pom.model.property.code.ignoredWarnings = ${tests.ignoredWarnings} +pom.model.property.testClass = org.eclipse.ui.workbench.texteditor.tests.WorkbenchTextEditorTestSuite +pom.model.property.tycho.surefire.useUIHarness = true +pom.model.property.tycho.surefire.useUIThread = true diff --git a/org.eclipse.ui.workbench.texteditor.tests/pom.xml b/org.eclipse.ui.workbench.texteditor.tests/pom.xml deleted file mode 100644 index 9021d83..0000000 --- a/org.eclipse.ui.workbench.texteditor.tests/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - 4.0.0 - - tests-pom - eclipse.platform.text - 4.21.0-SNAPSHOT - ../tests-pom/ - - org.eclipse.ui - org.eclipse.ui.workbench.texteditor.tests - 3.13.100-SNAPSHOT - eclipse-test-plugin - - ${project.artifactId} - org.eclipse.ui.workbench.texteditor.tests.WorkbenchTextEditorTestSuite - - - - - org.eclipse.tycho - tycho-surefire-plugin - ${tycho.version} - - true - true - - - - eclipse-plugin - org.eclipse.equinox.event - 0.0.0 - - - - - - - diff --git a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF index a2c871e..6db22b9 100644 --- a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor; singleton:=true -Bundle-Version: 3.16.200.qualifier +Bundle-Version: 3.16.600.qualifier Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName @@ -27,7 +27,7 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)", org.eclipse.compare.core;bundle-version="[3.5.0,4.0.0)", org.eclipse.core.expressions;bundle-version="[3.4.100,4.0.0)", - org.eclipse.jface.text;bundle-version="[3.8.0,4.0.0)", + org.eclipse.jface.text;bundle-version="[3.19.0,4.0.0)", org.eclipse.swt;bundle-version="[3.107.0,4.0.0)", org.eclipse.ui;bundle-version="[3.5.0,4.0.0)" Bundle-RequiredExecutionEnvironment: JavaSE-11 diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties index 3f41239..f647d81 100644 --- a/org.eclipse.ui.workbench.texteditor/plugin.properties +++ b/org.eclipse.ui.workbench.texteditor/plugin.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2000, 2015 IBM Corporation and others. +# Copyright (c) 2000, 2022 IBM Corporation and others. # # This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 @@ -14,6 +14,7 @@ # Daesung Ha - update recenter command description # Mickael Istria (Red Hat Inc.) - 469918 Zoom In/Out # Angelo Zerr - [CodeMining] Provide extension point for CodeMining - Bug 528419 +# Dirk Steinkamp - [576377] Add multi caret selection commands ############################################################################### pluginName= Text Editor Framework providerName= Eclipse.org @@ -43,6 +44,8 @@ showInformation.description= Displays information for the current caret location toggleBlockSelectionMode.label= Toggle Block Selection toggleBlockSelectionMode.tooltip= Toggle Block Selection toggleBlockSelectionMode.description= Toggle block / column selection in the current text editor +toMultiSelection.label=To multi-selection +toMultiSelection.description=Turn current selection into multiple text selections toggleWordWrap.label= Toggle Word Wrap toggleWordWrap.tooltip= Toggle Word Wrap @@ -156,6 +159,18 @@ command.selectWindowEnd.description = Select to the end of the window command.selectWindowEnd.name = Select Window End command.selectWindowStart.description = Select to the start of the window command.selectWindowStart.name = Select Window Start +command.selectAddAllMatchesToMultiSelection.description = Looks for all regions matching the current selection or identifier and adds them to a multi-selection +command.selectAddAllMatchesToMultiSelection.name = Add all matches to multi-selection +command.selectMultiSelectionDown.description = Search next matching region and add it to the current selection, or remove first element from current multi-selection +command.selectMultiSelectionDown.name = Multi selection down relative to anchor selection +command.selectMultiSelectionUp.description = Search next matching region above and add it to the current selection, or remove last element from current multi-selection +command.selectMultiSelectionUp.name = Multi selection up relative to anchor selection +command.stopMultiSelection.description = Unselects all multi-selections returning to a single cursor +command.stopMultiSelection.name = End multi-selection +command.multiCaretUp.description=Add a new caret/multi selection above the current line, or remove the last caret/multi selection +command.multiCaretUp.name=Multi caret up +command.multiCaretDown.description=Add a new caret/multi selection below the current line, or remove the first caret/multi selection +command.multiCaretDown.name=Multi caret down command.selectWordNext.description = Select the next word command.selectWordNext.name = Select Next Word command.selectWordPrevious.description = Select the previous word diff --git a/org.eclipse.ui.workbench.texteditor/plugin.xml b/org.eclipse.ui.workbench.texteditor/plugin.xml index 7705130..9c861c2 100644 --- a/org.eclipse.ui.workbench.texteditor/plugin.xml +++ b/org.eclipse.ui.workbench.texteditor/plugin.xml @@ -304,6 +304,42 @@ categoryId="org.eclipse.ui.category.textEditor" id="org.eclipse.ui.edit.text.select.windowEnd"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1360,6 +1465,13 @@ checkEnabled="true"> + + + + diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java new file mode 100644 index 0000000..ee9ebaf --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.core.runtime.Adapters; + +import org.eclipse.jface.viewers.ISelection; + +import org.eclipse.jface.text.IBlockTextSelection; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; + +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ITextEditorExtension5; + +public class ToMultiSelectionHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IEditorPart editor = HandlerUtil.getActiveEditor(event); + ITextEditor textEditor = Adapters.adapt(editor, ITextEditor.class); + if (textEditor == null) { + return null; + } + ISelection selection = textEditor.getSelectionProvider().getSelection(); + if (!(selection instanceof IBlockTextSelection)) { + return null; + } + IBlockTextSelection blockSelection = (IBlockTextSelection) selection; + IRegion[] initialRegions = ((IMultiTextSelection) blockSelection).getRegions(); + IDocument document = textEditor.getDocumentProvider().getDocument(editor.getEditorInput()); + if (document == null) { + return null; + } + IMultiTextSelection newSelection = new MultiTextSelection(document, initialRegions); + if (!(editor instanceof ITextEditorExtension5)) { + return null; + } + ITextEditorExtension5 ext = (ITextEditorExtension5) editor; + ext.setBlockSelectionMode(false); + textEditor.getSelectionProvider().setSelection(newSelection); + return newSelection; + } + +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java new file mode 100644 index 0000000..986afd6 --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java @@ -0,0 +1,462 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.core.runtime.Adapters; + +import org.eclipse.jface.viewers.ISelection; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IMultiTextSelection; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension5; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; + +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ITextEditorExtension5; + +/** + * Common super class for Multi-Selection-actions, containing various helper + * methods. Subclasses need to overwrite {@link #execute()}, which is only + * invoked if the {@link #textEditor} and {@link #document} could be properly + * initialized. + * + * @see AddAllMatchesToMultiSelectionHandler + * @see MultiSelectionDownHandler + * @see MultiSelectionUpHandler + * @see StopMultiSelectionHandler + */ +abstract class AbstractMultiSelectionHandler extends AbstractHandler { + /** + * Each widget can have a different anchor selection, that is stored in the + * widget's data with this key. + */ + private static final String ANCHOR_REGION_KEY = "org.eclipse.ui.internal.texteditor.multiselection.AbstractMultiSelectionHandler.anchorRegion"; //$NON-NLS-1$ + private ExecutionEvent event; + private ITextEditor textEditor; + private IDocument document; + /** + * SourceViewer Might be null, if {@link #textEditor} doesn't + * implement this interface. + */ + private ITextViewerExtension5 sourceViewer; + + /** + * This method needs to be overwritten from subclasses to handle the event. + * + * @throws ExecutionException an Exception the event handler might throw + */ + public abstract void execute() throws ExecutionException; + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + if (initFrom(event)) { + execute(); + } + return null; + } + + public ExecutionEvent getEvent() { + return event; + } + + protected boolean isMultiSelectionActive() { + IRegion[] regions = getSelectedRegions(); + return regions != null && regions.length > 1; + } + + protected boolean nothingSelected() { + IRegion[] regions = getSelectedRegions(); + return regions == null || regions.length == 0 || (regions.length == 1 && regions[0].getLength() == 0); + } + + protected IRegion[] getSelectedRegions() { + ISelection selection = textEditor.getSelectionProvider().getSelection(); + + if (!(selection instanceof IMultiTextSelection)) { + return null; + } + + return ((IMultiTextSelection) selection).getRegions(); + } + + protected IRegion offsetAsCaretRegion(int offset) { + return createRegionIfValid(offset, 0); + } + + protected void selectRegion(IRegion region) { + selectRegions(new IRegion[] { region }); + } + + protected void selectRegions(IRegion[] regions) { + setBlockSelectionMode(false); + + ISelection newSelection = new MultiTextSelection(document, regions); + textEditor.getSelectionProvider().setSelection(newSelection); + } + + protected void selectIdentifierUnderCaret() { + int offset = getCaretOffset(); + + Region identifierRegion = getIdentifierUnderCaretRegion(offset); + if (identifierRegion != null) { + selectRegion(identifierRegion); + setAnchorRegion(identifierRegion); + } + } + + protected void selectCaretPosition() { + IRegion caretRegion = offsetAsCaretRegion(getCaretOffset()); + selectRegion(caretRegion); + setAnchorRegion(caretRegion); + } + + protected boolean allRegionsHaveSameText() { + return allRegionsHaveSameText(getSelectedRegions()); + } + + protected boolean allRegionsEmpty() { + IRegion[] selectedRegions = getSelectedRegions(); + if (selectedRegions == null) + return true; + return isEmpty(selectedRegions[0]) && allRegionsHaveSameText(selectedRegions); + } + + protected boolean isEmpty(IRegion region) { + return region == null || region.getLength() == 0; + } + + protected IRegion getAnchorRegion() { + return (IRegion) getWidget().getData(ANCHOR_REGION_KEY); + } + + protected void setAnchorRegion(IRegion selection) { + if (selection == null) { + getWidget().setData(ANCHOR_REGION_KEY, null); + } else { + getWidget().setData(ANCHOR_REGION_KEY, selection); + } + } + + private void initAnchorRegion() { + IRegion[] regions = getSelectedRegions(); + if ((regions != null && regions.length == 1) || !contains(regions, getAnchorRegion())) { + setAnchorRegion(regions[0]); + } + } + + private boolean contains(IRegion[] regions, IRegion region) { + return Arrays.asList(regions).contains(region); + } + + private boolean allRegionsHaveSameText(IRegion[] regions) { + if (regions == null || regions.length == 1) + return true; + + try { + return allRegionsHaveText(regions, regionAsString(regions[0])); + } catch (BadLocationException e) { + return false; + } + } + + private boolean allRegionsHaveText(IRegion[] regions, String text) throws BadLocationException { + for (IRegion iRegion : regions) { + if (!text.equals(regionAsString(iRegion))) { + return false; + } + } + return true; + } + + protected IRegion[] addRegion(IRegion[] regions, IRegion newRegion) { + if (newRegion != null) { + IRegion[] newRegions = Arrays.copyOf(regions, regions.length + 1); + newRegions[newRegions.length - 1] = newRegion; + return newRegions; + } else { + return regions; + } + } + + protected IRegion[] removeLastRegionButOne(IRegion[] regions) { + if (regions == null || regions.length == 0) + return null; + if (regions.length == 1) { + return regions; + } + + return Arrays.copyOf(regions, regions.length - 1); + } + + protected IRegion[] removeFirstRegionButOne(IRegion[] regions) { + if (regions == null || regions.length == 0) + return null; + if (regions.length == 1) { + return regions; + } + + return Arrays.copyOfRange(regions, 1, regions.length); + } + + protected int getCaretOffset() { + IRegion[] regions = getSelectedRegions(); + if (regions == null) { + return -1; + } + return regions[0].getOffset() + regions[0].getLength(); + } + + protected void setCaretOffset(int caretOffset) { + selectRegion(offsetAsCaretRegion(caretOffset)); + } + + protected IRegion findNextMatch(IRegion region) throws ExecutionException { + try { + if (region.getLength() == 0) { + return offsetAsCaretRegion(offsetInNextLine(region.getOffset())); + } else { + String searchString = getTextOfRegion(region); + + String fullText = getFullText(); + int matchPos = fullText.indexOf(searchString, offsetAfter(region)); + return createRegionIfValid(matchPos, region.getLength()); + } + } catch (BadLocationException e) { + throw new ExecutionException("Internal error in findNextMatch", e); + } + } + + protected IRegion findPreviousMatch(IRegion region) throws ExecutionException { + try { + if (region.getLength() == 0) { + return offsetAsCaretRegion(offsetInPreviousLine(region.getOffset())); + } else { + String searchString = getTextOfRegion(region); + + String fullText = getFullText(); + int matchPos = fullText.lastIndexOf(searchString, region.getOffset() - 1); + return createRegionIfValid(matchPos, region.getLength()); + } + } catch (BadLocationException e) { + throw new ExecutionException("Internal error in findPreviousMatch", e); + } + } + + protected IRegion createRegionIfValid(int offset, int length) { + if ((offset < 0) || (offset > document.getLength())) + return null; + + return new Region(offset, Math.min(length, document.getLength() - offset)); + } + + protected IRegion[] findAllMatches(IRegion region) throws ExecutionException { + try { + String searchString = getTextOfRegion(region); + + String fullText = getFullText(); + List regions = findAllMatches(fullText, searchString); + return toArray(regions); + } catch (BadLocationException e) { + throw new ExecutionException("Internal error in findAllMatches", e); + } + } + + private List findAllMatches(String fullText, String searchString) { + List regions = new ArrayList<>(); + int length = searchString.length(); + int matchPos = 0; + while ((matchPos = fullText.indexOf(searchString, matchPos)) >= 0) { + regions.add(new Region(matchPos, length)); + matchPos += length; + } + return regions; + } + + protected int offsetInNextLine(int offset) throws BadLocationException { + return moveOffsetByLines(offset, 1); + } + + protected int offsetInPreviousLine(int offset) throws BadLocationException { + return moveOffsetByLines(offset, -1); + } + + private int moveOffsetByLines(int offset, int lineDelta) throws BadLocationException { + int newLineNo = document.getLineOfOffset(offset) + lineDelta; + if ((newLineNo < 0) || (newLineNo >= document.getNumberOfLines())) + return -1; + + int newOffset; + if (sourceViewer == null) { + // we don't have a sourceViewer and thus as a fallback + // assume the widget offsets are identical to the document offsets + newOffset = moveWidgetOffsetByLines(offset, lineDelta); + } else { + int widgetOffset = sourceViewer.modelOffset2WidgetOffset(offset); + int newWidgetOffset = moveWidgetOffsetByLines(widgetOffset, lineDelta); + newOffset = sourceViewer.widgetOffset2ModelOffset(newWidgetOffset); + } + if (newOffset == -1) { + return endOfLineOffset(newLineNo); + } + return newOffset; + } + + private int moveWidgetOffsetByLines(int widgetOffset, int lineDelta) throws BadLocationException { + Point location = getWidget().getLocationAtOffset(widgetOffset); + Point newLocation = new Point(location.x, location.y + lineDelta * getWidget().getLineHeight(widgetOffset)); + return getWidget().getOffsetAtPoint(newLocation); + } + + private int endOfLineOffset(int lineNo) throws BadLocationException { + return document.getLineOffset(lineNo) + document.getLineInformation(lineNo).getLength(); + } + + private boolean initFrom(ExecutionEvent event) { + this.event = event; + initTextEditor(); + if (textEditor == null) + return false; + initDocument(); + initSourceViewer(); + initAnchorRegion(); + return true; + } + + private void initTextEditor() { + IEditorPart editor = HandlerUtil.getActiveEditor(event); + textEditor = Adapters.adapt(editor, ITextEditor.class); + } + + private void initDocument() { + document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); + } + + private void initSourceViewer() { + ITextViewer textViewer = textEditor.getAdapter(ITextViewer.class); + sourceViewer = Adapters.adapt(textViewer, ITextViewerExtension5.class); + } + + private IRegion[] toArray(List regions) { + return regions.toArray(new IRegion[regions.size()]); + } + + private int offsetAfter(IRegion region) { + return region.getOffset() + region.getLength(); + } + + private String getTextOfRegion(IRegion region) throws BadLocationException { + return document.get(region.getOffset(), region.getLength()); + } + + private String getFullText() { + return document.get(); + } + + private String regionAsString(IRegion region) throws BadLocationException { + return document.get(region.getOffset(), region.getLength()); + } + + private Region getIdentifierUnderCaretRegion(int offset) { + try { + int startOffset = findStartOfIdentifier(offset); + int endOffset = findEndOfIdentifier(startOffset); + Region identifierRegion = new Region(startOffset, endOffset - startOffset); + return identifierRegion; + } catch (BadLocationException e) { + return null; + } + } + + private int findStartOfIdentifier(int offset) throws BadLocationException { + for (int i = offset - 1; i >= 0; i--) { + if (!isJavaIdentifierCharAtPos(i)) { + return i + 1; + } + } + return 0; // start of document reached + } + + private int findEndOfIdentifier(int offset) throws BadLocationException { + for (int i = offset; i <= document.getLength(); i++) { + if (i == document.getLength() || !isJavaIdentifierCharAtPos(i)) { + return i; + } + } + return offset; + } + + private boolean isJavaIdentifierCharAtPos(int i) throws BadLocationException { + return Character.isJavaIdentifierStart(document.getChar(i)) + || Character.isJavaIdentifierPart(document.getChar(i)); + } + + private StyledText getWidget() { + return (StyledText) textEditor.getAdapter(Control.class); + } + + private void setBlockSelectionMode(boolean blockSelectionMode) { + if (!(textEditor instanceof ITextEditorExtension5)) { + return; + } + ITextEditorExtension5 ext = (ITextEditorExtension5) textEditor; + ext.setBlockSelectionMode(blockSelectionMode); + } + + protected boolean selectionIsAboveAnchorRegion() { + IRegion[] selectedRegions = getSelectedRegions(); + if (selectedRegions == null || selectedRegions.length == 1) + return false; + return isLastRegion(getAnchorRegion(), selectedRegions); + } + + protected boolean selectionIsBelowAnchorRegion() { + IRegion[] selectedRegions = getSelectedRegions(); + if (selectedRegions == null || selectedRegions.length == 1) + return false; + return isFirstRegion(getAnchorRegion(), selectedRegions); + } + + private boolean isLastRegion(IRegion region, IRegion[] regions) { + if (region == null || regions == null || regions.length == 0) + return false; + + return region.equals(regions[regions.length - 1]); + } + + private boolean isFirstRegion(IRegion region, IRegion[] regions) { + if (region == null || regions == null || regions.length == 0) + return false; + + return region.equals(regions[0]); + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java new file mode 100644 index 0000000..189ea10 --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +/** + * Handler to extend the current selection to all found matches in the document. + * If nothing is selected, an implicit selection of the word under the cursor is + * performed and the selection performed with this. + */ +public class AddAllMatchesToMultiSelectionHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (nothingSelected()) { + selectIdentifierUnderCaret(); + } + extendSelectionToAllMatches(); + } + + private void extendSelectionToAllMatches() throws ExecutionException { + if (allRegionsHaveSameText()) { + if (!isEmpty(getAnchorRegion())) { + selectRegions(findAllMatches(getAnchorRegion())); + } + } + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java new file mode 100644 index 0000000..6d44542 --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IRegion; + +/** + * Handler to change the current set of multi carets/selections downwards. This + * might either mean to add a caret/selection below by adding a new + * caret/selection at the same line offset in the next line, or reduce the + * number of carets/selections by removing the first caret/selection range. This + * depends on the selection a multi caret/selection command was invoked with the + * first time -- that selection is remembered as an "anchor" to which successive + * calls are related as reference selection.
+ */ +public class MultiCaretDownHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (selectionIsAboveAnchorRegion()) { + removeFirstRegionFromSelection(); + } else { + extendSelectionWithSamePositionInNextLine(); + } + } + + private void extendSelectionWithSamePositionInNextLine() throws ExecutionException { + IRegion[] regions = getSelectedRegions(); + if (regions == null || regions.length == 0) { + return; + } + try { + IRegion lastRegion = regions[regions.length - 1]; + int newOffset = offsetInNextLine(lastRegion.getOffset()); + IRegion nextLineRegion = createRegionIfValid(newOffset, lastRegion.getLength()); + selectRegions(addRegion(regions, nextLineRegion)); + } catch (BadLocationException e) { + throw new ExecutionException("Internal error in extendSelectionWithSamePositionInNextLine", e); + } + } + + private void removeFirstRegionFromSelection() { + selectRegions(removeFirstRegionButOne(getSelectedRegions())); + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java new file mode 100644 index 0000000..1f12b4c --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IRegion; + +/** + * Handler to change the current set of multi carets/selections upwards. This + * might either mean to add a caret/selection above by adding a new + * caret/selection at the same line offset in the previous line, or reduce the + * number of carets/selections by removing the last caret/selection range. This + * depends on the selection a multi caret/selection command was invoked with the + * first time -- that selection is remembered as an "anchor" to which successive + * calls are related as reference selection.
+ */ +public class MultiCaretUpHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (selectionIsBelowAnchorRegion()) { + removeLastRegionFromSelection(); + } else { + extendSelectionWithSamePositionInPreviousLine(); + } + } + + private void extendSelectionWithSamePositionInPreviousLine() throws ExecutionException { + IRegion[] regions = getSelectedRegions(); + if (regions == null || regions.length == 0) { + return; + } + try { + IRegion firstRegion = regions[0]; + int newOffset = offsetInPreviousLine(firstRegion.getOffset()); + IRegion previousLineRegion = createRegionIfValid(newOffset, firstRegion.getLength()); + selectRegions(addRegion(regions, previousLineRegion)); + } catch (BadLocationException e) { + throw new ExecutionException("Internal error in extendSelectionWithSamePositionInPreviousLine", e); + } + } + + private void removeLastRegionFromSelection() { + selectRegions(removeLastRegionButOne(getSelectedRegions())); + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java new file mode 100644 index 0000000..20ed24c --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.jface.text.IRegion; + +/** + * Handler to change the current multi selection downwards. This might either + * mean to extend the selection by adding a match below, or shrink the selection + * by removing the first selection range. This depends on the selection a multi + * caret/selection command was invoked with the first time -- that selection is + * remembered as an "anchor" to which successive calls are related as reference + * selection.
+ * If no word is selected, an implicit selection of the word under the cursor is + * performed. + */ +public class MultiSelectionDownHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (nothingSelected()) { + selectIdentifierUnderCaret(); + } else if (selectionIsAboveAnchorRegion()) { + removeFirstRegionFromSelection(); + } else { + extendSelectionToNextMatch(); + } + } + + private void extendSelectionToNextMatch() throws ExecutionException { + if (allRegionsHaveSameText()) { + IRegion[] regions = getSelectedRegions(); + IRegion nextMatch = findNextMatch(regions[regions.length - 1]); + selectRegions(addRegion(regions, nextMatch)); + } + } + + private void removeFirstRegionFromSelection() { + if (allRegionsHaveSameText()) { + selectRegions(removeFirstRegionButOne(getSelectedRegions())); + } + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java new file mode 100644 index 0000000..6a31f55 --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +import org.eclipse.jface.text.IRegion; + +/** + * Handler to change the current multi selection upwards. This might either mean + * to extend the selection by adding a match above, or shrink the selection by + * removing the last selection range. This depends on the selection a multi + * caret/selection command was invoked with the first time -- that selection is + * remembered as an "anchor" to which successive calls are related as reference + * selection.
+ * If no word is selected, an implicit selection of the word under the cursor is + * performed. + */ +public class MultiSelectionUpHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (nothingSelected()) { + selectIdentifierUnderCaret(); + } else if (selectionIsBelowAnchorRegion()) { + removeLastRegionFromSelection(); + } else { + extendSelectionToPreviousMatch(); + } + } + + private void extendSelectionToPreviousMatch() throws ExecutionException { + if (allRegionsHaveSameText()) { + IRegion[] regions = getSelectedRegions(); + IRegion nextMatch = findPreviousMatch(regions[0]); + selectRegions(addRegion(regions, nextMatch)); + } + } + + private void removeLastRegionFromSelection() { + if (allRegionsHaveSameText()) { + selectRegions(removeLastRegionButOne(getSelectedRegions())); + } + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java new file mode 100644 index 0000000..46a75dc --- /dev/null +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2022 Dirk Steinkamp + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dirk Steinkamp - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.texteditor.multiselection; + +import org.eclipse.core.commands.ExecutionException; + +/** + * Handler to stop multi-selection mode. If more than one selection is active, + * all selections are revoked, and the caret is positioned at position of the + * first caret. + */ +public class StopMultiSelectionHandler extends AbstractMultiSelectionHandler { + + @Override + public void execute() throws ExecutionException { + if (isMultiSelectionActive()) { + stopMultiSelection(); + } + } + + private void stopMultiSelection() throws ExecutionException { + int caretOffset = getCaretOffset(); + selectRegion(offsetAsCaretRegion(caretOffset)); + setAnchorRegion(null); + } +} diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java index cda1fcf..878a579 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2021 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -33,6 +33,7 @@ import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.osgi.framework.Bundle; @@ -88,6 +89,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; @@ -105,6 +107,7 @@ import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.PlainMessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; @@ -131,6 +134,7 @@ import org.eclipse.jface.text.IFindReplaceTargetExtension; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IMarkRegionTarget; +import org.eclipse.jface.text.IMultiTextSelection; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.IRewriteTarget; import org.eclipse.jface.text.ISelectionValidator; @@ -387,6 +391,8 @@ public void documentChanged(DocumentEvent event) { document.addDocumentListener(listener); if (! validateEditorInputState() || documentChanged[0]) e.doit= false; + } catch (OperationCanceledException userPressedCancel) { + e.doit= false; } finally { if (document != null) document.removeDocumentListener(listener); @@ -1190,72 +1196,68 @@ public void run() { StyledText st= fSourceViewer.getTextWidget(); if (st == null || st.isDisposed()) return; - int caretOffset= st.getCaretOffset(); - int lineNumber= st.getLineAtOffset(caretOffset); - int lineOffset= st.getOffsetAtLine(lineNumber); + boolean caretAtBeginningOfSelection = st.getCaretOffset() == st.getSelection().x; + Point firstSelection = st.getSelection(); + int[] ranges = st.getSelectionRanges(); + List newSelection = new ArrayList<>(ranges.length / 2); + for (int j = 0; j < ranges.length; j += 2) { + int offset = ranges[j]; + int length = ranges[j + 1]; + int caretOffset = caretAtBeginningOfSelection ? offset : offset + length; + int lineNumber = st.getLineAtOffset(caretOffset); + int lineOffset = st.getOffsetAtLine(lineNumber); + int lineLength; + int caretOffsetInDocument; + final IDocument document = fSourceViewer.getDocument(); - int lineLength; - int caretOffsetInDocument; - final IDocument document= fSourceViewer.getDocument(); - - try { - caretOffsetInDocument= widgetOffset2ModelOffset(fSourceViewer, caretOffset); - lineLength= document.getLineInformationOfOffset(caretOffsetInDocument).getLength(); - } catch (BadLocationException ex) { - return; - } - int lineEndOffset= lineOffset + lineLength; - - int delta= lineEndOffset - st.getCharCount(); - if (delta > 0) { - lineEndOffset -= delta; - lineLength -= delta; - } - - String line= ""; //$NON-NLS-1$ - if (lineLength > 0) - line= st.getText(lineOffset, lineEndOffset - 1); + try { + caretOffsetInDocument = widgetOffset2ModelOffset(fSourceViewer, caretOffset); + lineLength = document.getLineInformationOfOffset(caretOffsetInDocument).getLength(); + } catch (BadLocationException ex) { + return; + } + int lineEndOffset = lineOffset + lineLength; - // Remember current selection - Point oldSelection= st.getSelection(); + int delta = lineEndOffset - st.getCharCount(); + if (delta > 0) { + lineEndOffset -= delta; + lineLength -= delta; + } + String line = ""; //$NON-NLS-1$ + if (lineLength > 0) + line = st.getText(lineOffset, lineEndOffset - 1); - // The new caret position - int newCaretOffset= -1; + // Remember current selection + Point oldSelection = new Point(offset, offset + length); - if (isSmartHomeEndEnabled) { - // Compute the line end offset - int i= getLineEndPosition(document, line, lineLength, caretOffsetInDocument); + // The new caret position + int newCaretOffset = -1; - if (caretOffset - lineOffset == i) + if (isSmartHomeEndEnabled) { + // Compute the line end offset + int i = getLineEndPosition(document, line, lineLength, caretOffsetInDocument); + newCaretOffset = (caretOffset - lineOffset == i) ? lineEndOffset : lineOffset + i; + } else if (caretOffset < lineEndOffset) { // to end of line - newCaretOffset= lineEndOffset; - else - // to end of text - newCaretOffset= lineOffset + i; + newCaretOffset = lineEndOffset; + } - } else { + if (newCaretOffset == -1) { + newCaretOffset = caretOffset; + } - if (caretOffset < lineEndOffset) - // to end of line - newCaretOffset= lineEndOffset; + newSelection.add(new Point( + fDoSelect ? (caretOffset < oldSelection.y ? oldSelection.y : oldSelection.x) : newCaretOffset, + newCaretOffset)); } - - if (newCaretOffset == -1) - newCaretOffset= caretOffset; - else - st.setCaretOffset(newCaretOffset); - - st.setCaretOffset(newCaretOffset); - if (fDoSelect) { - if (caretOffset < oldSelection.y) - st.setSelection(oldSelection.y, newCaretOffset); - else - st.setSelection(oldSelection.x, newCaretOffset); - } else - st.setSelection(newCaretOffset); - - fireSelectionChanged(oldSelection); + st.setSelectionRanges(newSelection.stream().flatMapToInt( + p -> IntStream.of(p.x, p.y - p.x)) + .toArray()); + if (newSelection.size() == 1) { + st.showSelection(); + } + fireSelectionChanged(firstSelection); } } @@ -1326,70 +1328,58 @@ public void run() { StyledText st= fSourceViewer.getTextWidget(); if (st == null || st.isDisposed()) return; + boolean caretAtBeginningOfSelection = st.getCaretOffset() == st.getSelection().x; + Point firstSelection = st.getSelection(); + int[] ranges = st.getSelectionRanges(); + List newSelection = new ArrayList<>(ranges.length / 2); + for (int j = 0; j < ranges.length; j += 2) { + int offset = ranges[j]; + int length = ranges[j + 1]; + int caretOffset = caretAtBeginningOfSelection ? offset : offset + length; + int lineNumber = st.getLineAtOffset(caretOffset); + int lineOffset = st.getOffsetAtLine(lineNumber); + int lineLength; + int caretOffsetInDocument; + final IDocument document = fSourceViewer.getDocument(); - int caretOffset= st.getCaretOffset(); - int lineNumber= st.getLineAtOffset(caretOffset); - int lineOffset= st.getOffsetAtLine(lineNumber); - - int lineLength; - int caretOffsetInDocument; - final IDocument document= fSourceViewer.getDocument(); - - try { - caretOffsetInDocument= widgetOffset2ModelOffset(fSourceViewer, caretOffset); - lineLength= document.getLineInformationOfOffset(caretOffsetInDocument).getLength(); - } catch (BadLocationException ex) { - return; - } - - String line= ""; //$NON-NLS-1$ - if (lineLength > 0) { - int end= lineOffset + lineLength - 1; - end= Math.min(end, st.getCharCount() -1); - line= st.getText(lineOffset, end); - } - - // Remember current selection - Point oldSelection= st.getSelection(); - - // The new caret position - int newCaretOffset= -1; + try { + caretOffsetInDocument = widgetOffset2ModelOffset(fSourceViewer, caretOffset); + lineLength = document.getLineInformationOfOffset(caretOffsetInDocument).getLength(); + } catch (BadLocationException ex) { + return; + } - if (isSmartHomeEndEnabled) { + String line = ""; //$NON-NLS-1$ + if (lineLength > 0) { + int end = lineOffset + lineLength - 1; + end = Math.min(end, st.getCharCount() - 1); + line = st.getText(lineOffset, end); + } - // Compute the line start offset - int index= getLineStartPosition(document, line, lineLength, caretOffsetInDocument); + // The new caret position + int newCaretOffset = -1; - if (caretOffset - lineOffset == index) + if (isSmartHomeEndEnabled) { + // Compute the line start offset + int index = getLineStartPosition(document, line, lineLength, caretOffsetInDocument); + newCaretOffset = (caretOffset - lineOffset == index) ? lineOffset : lineOffset + index; + } else if (caretOffset >= lineOffset) { // to beginning of line - newCaretOffset= lineOffset; - else - // to beginning of text - newCaretOffset= lineOffset + index; - - } else { + newCaretOffset = lineOffset; + } - if (caretOffset > lineOffset) - // to beginning of line - newCaretOffset= lineOffset; + newSelection.add( + new Point(fDoSelect ? (caretAtBeginningOfSelection ? offset + length : offset) : newCaretOffset, + newCaretOffset)); } - - if (newCaretOffset == -1) - newCaretOffset= caretOffset; - else - st.setCaretOffset(newCaretOffset); - - if (fDoSelect) { - if (caretOffset < oldSelection.y) - st.setSelection(oldSelection.y, newCaretOffset); - else - st.setSelection(oldSelection.x, newCaretOffset); - } else - st.setSelection(newCaretOffset); - - fireSelectionChanged(oldSelection); + st.setSelectionRanges(newSelection.stream().flatMapToInt( + p -> IntStream.of(p.x, p.y - p.x)) + .toArray()); + if (newSelection.size() == 1) { + st.showSelection(); + } + fireSelectionChanged(firstSelection); } - } /** @@ -2944,7 +2934,9 @@ private boolean isValidSelection(int offset, int length) { * @since 2.1 */ protected void doSetSelection(ISelection selection) { - if (selection instanceof ITextSelection) { + if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) { + getSourceViewer().getSelectionProvider().setSelection(selection); + } else if (selection instanceof ITextSelection) { ITextSelection textSelection= (ITextSelection) selection; selectAndReveal(textSelection.getOffset(), textSelection.getLength()); } @@ -3964,7 +3956,7 @@ private void initializeFindScopeColor(ISourceViewer viewer) { Color color= createColor(store, PREFERENCE_COLOR_FIND_SCOPE, styledText.getDisplay()); IFindReplaceTarget target= viewer.getFindReplaceTarget(); - if (target != null && target instanceof IFindReplaceTargetExtension) + if (target instanceof IFindReplaceTargetExtension) ((IFindReplaceTargetExtension) target).setScopeHighlightColor(color); } @@ -4708,9 +4700,12 @@ protected void handleEditorInputChanged() { title= EditorMessages.Editor_error_activated_outofsync_title; msg= NLSUtility.format(EditorMessages.Editor_error_activated_outofsync_message, inputName); - if (MessageDialog.open(MessageDialog.QUESTION, shell, title, msg, SWT.NONE, - EditorMessages.Editor_error_replace_button_label, - EditorMessages.Editor_error_dontreplace_button_label) == 0) { + PlainMessageDialog replaceContentDialog = PlainMessageDialog.getBuilder(shell, title).message(msg) + .buttonLabels(List.of(EditorMessages.Editor_error_replace_button_label, + EditorMessages.Editor_error_dontreplace_button_label)) + .build(); + + if (replaceContentDialog.open() == 0) { try { if (provider instanceof IDocumentProviderExtension) { @@ -4908,6 +4903,10 @@ protected void validateState(IEditorInput input) { } catch (CoreException x) { IStatus status= x.getStatus(); + if (status != null && status.getSeverity() == IStatus.CANCEL) { + // bug 577511 - do not ignore cancel: + throw new OperationCanceledException(); + } if (status == null || status.getSeverity() != IStatus.CANCEL) { Bundle bundle= Platform.getBundle(PlatformUI.PLUGIN_ID); ILog log= Platform.getLog(bundle); @@ -7111,6 +7110,10 @@ protected static class TextEditorSavable extends Saveable { /** The cached document. */ private IDocument fDocument; + /** for debug only. see Bug 569286 **/ + private Exception disconnectStack; + private boolean disconnectStackShown; + /** * Creates a new savable for this text editor. * @@ -7128,7 +7131,12 @@ public TextEditorSavable(ITextEditor textEditor) { */ public void disconnectEditor() { getAdapter(IDocument.class); // make sure the document is cached - fTextEditor= null; + if (disconnectStack == null && !disconnectStackShown) { + disconnectStack = new IllegalStateException( + "Disconnected before saving. Please post stacktrace to https://bugs.eclipse.org/bugs/show_bug.cgi?id=569286 " //$NON-NLS-1$ + + fTextEditor.getClass().getName() + " " + getName()); //$NON-NLS-1$ + } + fTextEditor = null; } @Override @@ -7148,31 +7156,41 @@ public ImageDescriptor getImageDescriptor() { @Override public void doSave(IProgressMonitor monitor) throws CoreException { - try { - fTextEditor.doSave(monitor); - } catch (NullPointerException e) { + ITextEditor textEditor = fTextEditor; + if (textEditor == null) { // disconnected Editor - for example due to disposed // This should not happen. Code added to handle the below bug. - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=550336 - Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID); - ILog log = Platform.getLog(bundle); - Status status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, null, e); - log.log(status); + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=569286 + if (disconnectStack != null && !disconnectStackShown) { + Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID); + ILog log = Platform.getLog(bundle); + disconnectStack.addSuppressed(new IllegalStateException("doSave after disconnect")); //$NON-NLS-1$ + Status status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, null, disconnectStack); + log.log(status); + disconnectStackShown = true; // shut up + } + return; } + textEditor.doSave(monitor); } @Override public boolean isDirty() { - try { - return fTextEditor.isDirty(); - } catch (NullPointerException e) { + ITextEditor textEditor = fTextEditor; + if (textEditor == null) { // disconnected Editor - for example due to disposed // This should not happen. Code added to handle the below bug. - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=550336 - Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID); - ILog log = Platform.getLog(bundle); - Status status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, null, e); - log.log(status); + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=569286 + if (disconnectStack != null && !disconnectStackShown) { + Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID); + ILog log = Platform.getLog(bundle); + disconnectStack.addSuppressed(new IllegalStateException("isDirty check after disconnect")); //$NON-NLS-1$ + Status status = new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, disconnectStack.getMessage(), + disconnectStack); + log.log(status); + disconnectStackShown = true; // shut up + } return false; } + return textEditor.isDirty(); } /* diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/CaseAction.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/CaseAction.java index 9d91c18..9f376e1 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/CaseAction.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/CaseAction.java @@ -15,10 +15,15 @@ *******************************************************************************/ package org.eclipse.ui.texteditor; +import java.util.ArrayList; +import java.util.List; import java.util.ResourceBundle; import org.eclipse.swt.custom.StyledText; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IBlockTextSelection; import org.eclipse.jface.text.IDocument; @@ -26,8 +31,12 @@ import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.JFaceTextUtil; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.jface.text.Region; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.internal.texteditor.TextEditorPlugin; + /** * Action that converts the current selection to lower case or upper case. * @since 3.0 @@ -79,37 +88,44 @@ public void run() { ITextSelection selection= (ITextSelection) viewer.getSelectionProvider().getSelection(); - int adjustment= 0; try { if (JFaceTextUtil.isEmpty(viewer, selection)) return; IRegion[] ranges= JFaceTextUtil.getCoveredRanges(viewer, selection); + List newRanges = new ArrayList<>(ranges.length); if (ranges.length > 1 && viewer instanceof ITextViewerExtension) ((ITextViewerExtension) viewer).getRewriteTarget().beginCompoundChange(); + int offsetShift = 0; for (IRegion region : ranges) { - String target= document.get(region.getOffset(), region.getLength()); + int newOffset = region.getOffset() + offsetShift; + String target = document.get(newOffset, region.getLength()); String replacement= (fToUpper ? target.toUpperCase() : target.toLowerCase()); if (!target.equals(replacement)) { - document.replace(region.getOffset(), region.getLength(), replacement); - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=145326: replacement might be larger than the original - adjustment= replacement.length() - target.length(); + document.replace(newOffset, region.getLength(), replacement); } + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=145326: replacement might be + // larger than the original + int currentAdjustment = replacement.length() - target.length(); + offsetShift += currentAdjustment; + newRanges.add(new Region(newOffset, region.getLength() + currentAdjustment)); } if (ranges.length > 1 && viewer instanceof ITextViewerExtension) ((ITextViewerExtension) viewer).getRewriteTarget().endCompoundChange(); + + // reinstall selection and move it into view + if (!(selection instanceof IBlockTextSelection)) { + viewer.getSelectionProvider() + .setSelection(new MultiTextSelection(viewer.getDocument(), newRanges.toArray(IRegion[]::new))); + } else { + viewer.getSelectionProvider().setSelection(selection); + } + // don't use the viewer's reveal feature in order to avoid jumping around + st.showSelection(); } catch (BadLocationException x) { - // ignore and return - return; + TextEditorPlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, x.getMessage(), x)); } - - // reinstall selection and move it into view - if (!(selection instanceof IBlockTextSelection)) - viewer.setSelectedRange(selection.getOffset(), selection.getLength() + adjustment); - else - viewer.getSelectionProvider().setSelection(selection); - // don't use the viewer's reveal feature in order to avoid jumping around - st.showSelection(); } } diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java index 7fae625..9715d5c 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java @@ -117,11 +117,14 @@ private EditorMessages() { public static String FindReplace_ReplaceFindButton_label; public static String FindReplace_ReplaceSelectionButton_label; public static String FindReplace_ReplaceAllButton_label; + public static String FindReplace_SelectAllButton_label; public static String FindReplace_CloseButton_label; public static String FindReplace_Status_noMatch_label; public static String FindReplace_Status_noMatchWithValue_label; public static String FindReplace_Status_replacement_label; public static String FindReplace_Status_replacements_label; + public static String FindReplace_Status_selection_label; + public static String FindReplace_Status_selections_label; public static String FindReplace_Status_wrapped_label; public static String FindNext_Status_noMatch_label; public static String AbstractDocumentProvider_ok; diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties index 82c0a41..8ef0eb8 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties @@ -114,11 +114,14 @@ FindReplace_FindNextButton_label=Fi&nd FindReplace_ReplaceFindButton_label=Replace/Fin&d FindReplace_ReplaceSelectionButton_label=&Replace FindReplace_ReplaceAllButton_label=Replace &All +FindReplace_SelectAllButton_label=&Select All FindReplace_CloseButton_label=Close FindReplace_Status_noMatch_label=String not found FindReplace_Status_noMatchWithValue_label=String ''{0}'' not found FindReplace_Status_replacement_label=1 match replaced FindReplace_Status_replacements_label={0} matches replaced +FindReplace_Status_selection_label=1 match selected +FindReplace_Status_selections_label={0} matches selected FindReplace_Status_wrapped_label=Wrapped search FindNext_Status_noMatch_label=String ''{0}'' not found diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java index db81606..525f5c7 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java @@ -64,6 +64,7 @@ import org.eclipse.jface.text.IFindReplaceTarget; import org.eclipse.jface.text.IFindReplaceTargetExtension; import org.eclipse.jface.text.IFindReplaceTargetExtension3; +import org.eclipse.jface.text.IFindReplaceTargetExtension4; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; @@ -83,6 +84,8 @@ */ class FindReplaceDialog extends Dialog { + private static final int CLOSE_BUTTON_ID = 101; + /** * Updates the find replace dialog on activation changes. */ @@ -196,7 +199,7 @@ public void modifyText(ModifyEvent e) { */ private Button fIsRegExCheckBox; - private Button fReplaceSelectionButton, fReplaceFindButton, fFindNextButton, fReplaceAllButton; + private Button fReplaceSelectionButton, fReplaceFindButton, fFindNextButton, fReplaceAllButton, fSelectAllButton; private Combo fFindField, fReplaceField; /** @@ -340,6 +343,18 @@ public void widgetSelected(SelectionEvent e) { }); setGridData(fFindNextButton, SWT.FILL, true, SWT.FILL, false); + fSelectAllButton = makeButton(panel, EditorMessages.FindReplace_SelectAllButton_label, 106, false, + new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + performSelectAll(); + updateFindAndReplaceHistory(); + } + }); + setGridData(fSelectAllButton, SWT.FILL, true, SWT.FILL, false); + + new Label(panel, SWT.NONE); // filler + fReplaceFindButton= makeButton(panel, EditorMessages.FindReplace_ReplaceFindButton_label, 103, false, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { @@ -822,7 +837,7 @@ private Composite createStatusAndCloseButton(Composite parent) { setGridData(fStatusLabel, SWT.FILL, true, SWT.CENTER, false); String label= EditorMessages.FindReplace_CloseButton_label; - Button closeButton= createButton(panel, 101, label, false); + Button closeButton = createButton(panel, CLOSE_BUTTON_ID, label, false); setGridData(closeButton, SWT.RIGHT, false, SWT.BOTTOM, false); return panel; @@ -1360,7 +1375,8 @@ class ReplaceAllRunnable implements Runnable { public int numberOfOccurrences; @Override public void run() { - numberOfOccurrences= replaceAll(findString, replaceString == null ? "" : replaceString, isForwardSearch(), isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); //$NON-NLS-1$ + numberOfOccurrences = replaceAll(findString, replaceString == null ? "" : replaceString, //$NON-NLS-1$ + isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); } } @@ -1391,9 +1407,60 @@ public void run() { updateButtonState(); } + /** + * Replaces all occurrences of the user's findString with the replace string. + * Indicate to the user the number of replacements that occur. + */ + private void performSelectAll() { + + int selectCount = 0; + final String findString = getFindString(); + + if (findString != null && !findString.isEmpty()) { + + class SelectAllRunnable implements Runnable { + public int numberOfOccurrences; + + @Override + public void run() { + numberOfOccurrences = selectAll(findString, isCaseSensitiveSearch(), isWholeWordSearch(), + isRegExSearchAvailableAndChecked()); + } + } + + try { + SelectAllRunnable runnable = new SelectAllRunnable(); + BusyIndicator.showWhile(fActiveShell.getDisplay(), runnable); + selectCount = runnable.numberOfOccurrences; + + if (selectCount != 0) { + if (selectCount == 1) { // not plural + statusMessage(EditorMessages.FindReplace_Status_selection_label); + } else { + String msg = EditorMessages.FindReplace_Status_selections_label; + msg = NLSUtility.format(msg, String.valueOf(selectCount)); + statusMessage(msg); + } + } else { + String msg = NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, + findString); + statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); + } + } catch (PatternSyntaxException ex) { + statusError(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state in this dialog + } + } + writeSelection(); + updateButtonState(); + } + /** * Validates the state of the find/replace target. - * @return true if target can be changed, false otherwise + * + * @return true if target can be changed, false + * otherwise * @since 2.1 */ private boolean validateTargetState() { @@ -1493,7 +1560,6 @@ private void performSearch(boolean mustInitIncrementalBaseLocation, boolean beep * * @param findString the string to search for * @param replaceString the replacement string - * @param forwardSearch the search direction * @param caseSensitive should the search be case sensitive * @param wholeWord does the search string represent a complete word * @param regExSearch if true findString represents a regular expression @@ -1501,13 +1567,13 @@ private void performSearch(boolean mustInitIncrementalBaseLocation, boolean beep * * @since 3.0 */ - private int replaceAll(String findString, String replaceString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { + private int replaceAll(String findString, String replaceString, boolean caseSensitive, boolean wholeWord, + boolean regExSearch) { int replaceCount= 0; int findReplacePosition= 0; findReplacePosition= 0; - forwardSearch= true; if (!validateTargetState()) return replaceCount; @@ -1518,18 +1584,11 @@ private int replaceAll(String findString, String replaceString, boolean forwardS try { int index= 0; while (index != -1) { - index= findAndSelect(findReplacePosition, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); + index = findAndSelect(findReplacePosition, findString, true, caseSensitive, wholeWord, regExSearch); if (index != -1) { // substring not contained from current position Point selection= replaceSelection(replaceString, regExSearch); replaceCount++; - - if (forwardSearch) - findReplacePosition= selection.x + selection.y; - else { - findReplacePosition= selection.x - 1; - if (findReplacePosition == -1) - break; - } + findReplacePosition = selection.x + selection.y; } } } finally { @@ -1540,6 +1599,32 @@ private int replaceAll(String findString, String replaceString, boolean forwardS return replaceCount; } + private int selectAll(String findString, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { + + int replaceCount = 0; + int position = 0; + + if (!validateTargetState()) + return replaceCount; + + List selectedRegions = new ArrayList<>(); + int index = 0; + do { + index = findAndSelect(position, findString, true, caseSensitive, wholeWord, regExSearch); + if (index != -1) { // substring not contained from current position + Point selection = fTarget.getSelection(); + selectedRegions.add(new Region(selection.x, selection.y)); + replaceCount++; + position = selection.x + selection.y; + } + } while (index != -1); + if (fTarget instanceof IFindReplaceTargetExtension4) { + ((IFindReplaceTargetExtension4) fTarget).setSelection(selectedRegions.toArray(IRegion[]::new)); + } + + return replaceCount; + } + // ------- UI creation --------------------------------------- /** @@ -1752,6 +1837,10 @@ public void updateTarget(IFindReplaceTarget target, boolean isTargetEditable, bo updateButtonState(); } + if (okToUse(fSelectAllButton)) { + fSelectAllButton.setEnabled(fTarget instanceof IFindReplaceTargetExtension4); + } + setContentAssistsEnablement(isRegExSearchAvailableAndChecked()); } diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceTarget.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceTarget.java index 420a8fb..54d0b49 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceTarget.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceTarget.java @@ -19,6 +19,7 @@ import org.eclipse.jface.text.IFindReplaceTarget; import org.eclipse.jface.text.IFindReplaceTargetExtension; import org.eclipse.jface.text.IFindReplaceTargetExtension3; +import org.eclipse.jface.text.IFindReplaceTargetExtension4; import org.eclipse.jface.text.IRegion; @@ -26,7 +27,8 @@ * Internal find/replace target wrapping the editor's source viewer. * @since 2.1 */ -class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension2, IFindReplaceTargetExtension3 { +class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension2, + IFindReplaceTargetExtension3, IFindReplaceTargetExtension4 { /** The editor */ private AbstractTextEditor fEditor; @@ -170,6 +172,13 @@ public void setSelection(int offset, int length) { getExtension().setSelection(offset, length); } + @Override + public void setSelection(IRegion[] regions) { + if (fTarget instanceof IFindReplaceTargetExtension4) { + ((IFindReplaceTargetExtension4) fTarget).setSelection(regions); + } + } + @Override public void setScopeHighlightColor(Color color) { if (getExtension() != null) diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ResourceAction.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ResourceAction.java index 316a718..fadefea 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ResourceAction.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/ResourceAction.java @@ -15,7 +15,6 @@ -import java.util.MissingResourceException; import java.util.ResourceBundle; import org.eclipse.jface.action.Action; @@ -47,14 +46,7 @@ public abstract class ResourceAction extends Action { * null) */ protected static String getString(ResourceBundle bundle, String key, String defaultValue) { - - String value= defaultValue; - try { - value= bundle.getString(key); - } catch (MissingResourceException x) { - } - - return value; + return bundle.containsKey(key) ? bundle.getString(key) : defaultValue; } /** @@ -152,7 +144,7 @@ protected void initialize(ResourceBundle bundle, String prefix) { setDescription(getString(bundle, descriptionKey, null)); String file= getString(bundle, imageKey, null); - if (file != null && !file.trim().isEmpty()) + if (file != null && !file.isBlank()) setImageDescriptor(ImageDescriptor.createFromFile(getClass(), file)); } } diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/spelling/SpellingReconcileStrategy.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/spelling/SpellingReconcileStrategy.java index 33a30de..48064ca 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/spelling/SpellingReconcileStrategy.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/spelling/SpellingReconcileStrategy.java @@ -52,7 +52,7 @@ public class SpellingReconcileStrategy implements IReconcilingStrategy, IReconci /** * Spelling problem collector. */ - private class SpellingProblemCollector implements ISpellingProblemCollector { + private static class SpellingProblemCollector implements ISpellingProblemCollector { /** Annotation model. */ private IAnnotationModel fAnnotationModel; diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/templates/ColumnLayout.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/templates/ColumnLayout.java index af873b2..0a5721d 100644 --- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/templates/ColumnLayout.java +++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/templates/ColumnLayout.java @@ -176,13 +176,13 @@ private void layoutTable(final Table table, final int width, final Rectangle are } if (increase) { - table.setSize(area.width, area.height); + table.setBounds(0, 0, area.width, area.height); } for (int i= 0; i < size; i++) { tableColumns[i].setWidth(widths[i]); } if (!increase) { - table.setSize(area.width, area.height); + table.setBounds(0, 0, area.width, area.height); } } diff --git a/pom.xml b/pom.xml index 0dff6f5..1634f9c 100644 --- a/pom.xml +++ b/pom.xml @@ -15,16 +15,16 @@ org.eclipse eclipse-platform-parent - 4.21.0-SNAPSHOT + 4.26.0-SNAPSHOT ../eclipse-platform-parent - eclipse.platform.text + org.eclipse.platform eclipse.platform.text pom - scm:git:https://git.eclipse.org/r/platform/eclipse.platform.text.git + scm:git:https://github.com/eclipse-platform/eclipse.platform.text.git - - 4.0.0 - - eclipse.platform.text - eclipse.platform.text - 4.21.0-SNAPSHOT - - tests-pom - pom - - ${tests.ignoredWarnings} - -