diff --git a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF index 653c7b05d9d..78461c220ee 100644 --- a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF @@ -38,7 +38,6 @@ Require-Bundle: org.eclipse.text;bundle-version="[3.8.0,4.0.0)";visibility:=reexport, org.eclipse.swt;bundle-version="[3.133.0,4.0.0)", org.eclipse.jface;bundle-version="[3.39.0,4.0.0)" -Import-Package: com.ibm.icu.text Bundle-RequiredExecutionEnvironment: JavaSE-21 Automatic-Module-Name: org.eclipse.jface.text Bundle-Activator: org.eclipse.jface.text.Activator diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java index 6b1b370b8e4..c814e8f60be 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java @@ -14,12 +14,10 @@ package org.eclipse.jface.text; +import java.text.BreakIterator; import java.text.CharacterIterator; import java.util.Locale; -import com.ibm.icu.text.BreakIterator; - - /** * Standard implementation of @@ -223,9 +221,54 @@ protected IRegion findExtendedDoubleClickSelection(IDocument document, int offse * @since 3.5 */ protected IRegion findWord(IDocument document, int offset) { + IRegion identifier= findIdentifierAt(document, offset); + if (identifier != null) { + return identifier; + } return findWord(document, offset, getWordBreakIterator()); } + /** + * If the offset lies on an ASCII identifier character ({@code [A-Za-z0-9_]}), or + * just after one, returns the maximal contiguous identifier run. Otherwise + * returns {@code null} so the caller falls back to the locale-aware + * {@link BreakIterator}. This handles identifier-style words containing runs + * of {@code '_'} (e.g. {@code foo__bar}, {@code __aaaa}) consistently across + * JDK versions, since {@link BreakIterator#getWordInstance()} places word + * boundaries between consecutive underscores while users expect such tokens + * to be selected as a single word. + */ + private static IRegion findIdentifierAt(IDocument document, int offset) { + try { + IRegion line= document.getLineInformationOfOffset(offset); + int lineStart= line.getOffset(); + int lineEnd= lineStart + line.getLength(); + int probe; + if (offset < lineEnd && isIdentifierPart(document.getChar(offset))) { + probe= offset; + } else if (offset > lineStart && isIdentifierPart(document.getChar(offset - 1))) { + probe= offset - 1; + } else { + return null; + } + int start= probe; + while (start > lineStart && isIdentifierPart(document.getChar(start - 1))) { + start--; + } + int end= probe + 1; + while (end < lineEnd && isIdentifierPart(document.getChar(end))) { + end++; + } + return new Region(start, end - start); + } catch (BadLocationException e) { + return null; + } + } + + private static boolean isIdentifierPart(char c) { + return c == '_' || (c < 128 && (c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')); + } + /** * Returns the locale specific word break iterator. * diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java index 450d94d729b..4443dab5d37 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/DefaultTextDoubleClickStrategyTest.java @@ -55,6 +55,70 @@ public void testClickAtLineEnd() throws Exception { assertEquals("you", document.get(selection.getOffset(), selection.getLength()), "Unexpected selection"); } + @Test + public void testClickJustPastIdentifierSelectsThatIdentifier() throws Exception { + String content= "foo bar baz"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy strategy= new TestSpecificDefaultTextDoubleClickStrategy(); + // Click at offset 3: the space right after "foo". + IRegion selection= strategy.findWord(document, 3); + assertNotNull(selection); + assertEquals("foo", document.get(selection.getOffset(), selection.getLength())); + } + + @Test + public void testClickAtIdentifierStartSelectsWholeIdentifier() throws Exception { + String content= "foo __aaaa bar"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy strategy= new TestSpecificDefaultTextDoubleClickStrategy(); + // Click at offset 4: the first '_' starting "__aaaa". + IRegion selection= strategy.findWord(document, 4); + assertNotNull(selection); + assertEquals("__aaaa", document.get(selection.getOffset(), selection.getLength())); + } + + @Test + public void testIdentifierAtLineStartAndEnd() throws Exception { + String content= "_foo___\nbar_baz"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy strategy= new TestSpecificDefaultTextDoubleClickStrategy(); + // First line: every offset 0..7 should yield "_foo___". + for (int offset= 0; offset <= 7; offset++) { + IRegion selection= strategy.findWord(document, offset); + assertNotNull(selection, "no selection at offset " + offset); + assertEquals("_foo___", document.get(selection.getOffset(), selection.getLength()), + "unexpected selection at offset " + offset); + } + // Second line. + IRegion selection= strategy.findWord(document, 11); + assertNotNull(selection); + assertEquals("bar_baz", document.get(selection.getOffset(), selection.getLength())); + } + + @Test + public void testSingleLineDocument() throws Exception { + String content= "abc"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy strategy= new TestSpecificDefaultTextDoubleClickStrategy(); + IRegion selection= strategy.findWord(document, 0); + assertNotNull(selection); + assertEquals("abc", document.get(selection.getOffset(), selection.getLength())); + selection= strategy.findWord(document, document.getLength()); + assertNotNull(selection); + assertEquals("abc", document.get(selection.getOffset(), selection.getLength())); + } + + @Test + public void testIdentifierSurroundedByPunctuation() throws Exception { + String content= "(foo_bar);"; + IDocument document= new Document(content); + TestSpecificDefaultTextDoubleClickStrategy strategy= new TestSpecificDefaultTextDoubleClickStrategy(); + // Click in the middle of the identifier. + IRegion selection= strategy.findWord(document, 4); + assertNotNull(selection); + assertEquals("foo_bar", document.get(selection.getOffset(), selection.getLength())); + } + private static final class TestSpecificDefaultTextDoubleClickStrategy extends DefaultTextDoubleClickStrategy { @Override