| /******************************************************************************* |
| * Copyright (c) 2007, 2018 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Michael Scharf (Wind River) - initial implementation |
| * Michael Scharf (Wing River) - [211659] Add field assist to terminal input field |
| * Michael Scharf (Wing River) - [196447] The optional terminal input line should be resizeable |
| * Martin Oberhuber (Wind River) - [168197] Fix Terminal for CDC-1.1/Foundation-1.1 |
| * Michael Scharf (Wing River) - [236458] Fix 168197 lost the last entry |
| * Anton Leherbauer (Wind River) - [220971] The optional terminal input line has redraw problems when resizing |
| *******************************************************************************/ |
| package org.eclipse.tm.internal.terminal.control; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.jface.fieldassist.IContentProposal; |
| import org.eclipse.jface.fieldassist.IContentProposalProvider; |
| import org.eclipse.jface.fieldassist.TextContentAdapter; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Sash; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; |
| |
| /** |
| * Manages the Command History for the command line input |
| * of the terminal control. |
| * <li> |
| * <ul>Navigate with ARROW_UP,ARROW_DOWN,PAGE_UP,PAGE_DOWN |
| * <ul>ESC to cancel history editing |
| * <ul>History can be edited (by moving up and edit) but changes are |
| * not persistent (like in bash). |
| * <ul>If the same command is entered multiple times in a row, |
| * only one entry is kept in the history. |
| * </li> |
| * |
| */ |
| public class CommandInputFieldWithHistory implements ICommandInputField { |
| private class FieldAssist implements IContentProposalProvider { |
| |
| @Override |
| public IContentProposal[] getProposals(String contents, int position) { |
| String prefix = contents.substring(0, position); |
| List<Proposal> result = new ArrayList<>(); |
| // show an entry only once |
| Set<String> seen = new HashSet<>(); |
| for (Iterator<String> iterator = fHistory.iterator(); iterator.hasNext();) { |
| String history = iterator.next(); |
| if (history.startsWith(prefix) && !seen.contains(history)) { |
| // the content is the rest of the history item |
| String content = history.substring(prefix.length()); |
| result.add(new Proposal(content, history)); |
| // don't add this proposal again |
| seen.add(history); |
| } |
| } |
| return result.toArray(new IContentProposal[result.size()]); |
| } |
| |
| } |
| |
| private static class Proposal implements IContentProposal { |
| |
| private final String fContent; |
| private final String fLabel; |
| |
| Proposal(String content, String label) { |
| fContent = content; |
| fLabel = label; |
| } |
| |
| @Override |
| public String getContent() { |
| return fContent; |
| } |
| |
| @Override |
| public String getLabel() { |
| return fLabel; |
| } |
| |
| @Override |
| public String getDescription() { |
| return null; |
| } |
| |
| @Override |
| public int getCursorPosition() { |
| return fContent.length(); |
| } |
| } |
| |
| final List<String> fHistory = new ArrayList<>(); |
| /** |
| * Keeps a modifiable history while in history editing mode |
| */ |
| List<Object> fEditedHistory; |
| /** |
| * The current position in the edit history |
| */ |
| private int fEditHistoryPos = 0; |
| /** |
| * The limit of the history. |
| */ |
| private final int fMaxSize; |
| /** |
| * The input text field. |
| */ |
| private Text fInputField; |
| private Sash fSash; |
| private Composite fPanel; |
| |
| public CommandInputFieldWithHistory(int maxHistorySize) { |
| fMaxSize = maxHistorySize; |
| } |
| |
| /** |
| * Add a line to the history. |
| * @param line The line to be added to the history. |
| */ |
| protected void pushLine(String line) { |
| endHistoryMode(); |
| // anything to remember? |
| if (line == null || line.trim().length() == 0) |
| return; |
| fHistory.add(0, line); |
| // ignore if the same as last |
| if (fHistory.size() > 1 && line.equals(fHistory.get(1))) |
| fHistory.remove(0); |
| // limit the history size. |
| if (fHistory.size() >= fMaxSize) |
| fHistory.remove(fHistory.size() - 1); |
| } |
| |
| /** |
| * Sets the history |
| * @param history or null |
| */ |
| public void setHistory(String history) { |
| endHistoryMode(); |
| fHistory.clear(); |
| if (history == null) |
| return; |
| // add history entries separated by '\n' |
| // fHistory.addAll(Arrays.asList(history.split("\n"))); //$NON-NLS-1$ |
| //<J2ME CDC-1.1 Foundation-1.1 variant> |
| StringTokenizer tok = new StringTokenizer(history, "\n"); //$NON-NLS-1$ |
| while (tok.hasMoreElements()) |
| fHistory.add((String) tok.nextElement()); |
| //</J2ME CDC-1.1 Foundation-1.1 variant> |
| } |
| |
| /** |
| * @return the current content of the history buffer and new line separated list |
| */ |
| public String getHistory() { |
| StringBuffer buff = new StringBuffer(); |
| boolean sep = false; |
| for (Iterator<String> iterator = fHistory.iterator(); iterator.hasNext();) { |
| String line = iterator.next(); |
| if (line.length() > 0) { |
| if (sep) |
| buff.append("\n"); //$NON-NLS-1$ |
| else |
| sep = true; |
| buff.append(line); |
| } |
| } |
| return buff.toString(); |
| } |
| |
| /** |
| * @param currLine Line of text to be moved in history |
| * @param count (+1 or -1) for forward and backward movement. -1 goes back |
| * @return the new string to be displayed in the command line or null, |
| * if the limit is reached. |
| */ |
| public String move(String currLine, int count) { |
| if (!inHistoryMode()) { |
| fEditedHistory = new ArrayList<>(fHistory.size() + 1); |
| fEditedHistory.add(currLine); |
| fEditedHistory.addAll(fHistory); |
| fEditHistoryPos = 0; |
| } |
| fEditedHistory.set(fEditHistoryPos, currLine); |
| if (fEditHistoryPos + count >= fEditedHistory.size()) |
| return null; |
| if (fEditHistoryPos + count < 0) |
| return null; |
| fEditHistoryPos += count; |
| return (String) fEditedHistory.get(fEditHistoryPos); |
| } |
| |
| private boolean inHistoryMode() { |
| return fEditedHistory != null; |
| } |
| |
| /** |
| * Exit the history movements and go to position 0; |
| * @return the string to be shown in the command line |
| */ |
| protected String escape() { |
| if (!inHistoryMode()) |
| return null; |
| String line = (String) fEditedHistory.get(0); |
| endHistoryMode(); |
| return line; |
| } |
| |
| /** |
| * End history editing |
| */ |
| private void endHistoryMode() { |
| fEditedHistory = null; |
| fEditHistoryPos = 0; |
| } |
| |
| @Override |
| public void createControl(final Composite parent, final ITerminalViewControl terminal) { |
| // fSash = new Sash(parent,SWT.HORIZONTAL|SWT.SMOOTH); |
| fSash = new Sash(parent, SWT.HORIZONTAL); |
| final GridData gd_sash = new GridData(SWT.FILL, SWT.CENTER, true, false); |
| gd_sash.heightHint = 5; |
| fSash.setLayoutData(gd_sash); |
| fSash.addListener(SWT.Selection, e -> { |
| if (e.detail == SWT.DRAG) { |
| // don't redraw during drag, it causes paint errors - bug 220971 |
| return; |
| } |
| // no idea why this is needed |
| GridData gdata = (GridData) fInputField.getLayoutData(); |
| Rectangle sashRect = fSash.getBounds(); |
| Rectangle containerRect = parent.getClientArea(); |
| |
| int h = fInputField.getLineHeight(); |
| // make sure the input filed height is a multiple of the line height |
| gdata.heightHint = Math.max(((containerRect.height - e.y - sashRect.height) / h) * h, h); |
| // do not show less then one line |
| e.y = Math.min(e.y, containerRect.height - h); |
| fInputField.setLayoutData(gdata); |
| parent.layout(); |
| // else the content assist icon will be replicated |
| parent.redraw(); |
| }); |
| fPanel = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| layout.marginTop = 0; |
| layout.marginBottom = 2; |
| fPanel.setLayout(layout); |
| fPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| fInputField = new Text(fPanel, SWT.MULTI | SWT.BORDER | SWT.WRAP | SWT.V_SCROLL); |
| GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); |
| boolean installDecoration = true; |
| if (installDecoration) { |
| // The ContentAssistCommandAdapter says: "The client is responsible for |
| // ensuring that adequate space is reserved for the decoration." |
| // TODO: what is the "adequate space"??? |
| data.horizontalIndent = 6; |
| } |
| fInputField.setLayoutData(data); |
| fInputField.setFont(terminal.getFont()); |
| // Register field assist *before* the key listener. |
| // Else the ENTER key is sent *first* to the input field |
| // and then to the field assist popup. |
| // (https://bugs.eclipse.org/bugs/show_bug.cgi?id=211659) |
| new ContentAssistCommandAdapter(fInputField, new TextContentAdapter(), new FieldAssist(), null, null, |
| installDecoration); |
| fInputField.addKeyListener(new KeyListener() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| // if the field assist has handled the key already then |
| // ignore it (https://bugs.eclipse.org/bugs/show_bug.cgi?id=211659) |
| if (!e.doit) |
| return; |
| if (e.character == SWT.CR || e.character == SWT.LF) { |
| e.doit = false; |
| String line = fInputField.getText(); |
| if (!terminal.pasteString(line + '\r')) |
| return; |
| pushLine(line); |
| setCommand("");//$NON-NLS-1$ |
| } else if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.PAGE_UP) { |
| e.doit = false; |
| setCommand(move(fInputField.getText(), 1)); |
| } else if (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.PAGE_DOWN) { |
| e.doit = false; |
| setCommand(move(fInputField.getText(), -1)); |
| } else if (e.keyCode == SWT.ESC) { |
| e.doit = false; |
| setCommand(escape()); |
| } |
| } |
| |
| private void setCommand(String line) { |
| if (line == null) |
| return; |
| fInputField.setText(line); |
| fInputField.setSelection(fInputField.getCharCount()); |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| } |
| }); |
| } |
| |
| @Override |
| public void setFont(Font font) { |
| fInputField.setFont(font); |
| fInputField.getParent().layout(true); |
| } |
| |
| @Override |
| public void dispose() { |
| fSash.dispose(); |
| fSash = null; |
| fPanel.dispose(); |
| fPanel = null; |
| fInputField.dispose(); |
| fInputField = null; |
| } |
| } |