blob: 0bff1114ea472d4b79f5f2326f53433442943597 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 École Polytechnique de Montréal
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.tracecompass.incubator.internal.filters.ui.lspFilterTextbox;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.window.DefaultToolTip;
import org.eclipse.lsp4j.ColorInformation;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.tracecompass.incubator.internal.filters.core.client.wrapper.LanguageFilterClientWrapper;
import org.eclipse.tracecompass.incubator.internal.filters.core.shared.LspObserver;
/**
* Widget to wrap a FilterText widget with additional logic of a filter lsp
* client
*
* @author Jeremy Dube
*/
public class LspFilterTextbox implements LspObserver {
private final String fFilterBoxUri;
private @Nullable LanguageFilterClientWrapper fLspClient;
private List<FilterValidityListener> fListeners = new ArrayList<>();
private final Color fDefaultFilterTextColor;
private final Color fDefaultFilterBackgroundColor;
private final StyledText fFilterStyledText;
private final TextViewer fTextViewer;
private final CLabel fSearchButton;
private final CLabel fCancelButton;
private final RecentlyUsedFilters fRecentlyUsedFilters;
private String fLastTextUpdate;
private final DefaultToolTip fToolTip;
private Boolean fIsValidString = false;
private List<ColorInformation> fColors = new ArrayList<>();
private List<Diagnostic> fDiagnostics = new ArrayList<>();
/**
* Constructor
*
* @param parent
* the parent view
* @param filterBoxUri
* the uri of the lsp client
*/
public LspFilterTextbox(Composite parent, String filterBoxUri) {
fFilterBoxUri = filterBoxUri;
final Composite baseComposite = new Composite(parent, SWT.BORDER);
baseComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
final GridLayout baseCompositeGridLayout = new GridLayout(3, false);
baseCompositeGridLayout.marginHeight = 0;
baseCompositeGridLayout.marginWidth = 0;
baseComposite.setLayout(baseCompositeGridLayout);
// Search icon
fSearchButton = new CLabel(baseComposite, SWT.CENTER);
fSearchButton.setLayoutData(GridDataFactory.fillDefaults().grab(false, true).create());
fSearchButton.setText("search"); //$NON-NLS-1$ // Will be changed for an image
// Text box
fTextViewer = new TextViewer(baseComposite, SWT.SINGLE | SWT.BORDER);
fFilterStyledText = fTextViewer.getTextWidget();
fFilterStyledText.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
// Cancel icon
fCancelButton = new CLabel(baseComposite, SWT.CENTER);
fCancelButton.setLayoutData(GridDataFactory.fillDefaults().grab(false, true).create());
fCancelButton.setText("clear"); //$NON-NLS-1$ // Will be changed for an image
setIconsListener();
setKeyListener();
fDefaultFilterBackgroundColor = fFilterStyledText.getBackground();
fLspClient = new LanguageFilterClientWrapper(this, fFilterBoxUri);
fDefaultFilterTextColor = fFilterStyledText.getForeground();
fRecentlyUsedFilters = new RecentlyUsedFilters(5, "GlobalFilterViewer"); //$NON-NLS-1$
// TODO: To combine with the completion items once available
// List<String> filterStrings = fRecentlyUsedFilters.getRecently();
fToolTip = new DefaultToolTip(fFilterStyledText);
styleToolTip();
}
/**
* Method to set the focus on the FilterText
*
* @return true if the FilterText gets the focus
*/
public boolean setFocus() {
return fFilterStyledText.setFocus();
}
/**
* Method to return the text in the Filter Box
*
* @return the text
*/
public String getText() {
return fFilterStyledText.getText();
}
/**
* Method to set the text in the Filter Box
*
* @param text
* the value to set to
*/
public void setText(String text) {
fFilterStyledText.setText(text);
}
/**
* Method to add an external listener when the string is valid
*
* @param validListener
* the listener to add
*/
public void addValidListener(FilterValidityListener validListener) {
fListeners.add(validListener);
}
/**
* Method to add some custom style to the toolTip
*/
private void styleToolTip() {
fToolTip.setBackgroundColor(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
fToolTip.setForegroundColor(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
fToolTip.setShift(new Point(0, 10));
fToolTip.deactivate();
}
/**
* Method to notify listeners of valid string
*/
private void notifyValid() {
fRecentlyUsedFilters.addFilter(fFilterStyledText.getText());
if (fIsValidString) {
for (FilterValidityListener validListener : fListeners) {
validListener.validFilter();
}
}
}
/**
* Method to notify listeners of invalid string
*/
private void notifyInvalid() {
for (FilterValidityListener validListener : fListeners) {
validListener.invalidFilter();
}
}
/**
* Method to add a key Listener to the FilterText Widget
*/
private void setKeyListener() {
fFilterStyledText.addKeyListener(new KeyListener() {
@Override
public void keyReleased(@Nullable KeyEvent e) {
if (e == null) {
return;
}
// Get the filterbox texte
String text = Objects.requireNonNull(fFilterStyledText.getText());
// If text has not change since last update from autocompletion,
// do nothing
// Also if the last key-up is not backspace and delete,
if (e.character != SWT.BS && e.character != SWT.DEL && !text.equals(fLastTextUpdate)) {
// it means a new character has been inserted, so try to
// autocomplete it
Integer cursorPosition = fFilterStyledText.getCaretOffset();
String newText = FilterBoxLocalTextCompletion.autocomplete(text, cursorPosition);
// If the autocompletion changed the string
if (!newText.equals(text)) {
// Update the filtertextbox
fFilterStyledText.setText(newText);
fFilterStyledText.setCaretOffset(cursorPosition);
text = newText;
}
}
fLastTextUpdate = text;
notifyLspClient(text);
}
@Override
public void keyPressed(@Nullable KeyEvent e) {
if (e == null) {
return;
}
if (e.character == SWT.CR) {
notifyValid();
}
}
});
}
/**
* Method to add a mouse Listener to the icons
*/
private void setIconsListener() {
fSearchButton.addMouseListener(new MouseListener() {
@Override
public void mouseUp(MouseEvent e) {
notifyValid();
}
@Override
public void mouseDown(MouseEvent e) {
// Nothing to do here
}
@Override
public void mouseDoubleClick(MouseEvent e) {
// Nothing to do here
}
});
fCancelButton.addMouseListener(new MouseListener() {
@Override
public void mouseUp(MouseEvent e) {
fFilterStyledText.setText(StringUtils.EMPTY);
resetView();
}
@Override
public void mouseDown(MouseEvent e) {
// Nothing to do here
}
@Override
public void mouseDoubleClick(MouseEvent e) {
// Nothing to do here
}
});
}
/**
* Method called by the lsp client to notify the view of errors
*/
@Override
public void diagnostic(String uri, List<Diagnostic> diagnostics) {
if (fFilterBoxUri.equals(uri)) {
Display.getDefault().syncExec(new Runnable() {
@Override()
public void run() {
fDiagnostics = diagnostics;
if (fDiagnostics.size() > 0) {
updateView();
fIsValidString = false;
notifyInvalid();
addErrorPopup();
} else {
fToolTip.deactivate();
fIsValidString = true;
}
}
});
}
}
/**
* Method called by the lsp client to notify the view of completion items
*/
@Override
public void completion(String uri, final Either<List<CompletionItem>, CompletionList> completion) {
if (fFilterBoxUri.equals(uri)) {
Display.getDefault().syncExec(new Runnable() {
@Override()
public void run() {
// TODO: Needs to be implemented and linked with the dropdown
/*List<CompletionItem> completions = completion.getLeft();
for (int i = 0; i < completions.size(); i++) {
TextEdit textEdit = completions.get(i).getTextEdit();
String suggestion = textEdit.getNewText();
// this need to be link with the dropdown
System.out.println("Please do something with this variable " + suggestion);
}*/
}
});
}
}
/**
* Method called by the lsp client to notify the view of colors' definition
*/
@Override
public void syntaxHighlighting(String uri, List<ColorInformation> colors) {
if (fFilterBoxUri.equals(uri)) {
Display.getDefault().syncExec(new Runnable() {
@Override()
public void run() {
fColors = colors;
updateView();
}
});
}
}
/**
* Method to notify the LSP Client of a change
*
* @param message
* string entered in the filter box
*/
private void notifyLspClient(String message) {
if (message.isEmpty()) {
resetView();
} else {
if (fLspClient != null) {
fLspClient.notify(fFilterBoxUri, message, fFilterStyledText.getCaretOffset());
}
}
}
/**
* Method to reset the filter box view (i.e. put back initial color, remove
* error message, remove suggestions)
*/
private void resetView() {
fFilterStyledText.setBackground(fDefaultFilterBackgroundColor);
}
/**
* Method to add a popup on hover of the range to show the error message
*
* @param diagnostic
*/
private void addErrorPopup() {
fToolTip.activate();
String toolTipText = ""; //$NON-NLS-1$
int index = 0;
for (Diagnostic diagnostic : fDiagnostics) {
int start = diagnostic.getRange().getStart().getCharacter() + 1;
toolTipText += "Error at character " + start + " : " + diagnostic.getMessage(); //$NON-NLS-1$//$NON-NLS-2$
index++;
if (index < fDiagnostics.size()) {
toolTipText += "\n"; //$NON-NLS-1$
}
}
fToolTip.setText(toolTipText);
}
/**
* Method to call when an update to the text is needed
*/
private void updateView() {
for (int index = 0; index < fFilterStyledText.getText().length(); index++) {
Color foregroundColor = getColor(index);
Boolean hasError = indexIsError(index);
updateViewBetween(index, index + 1, foregroundColor, hasError);
}
}
/**
* Method to update the view between the specified indexes
*
* @param start
* the start index
* @param end
* the end index
* @param foregroundColor
* the color of the text
* @param hasError
* if the range has an error, the underlining will be added
*/
private void updateViewBetween(int start, int end, Color foregroundColor, Boolean hasError) {
StyleRange styleRange = new StyleRange();
styleRange.start = start;
styleRange.length = end - start;
styleRange.foreground = foregroundColor;
if (hasError) {
styleRange.underline = true;
styleRange.underlineColor = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
}
fFilterStyledText.setStyleRange(styleRange);
}
/**
* Method to check if the index passed is inside an error range
*
* @param index
* the index to check
* @return true if index is inside error range, false otherwise
*/
private boolean indexIsError(int index) {
for (Diagnostic diagnostic : fDiagnostics) {
int start = diagnostic.getRange().getStart().getCharacter();
int end = diagnostic.getRange().getEnd().getCharacter() - 1;
if (index >= start && index <= end) {
return true;
}
}
return false;
}
/**
* Get the color at the specified index. Looks into all the received color
* information items
*
* @param index
* the index to check
* @return the color, either gotten inside a color information item or the
* default text color
*/
private Color getColor(int index) {
for (ColorInformation colorInformation : fColors) {
int start = colorInformation.getRange().getStart().getCharacter();
int end = colorInformation.getRange().getEnd().getCharacter();
if (index >= start && index <= end) {
Device device = Display.getCurrent();
Color color = new Color(device, (int) (colorInformation.getColor().getRed() * 255),
(int) (colorInformation.getColor().getGreen() * 255),
(int) (colorInformation.getColor().getBlue() * 255));
return color;
}
}
return fDefaultFilterTextColor;
}
}