From 30c3e79af1f05e95121718b4e9d4b5fd66b2678d Mon Sep 17 00:00:00 2001 From: Sebastian Thomschke Date: Wed, 15 Jan 2025 23:41:15 +0100 Subject: [PATCH] feat: add "Copy Cell Value" context menu item to LanguageServers view --- .../eclipse/lsp4e/ui/LanguageServersView.java | 123 +++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LanguageServersView.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LanguageServersView.java index 31019164c..0ea2183c3 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LanguageServersView.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/ui/LanguageServersView.java @@ -35,10 +35,20 @@ import org.eclipse.lsp4e.LanguageServerWrapper; import org.eclipse.lsp4e.LanguageServiceAccessor; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.TableCursor; import org.eclipse.swt.custom.TableEditor; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; @@ -57,6 +67,7 @@ public class LanguageServersView extends ViewPart { private final Map actionButtons = new HashMap<>(); private final List columnLabelProviders = new ArrayList<>(); + private @Nullable TableCursor tableCursor; private int tableSortColumn = 1; private int tableSortDirection = 1; // 1 = ascending, -1 = descending private final ViewerComparator tableSorter = new ViewerComparator() { @@ -80,6 +91,27 @@ public int compare(final @Nullable Viewer viewer, @Nullable final Object e1, @Nu } }; + private void copyCurrentCellToClipboard() { + final TableCursor cursor = this.tableCursor; + if (cursor == null) + return; + final Table table = viewer.getTable(); + + final TableItem currentRow = cursor.getRow(); + final int currentColumn = cursor.getColumn(); + if (currentRow != null && currentColumn >= 0) { + final String cellContent = currentRow.getText(currentColumn); + if (cellContent != null && !cellContent.isEmpty()) { + final var clipboard = new Clipboard(table.getDisplay()); + try { + clipboard.setContents(new Object[] { cellContent }, new Transfer[] { TextTransfer.getInstance() }); + } finally { + clipboard.dispose(); + } + } + } + } + private void createColumn(String name, int width, ColumnLabelProvider labelProvider) { final var viewerColumn = new TableViewerColumn(viewer, SWT.NONE); final var tableColumn = viewerColumn.getColumn(); @@ -118,7 +150,7 @@ public void createPartControl(Composite parent) { createColumn(EMPTY, 26, new ColumnLabelProvider() { @Override public void update(final ViewerCell cell) { - if(cell.getElement() instanceof LanguageServerWrapper lsWrapper) { + if (cell.getElement() instanceof LanguageServerWrapper lsWrapper) { final var item = (TableItem) cell.getItem(); final var buttons = actionButtons.computeIfAbsent(lsWrapper, unused -> { final var toolBar = new ToolBar((Composite) cell.getViewerRow().getControl(), SWT.FLAT); @@ -213,6 +245,8 @@ public String getText(Object element) { viewer.setContentProvider(new ArrayContentProvider()); + initContextMenu(); + scheduleRefreshJob(); } @@ -224,6 +258,73 @@ public void dispose() { super.dispose(); } + private void initContextMenu() { + // Avoiding the use of MenuManager, table.setMenu(contextMenu), and + // SWT.MenuDetect because the table's SWT.FULL_SELECTION interferes with + // right-click detection, causing inconsistent behavior and preventing the + // context menu from being displayed reliably. + final Table table = viewer.getTable(); + final var contextMenu = new Menu(table); + final var copyValueMenuItem = new MenuItem(contextMenu, SWT.NONE); + copyValueMenuItem.setText("Copy Cell Value"); //$NON-NLS-1$ + copyValueMenuItem.addListener(SWT.Selection, event -> copyCurrentCellToClipboard()); + table.addListener(SWT.MouseDown, event -> { + if (event.button == 3 /* Right-click? */ + && viewer.getCell(new Point(event.x, event.y)) != null /* Cell selected? */) { + contextMenu.setVisible(true); + } + }); + } + + private void initTableCursor() { + final TableCursor cursorOld = this.tableCursor; + if (cursorOld != null && !cursorOld.isDisposed()) { + cursorOld.dispose(); + } + final Table table = viewer.getTable(); + + /* + * enable single cell selection via mouse and cell navigation via keyboard + */ + final var cursor = this.tableCursor = new TableCursor(table, SWT.NONE); + cursor.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(final SelectionEvent e) { + selectCell(table.indexOf(cursor.getRow()), cursor.getColumn()); + } + }); + + /* + * enable CTRL+C to copy cell content + */ + cursor.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(final KeyEvent e) { + if ((e.stateMask & SWT.CTRL) != 0 && e.keyCode == 'c') { // Check for CTRL+C + copyCurrentCellToClipboard(); + } + } + }); + } + + private void selectCell(int rowIdx, int colIdx) { + final var cursor = this.tableCursor; + if (cursor == null) + return; + final var table = viewer.getTable(); + if (table.getItemCount() == 0) + return; + + rowIdx = Math.max(0, Math.min(rowIdx, table.getItemCount() - 1)); + colIdx = Math.max(1 /* exclude action buttons column */, Math.min(colIdx, table.getColumnCount() - 1)); + + table.setSelection(rowIdx); + + cursor.setSelection(rowIdx, colIdx); + cursor.setVisible(true); + cursor.setFocus(); + } + private void scheduleRefreshJob() { final var viewerRefreshJob = this.viewerRefreshJob = new Job("Refresh Language Server Processes view") { //$NON-NLS-1$ @Override @@ -231,7 +332,7 @@ protected IStatus run(IProgressMonitor monitor) { if (getSite().getPage().isPartVisible(LanguageServersView.this)) { updateViewerInput(); } - schedule(2000); + schedule(2_000); return Status.OK_STATUS; } }; @@ -252,7 +353,25 @@ private void updateViewerInput() { UI.getDisplay().execute(() -> { actionButtons.values().forEach(Widget::dispose); actionButtons.clear(); + + final var table = viewer.getTable(); + + // save TableCursor position + int selectedRow = Math.max(0, table.getSelectionIndex()); + int selectedCol = 1; + final var cursor = this.tableCursor; + if (cursor != null) { + selectedCol = cursor.getColumn(); + } + viewer.setInput(newElements); + initTableCursor(); + + // restore TableCursor position + if (table.getSelectionIndex() > -1) { + selectedRow = table.getSelectionIndex(); + } + selectCell(selectedRow, selectedCol); }); } }