8000 feat: support initial folding of license header by sebthom · Pull Request #1184 · eclipse-lsp4e/lsp4e · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: support initial folding of license header #1184

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,64 +12,99 @@

import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.test.utils.AbstractTest;
import org.eclipse.lsp4e.test.utils.TestUtils;
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
import org.eclipse.lsp4e.ui.FoldingPreferencePage;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeKind;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.tests.harness.util.DisplayHelper;
import org.junit.Test;

public class FoldingTest extends AbstractTest {

private static final int MAX_WAIT_FOR_FOLDING = 3_000;

private static final String CONTENT = """
/**
* SPDX-License-Identifier: EPL-2.0
*/
import
import
import
/**
* Some comment
*/
visible
""";

@Test
public void testImportsFoldedByDefaultEnabled() throws CoreException {
collapseImports(true);
public void testLicenseHeaderAutoFolding() throws CoreException {
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, true);
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS, false);
IEditorPart editor = createEditor();

// wait for folding to happen
TestUtils.waitForAndAssertCondition(MAX_WAIT_FOR_FOLDING, () -> assertEquals("""
/**
import
import
import
/**
* Some comment
*/
visible""", ((StyledText) editor.getAdapter(Control.class)).getText().trim()));
}

@Test
public void testImportsAutoFolding() throws CoreException {
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, false);
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS, true);

IEditorPart editor = createEditor();

// wait for folding to happen
DisplayHelper.waitAndAssertCondition(editor.getSite().getShell().getDisplay(),
() -> assertEquals("import\nvisible",
((StyledText) editor.getAdapter(Control.class)).getText().trim()));
TestUtils.waitForAndAssertCondition(MAX_WAIT_FOR_FOLDING, () -> assertEquals("""
/**
* SPDX-License-Identifier: EPL-2.0
*/
import
/**
* Some comment
*/
visible""", ((StyledText) editor.getAdapter(Control.class)).getText().trim()));
}

@Test
public void testImportsFoldedByDefaultDisabled() throws CoreException {
collapseImports(false);
public void testAutoFoldingDisabled() throws CoreException {
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, false);
configureCollapse(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS, false);
IEditorPart editor = createEditor();

// wait a few seconds before testing to ensure no folding happened
DisplayHelper.sleep(3000);
DisplayHelper.sleep(MAX_WAIT_FOR_FOLDING);
assertEquals(CONTENT, ((StyledText) editor.getAdapter(Control.class)).getText());
}

