diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui.test/META-INF/MANIFEST.MF b/bundles/org.eclipse.cdt.lsp.editor.ui.test/META-INF/MANIFEST.MF
index 24a4866..d0456d6 100644
--- a/bundles/org.eclipse.cdt.lsp.editor.ui.test/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui.test/META-INF/MANIFEST.MF
@@ -7,6 +7,10 @@ Bundle-Vendor: Eclipse.org
Fragment-Host: org.eclipse.cdt.lsp.editor.ui;bundle-version="0.0.0"
Require-Bundle: org.eclipse.cdt.lsp,
junit-jupiter-api,
- org.yaml.snakeyaml
+ org.yaml.snakeyaml,
+ org.objenesis,
+ net.bytebuddy.byte-buddy,
+ net.bytebuddy.byte-buddy-agent,
+ org.mockito
Automatic-Module-Name: org.eclipse.cdt.lsp.editor.ui.test
Bundle-RequiredExecutionEnvironment: JavaSE-17
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui.test/src/org/eclipse/cdt/lsp/editor/ui/test/commands/LspEditorActiveTesterTest.java b/bundles/org.eclipse.cdt.lsp.editor.ui.test/src/org/eclipse/cdt/lsp/editor/ui/test/commands/LspEditorActiveTesterTest.java
new file mode 100644
index 0000000..1b83e5a
--- /dev/null
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui.test/src/org/eclipse/cdt/lsp/editor/ui/test/commands/LspEditorActiveTesterTest.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2023 COSEDA Technologies GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dominic Scharfe (COSEDA Technologies GmbH) - initial implementation
+ *******************************************************************************/
+package org.eclipse.cdt.lsp.editor.ui.test.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.eclipse.cdt.lsp.LspPlugin;
+import org.eclipse.cdt.lsp.editor.ui.commands.LspEditorActiveTester;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class LspEditorActiveTesterTest {
+
+ LspEditorActiveTester tut;
+
+ IEditorPart editorPart;
+ IEditorSite editorSite;
+
+ @BeforeEach
+ public void setup() {
+ tut = new LspEditorActiveTester();
+
+ editorPart = mock(IEditorPart.class);
+ editorSite = mock(IEditorSite.class);
+
+ when(editorPart.getSite()).thenReturn(editorSite);
+ }
+
+ /**
+ * Assert that a receiver which is not an editor part is not detected as c
+ * editor.
+ */
+ @Test
+ public void nonEditorPart() {
+ assertFalse(tut.test(new String("not an edit part"), LspEditorActiveTester.IS_LSP_CEDITOR_PROPERTY, null, tut));
+ }
+
+ /**
+ * Assert that tester only tests for the correct property, even if the given
+ * receiver is an {@link IEditorPart}.
+ */
+ @Test
+ public void editorPartNonMatchingProperty() {
+ assertFalse(tut.test(editorPart, "someOtherProperty", null, tut));
+ }
+
+ /**
+ * Assert that an {@link IEditorPart} with some other id is not detected as c editor.
+ */
+ @Test
+ public void matchingEditorPartWithNonMatchingId() {
+ when(editorSite.getId()).thenReturn("someId");
+ assertTestResult(false);
+ }
+
+ /**
+ * Assert that an {@link IEditorPart} with the c editor id is detected as c editor.
+ */
+ @Test
+ public void matchingEditorPartWithMatchingId() {
+ when(editorSite.getId()).thenReturn(LspPlugin.LSP_C_EDITOR_ID);
+ assertTestResult(true);
+ }
+
+ /**
+ * Assert that an {@link IEditorPart} with an "inner editor" (part adapts to
+ * {@link ITextEditor.class}) is not detected as c editor when the inner editor
+ * doesn't have the c editor id.
+ */
+ @Test
+ public void editorPartWithInnerNonMatchingEditor() {
+ ITextEditor innerEditor = mock(ITextEditor.class);
+ IEditorSite innerEditorSite = mock(IEditorSite.class);
+ when(innerEditor.getSite()).thenReturn(innerEditorSite);
+ when(innerEditorSite.getId()).thenReturn("someId");
+ when(editorPart.getAdapter(ITextEditor.class)).thenReturn(innerEditor);
+ when(editorSite.getId()).thenReturn("someOtherId");
+
+ assertTestResult(false);
+ }
+
+ /**
+ * Assert that an {@link IEditorPart} with an "inner editor" (part adapts to
+ * {@link ITextEditor.class}) is detected as c editor when the inner editor
+ * has the c editor id.
+ */
+ @Test
+ public void editorPartWithInnerMatchingEditor() {
+ ITextEditor innerEditor = mock(ITextEditor.class);
+ IEditorSite innerEditorSite = mock(IEditorSite.class);
+ when(innerEditor.getSite()).thenReturn(innerEditorSite);
+ when(innerEditorSite.getId()).thenReturn(LspPlugin.LSP_C_EDITOR_ID);
+ when(editorPart.getAdapter(ITextEditor.class)).thenReturn(innerEditor);
+ when(editorSite.getId()).thenReturn("someOtherId");
+
+ assertTestResult(true);
+ }
+
+ /**
+ * Helper method.
+ */
+ private void assertTestResult(boolean expectedResult) {
+ assertEquals(expectedResult, tut.test(editorPart, LspEditorActiveTester.IS_LSP_CEDITOR_PROPERTY, null, tut));
+ }
+
+}
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/META-INF/MANIFEST.MF b/bundles/org.eclipse.cdt.lsp.editor.ui/META-INF/MANIFEST.MF
index cfd2bfd..d47f95f 100644
--- a/bundles/org.eclipse.cdt.lsp.editor.ui/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/META-INF/MANIFEST.MF
@@ -5,6 +5,7 @@ Bundle-SymbolicName: org.eclipse.cdt.lsp.editor.ui;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: org.eclipse.cdt.lsp.editor.ui.LspEditorUiPlugin
Bundle-Vendor: Eclipse.org
+Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.cdt.lsp,
@@ -15,7 +16,10 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.ui.editors,
org.eclipse.cdt.core,
org.eclipse.jface.text,
- org.yaml.snakeyaml
+ org.yaml.snakeyaml,
+ org.eclipse.lsp4j,
+ org.eclipse.lsp4e,
+ org.eclipse.lsp4j.jsonrpc
Bundle-RequiredExecutionEnvironment: JavaSE-17
Automatic-Module-Name: org.eclipse.cdt.lsp.editor.ui
Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/build.properties b/bundles/org.eclipse.cdt.lsp.editor.ui/build.properties
index e9863e2..0dc34f7 100644
--- a/bundles/org.eclipse.cdt.lsp.editor.ui/build.properties
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/build.properties
@@ -2,4 +2,5 @@ source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
- plugin.xml
+ plugin.xml,\
+ plugin.properties
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.properties b/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.properties
new file mode 100644
index 0000000..da72f0d
--- /dev/null
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.properties
@@ -0,0 +1,2 @@
+Commands.ToggleSourceAndHeader.name=Toggle Source/Header
+PopupMenu.ToggleSourceAndHeader.label=Toggle Source/Header
\ No newline at end of file
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.xml b/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.xml
index 496f9fa..00ccc98 100644
--- a/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.xml
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/plugin.xml
@@ -37,6 +37,13 @@
properties="LspEditorPreference"
type="java.lang.Object">
+
+
@@ -52,4 +59,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/LspEditorActiveTester.java b/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/LspEditorActiveTester.java
new file mode 100644
index 0000000..60d903a
--- /dev/null
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/LspEditorActiveTester.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2023 COSEDA Technologies GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dominic Scharfe (COSEDA Technologies GmbH) - initial implementation
+ *******************************************************************************/
+package org.eclipse.cdt.lsp.editor.ui.commands;
+
+import java.util.Optional;
+
+import org.eclipse.cdt.lsp.LspPlugin;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.core.runtime.Adapters;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+/**
+ * Property tester for checking if a receiver is a c editor. Will evaluate
+ * {@code true} if the receiver can adapt to {@link ITextEditor} and has the c
+ * editor id.
+ */
+public class LspEditorActiveTester extends PropertyTester {
+ public final static String IS_LSP_CEDITOR_PROPERTY = "isLspCEditor";
+
+ @Override
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart && IS_LSP_CEDITOR_PROPERTY.equals(property)) {
+ IEditorPart innerEditor = Optional.ofNullable((IEditorPart) Adapters.adapt(receiver, ITextEditor.class))
+ .orElse((IEditorPart) receiver);
+
+ return LspPlugin.LSP_C_EDITOR_ID.equals(innerEditor.getSite().getId());
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/ToggleSourceAndHeaderCommandHandler.java b/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/ToggleSourceAndHeaderCommandHandler.java
new file mode 100644
index 0000000..fd0e7ac
--- /dev/null
+++ b/bundles/org.eclipse.cdt.lsp.editor.ui/src/org/eclipse/cdt/lsp/editor/ui/commands/ToggleSourceAndHeaderCommandHandler.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2023 COSEDA Technologies GmbH and others.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dominic Scharfe (COSEDA Technologies GmbH) - initial implementation
+ *******************************************************************************/
+package org.eclipse.cdt.lsp.editor.ui.commands;
+
+import java.net.URI;
+import java.util.Optional;
+
+import org.eclipse.cdt.lsp.LspPlugin;
+import org.eclipse.cdt.lsp.editor.ui.LspEditorUiPlugin;
+import org.eclipse.cdt.lsp.services.ClangdLanguageServer;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IHandler;
+import org.eclipse.core.runtime.Adapters;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.handlers.HandlerUtil;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.statushandlers.StatusManager;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+public class ToggleSourceAndHeaderCommandHandler extends AbstractHandler implements IHandler {
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ return execute(HandlerUtil.getActiveEditor(event));
+ }
+
+ @SuppressWarnings("restriction")
+ private Object execute(IEditorPart activeEditor) {
+ // Try to adapt to ITextEditor (e.g. to support editors embedded in
+ // MultiPageEditorParts), otherwise use activeEditor.
+ IEditorPart innerEditor = Optional.ofNullable((IEditorPart) Adapters.adapt(activeEditor, ITextEditor.class))
+ .orElse(activeEditor);
+
+ getUri(innerEditor).ifPresent(fileUri -> {
+ IDocument document = org.eclipse.lsp4e.LSPEclipseUtils.getDocument(innerEditor.getEditorInput());
+ org.eclipse.lsp4e.LanguageServers.forDocument(document)
+ .computeFirst(
+ server -> server instanceof ClangdLanguageServer
+ ? ((ClangdLanguageServer) server)
+ .switchSourceHeader(new TextDocumentIdentifier(fileUri.toString()))
+ : null)
+ .thenAccept(otherFileUri -> otherFileUri
+ .ifPresent(uri -> openEditor(innerEditor.getEditorSite().getPage(), URI.create(uri))));
+ });
+
+ return null;
+ }
+
+ /**
+ * Returns the URI of the given editor depending on the type of its
+ * {@link IEditorPart#getEditorInput()}.
+ *
+ * @return the URI or an empty {@link Optional} if the URI couldn't be
+ * determined.
+ */
+ private static Optional getUri(IEditorPart editor) {
+ IEditorInput editorInput = editor.getEditorInput();
+
+ if (editorInput instanceof IFileEditorInput) {
+ return Optional.of(((IFileEditorInput) editor.getEditorInput()).getFile().getLocationURI());
+ } else if (editorInput instanceof IURIEditorInput) {
+ return Optional.of(((IURIEditorInput) editorInput).getURI());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ private static void openEditor(IWorkbenchPage page, URI fileUri) {
+ page.getWorkbenchWindow().getShell().getDisplay().asyncExec(() -> {
+ try {
+ IDE.openEditor(page, fileUri, LspPlugin.LSP_C_EDITOR_ID, true);
+ } catch (PartInitException e) {
+ StatusManager.getManager().handle(e, LspEditorUiPlugin.PLUGIN_ID);
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.cdt.lsp/META-INF/MANIFEST.MF b/bundles/org.eclipse.cdt.lsp/META-INF/MANIFEST.MF
index 0442cba..5455ab0 100644
--- a/bundles/org.eclipse.cdt.lsp/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.cdt.lsp/META-INF/MANIFEST.MF
@@ -4,7 +4,8 @@ Bundle-Name: Language Server based C/C++ Editor
Bundle-SymbolicName: org.eclipse.cdt.lsp;singleton:=true
Bundle-Version: 1.0.0.qualifier
Export-Package: org.eclipse.cdt.lsp,
- org.eclipse.cdt.lsp.server
+ org.eclipse.cdt.lsp.server,
+ org.eclipse.cdt.lsp.services
Bundle-Activator: org.eclipse.cdt.lsp.LspPlugin
Bundle-Vendor: Eclipse.org
Require-Bundle: org.eclipse.ui,
@@ -22,7 +23,9 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.ui.workbench,
org.eclipse.core.expressions,
org.eclipse.jface.text,
- org.eclipse.debug.ui
+ org.eclipse.debug.ui,
+ org.eclipse.lsp4j,
+ org.eclipse.lsp4j.jsonrpc
Bundle-RequiredExecutionEnvironment: JavaSE-17
Automatic-Module-Name: org.eclipse.cdt.lsp
Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.eclipse.cdt.lsp/plugin.xml b/bundles/org.eclipse.cdt.lsp/plugin.xml
index 280c738..30cc1d4 100644
--- a/bundles/org.eclipse.cdt.lsp/plugin.xml
+++ b/bundles/org.eclipse.cdt.lsp/plugin.xml
@@ -34,6 +34,7 @@
class="org.eclipse.cdt.lsp.internal.server.CLanguageServerStreamConnectionProvider"
id="org.eclipse.cdt.lsp.server"
label="C/C++ Language Server"
+ serverInterface="org.eclipse.cdt.lsp.services.ClangdLanguageServer"
singleton="true">
+ * get the corresponding header if a source file was provided
+ * get the source file if a header was provided
+ *
+ *
+ * @param textDocument open file
+ * @return URI of the corresponding header/source file
+ *
+ * @see https://clangd.llvm.org/extensions#switch-between-sourceheader
+ */
+ @JsonRequest(value = "textDocument/switchSourceHeader")
+ CompletableFuture switchSourceHeader(TextDocumentIdentifier textDocument);
+}
diff --git a/releng/org.eclipse.cdt.lsp.target/org.eclipse.cdt.lsp.target.target b/releng/org.eclipse.cdt.lsp.target/org.eclipse.cdt.lsp.target.target
index 75ffb2a..f496ed5 100644
--- a/releng/org.eclipse.cdt.lsp.target/org.eclipse.cdt.lsp.target.target
+++ b/releng/org.eclipse.cdt.lsp.target/org.eclipse.cdt.lsp.target.target
@@ -45,6 +45,8 @@
+
+
\ No newline at end of file