private IEditorPart createEditor() throws CoreException, PartInitException {
IFile file = TestUtils.createUniqueTestFile(null, CONTENT);
final var foldingRange = new FoldingRange(0, 2);
foldingRange.setKind(FoldingRangeKind.Imports);
MockLanguageServer.INSTANCE.setFoldingRanges(List.of(foldingRange));
IEditorPart editor = TestUtils.openEditor(file);
return editor;
private IEditorPart createEditor() throws CoreException {
final var foldingRangeLicense = new FoldingRange(0, 2);
foldingRangeLicense.setKind(FoldingRangeKind.Comment);
final var foldingRangeImport = new FoldingRange(3, 5);
foldingRangeImport.setKind(FoldingRangeKind.Imports);
MockLanguageServer.INSTANCE.setFoldingRanges(List.of(foldingRangeLicense, foldingRangeImport));

return TestUtils.openEditor(TestUtils.createUniqueTestFile(null, CONTENT));
}

private void collapseImports(boolean collapseImports) {
private void configureCollapse(String type, boolean collapse) {
IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore();
store.setValue("foldingReconcilingStrategy.collapseImports", collapseImports); //$NON-NLS-1$
store.setValue(type, collapse);
store.setValue(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS, collapse); //$NON-NLS-1$
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,24 @@ public static IEditorReference[] getEditors() {
return null;
}

public static void waitForAndAssertCondition(int timeout_ms, Runnable condition) {
waitForAndAssertCondition("Condition not met within expected time.", timeout_ms, condition);
}

public static void waitForAndAssertCondition(int timeout_ms, Display display, Runnable condition) {
waitForAndAssertCondition("Condition not met within expected time.", timeout_ms, display, () -> {
condition.run();
return true;
});
}

public static void waitForAndAssertCondition(String errorMessage, int timeout_ms, Runnable condition) {
waitForAndAssertCondition(errorMessage, timeout_ms, UI.getDisplay(), () -> {
condition.run();
return true;
});
}

public static void waitForAndAssertCondition(int timeout_ms, Condition condition) {
waitForAndAssertCondition("Condition not met within expected time.", timeout_ms, condition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -63,6 +64,9 @@ public class LSPFoldingReconcilingStrategy

private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];

private static final Pattern LICENSE_KEYWORDS = Pattern
.compile("(?i)(copyright|licensed under|all rights reserved|SPDX-License-Identifier)"); //$NON-NLS-1$
Copy link
Contributor
@rubenporras rubenporras Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this kind of folding not something that should be determined by de server, which can know more about the structure of the language?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, probably, but the LSP does specify such information at the moment; so we cannot use anything like it so far.

Copy link
Member Author
@sebthom sebthom Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was discussed/rejected here microsoft/language-server-protocol#1819

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks


private @Nullable IDocument document;
private @Nullable ProjectionAnnotationModel projectionAnnotationModel;
private @Nullable ProjectionViewer viewer;
Expand All @@ -72,6 +76,7 @@ public class LSPFoldingReconcilingStrategy
private final IPreferenceStore prefStore = LanguageServerPlugin.getDefault().getPreferenceStore();
private boolean isFoldingEnabled = prefStore.getBoolean(FoldingPreferencePage.PREF_FOLDING_ENABLED);
private boolean collapseComments = prefStore.getBoolean(FoldingPreferencePage.PREF_AUTOFOLD_COMMENTS);
private boolean collapseLicenseHeader = prefStore.getBoolean(FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS);
private boolean collapseFoldingRegions = prefStore.getBoolean(FoldingPreferencePage.PREF_AUTOFOLD_REGIONS);
private boolean collapseImports = prefStore.getBoolean(FoldingPreferencePage.PREF_AUTOFOLD_IMPORT_STATEMENTS);
private final IPropertyChangeListener foldingPrefsListener = (final PropertyChangeEvent event) -> {
Expand All @@ -89,6 +94,9 @@ public class LSPFoldingReconcilingStrategy
case FoldingPreferencePage.PREF_AUTOFOLD_COMMENTS:
collapseComments = Boolean.parseBoolean(newValue.toString());
break;
case FoldingPreferencePage.PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS:
collapseLicenseHeader = Boolean.parseBoolean(newValue.toString());
break;
case FoldingPreferencePage.PREF_AUTOFOLD_REGIONS:
collapseFoldingRegions = Boolean.parseBoolean(newValue.toString());
break;
Expand Down Expand Up @@ -187,22 +195,29 @@ private void applyFolding(@Nullable List<FoldingRange> ranges) {
markInvalidAnnotationsForDeletion(deletions, existing);

if (ranges != null) {
boolean[] isFirstFoldingRange = { true };
ranges.stream() //
.sorted(Comparator.comparing(FoldingRange::getEndLine)) //
.forEach(foldingRange -> {
try {
final var collapsByDefault = foldingRange.getKind() != null
&& switch (foldingRange.getKind()) {
case FoldingRangeKind.Comment -> collapseComments;
case FoldingRangeKind.Comment -> {
if (isFirstFoldingRange[0]
&& LICENSE_KEYWORDS.matcher(getTextOfFoldingRange(foldingRange)).find())
yield collapseLicenseHeader || collapseComments;
yield collapseComments;
}
case FoldingRangeKind.Imports -> collapseImports;
case FoldingRangeKind.Region -> collapseFoldingRegions;
default -> false;
};
updateAnnotation(deletions, existing, additions, foldingRange.getStartLine(),
foldingRange.getEndLine(), collapsByDefault);
} catch (BadLocationException e) {
// should never occur
} catch (BadLocationException ex) {
LanguageServerPlugin.logError(ex);
}
isFirstFoldingRange[0] = false;
});
}

Expand All @@ -219,6 +234,20 @@ && switch (foldingRange.getKind()) {
}
}

private String getTextOfFoldingRange(final FoldingRange range) {
final var doc = this.document;
if (doc != null) {
try {
final int offsetStart = doc.getLineOffset(range.getStartLine());
return doc.get(offsetStart,
doc.getLineOffset(range.getEndLine()) + doc.getLineLength(range.getEndLine()) - offsetStart);
} catch (BadLocationException ex) {
LanguageServerPlugin.logError(ex);
}
}
return ""; //$NON-NLS-1$
}

@Override
public void install(ITextViewer viewer) {
if (this.viewer != null) {
Expand Down
F438
5834
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class FoldingPreferencePage extends FieldEditorPreferencePage implements

public static final String PREF_FOLDING_ENABLED = "foldingReconcilingStrategy.enabled"; //$NON-NLS-1$
public static final String PREF_AUTOFOLD_COMMENTS = "foldingReconcilingStrategy.collapseComments"; //$NON-NLS-1$
public static final String PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS = "foldingReconcilingStrategy.collapseLicenseHeaders"; //$NON-NLS-1$
public static final String PREF_AUTOFOLD_REGIONS = "foldingReconcilingStrategy.collapseRegions"; //$NON-NLS-1$
public static final String PREF_AUTOFOLD_IMPORT_STATEMENTS = "foldingReconcilingStrategy.collapseImports"; //$NON-NLS-1$

Expand All @@ -35,6 +36,7 @@ public void initializeDefaultPreferences() {
final var store = LanguageServerPlugin.getDefault().getPreferenceStore();
store.setDefault(PREF_FOLDING_ENABLED, true);
store.setDefault(PREF_AUTOFOLD_COMMENTS, false);
store.setDefault(PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, false);
store.setDefault(PREF_AUTOFOLD_REGIONS, false);
store.setDefault(PREF_AUTOFOLD_IMPORT_STATEMENTS, false);
}
Expand All @@ -47,32 +49,61 @@ public FoldingPreferencePage() {

@Override
public void createFieldEditors() {
Composite parent = getFieldEditorParent();
final Composite parent = getFieldEditorParent();

// Add check boxes
addField(new BooleanFieldEditor( //
/*
* check box to globally enable/disable folding
*/
final var foldingEnabled = new BooleanFieldEditor( //
PREF_FOLDING_ENABLED, //
"Enable folding", //$NON-NLS-1$
parent));
parent);
addField(foldingEnabled);

// Add a label before the field editors
final var label = new Label(parent, SWT.NONE);
label.setText("Initially fold these elements:"); //$NON-NLS-1$
label.setText("\nInitially fold these elements:"); //$NON-NLS-1$
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

// Add check boxes
addField(new BooleanFieldEditor( //
/*
* check boxes to control auto-folding
*/
final var autoFoldComments = new BooleanFieldEditor( //
PREF_AUTOFOLD_COMMENTS, //
"Comments", //$NON-NLS-1$
parent));
addField(new BooleanFieldEditor( //
PREF_AUTOFOLD_COMMENTS, //
parent);
addField(autoFoldComments);

final var autoFoldLicenseHeaders = new BooleanFieldEditor( //
PREF_AUTOFOLD_LICENSE_HEADERS_COMMENTS, //
"License Header Comments", //$NON-NLS-1$
parent);
addField(autoFoldLicenseHeaders);

final var autoFoldRegions = new BooleanFieldEditor( //
PREF_AUTOFOLD_REGIONS, //
"Folding Regions", //$NON-NLS-1$
parent));
addField(new BooleanFieldEditor( //
parent);
addField(autoFoldRegions);

final var autoFoldImports = new BooleanFieldEditor( //
PREF_AUTOFOLD_IMPORT_STATEMENTS, //
"Import statements", //$NON-NLS-1$
parent));
parent);
addField(autoFoldImports);

/*
* update editor states
*/
final Runnable updateEditorStates = () -> {
autoFoldComments.setEnabled(foldingEnabled.getBooleanValue(), parent);
autoFoldLicenseHeaders.setEnabled(foldingEnabled.getBooleanValue() && !autoFoldComments.getBooleanValue(),
parent);
autoFoldRegions.setEnabled(foldingEnabled.getBooleanValue(), parent);
autoFoldImports.setEnabled(foldingEnabled.getBooleanValue(), parent);
};
foldingEnabled.getDescriptionControl(parent).addListener(SWT.Selection, event -> updateEditorStates.run());
autoFoldComments.getDescriptionControl(parent).addListener(SWT.Selection, event -> updateEditorStates.run());
UI.getDisplay().asyncExec(updateEditorStates);
}

@Override
Expand Down
Loading
0