/* * Copyright 2012 Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.swtbot; import com.google.dart.tools.ui.swtbot.matchers.TableItemWithText; import com.google.dart.tools.ui.swtbot.performance.SwtBotPerformance; import org.eclipse.jface.action.IAction; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Widget; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.eclipse.finder.exceptions.QuickFixNotFoundException; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEclipseEditor; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.Finder; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.results.WidgetResult; import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition; import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; import org.eclipse.ui.internal.WorkbenchPartReference; import org.eclipse.ui.texteditor.ITextEditor; import org.hamcrest.Matcher; import static org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable.syncExec; import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; import static org.junit.Assert.fail; import java.util.Arrays; import java.util.List; /** * Helper for manipulating source code in an editor */ @SuppressWarnings("restriction") public class DartEditorHelper { private final SWTWorkbenchBot bot; private final DartLib app; private final SWTBotEclipseEditor editor; private final SWTBotStyledText styledText; public DartEditorHelper(SWTWorkbenchBot bot, DartLib app) { this.bot = bot; this.app = app; this.editor = app.editor; this.styledText = new SWTBotStyledText(widget()); } /** * Code completion action with the passed proposal text, the passed boolean indicates if the * completion is unique or not. This is important because the UX is different in these two cases, * in instances where it is unique (<code>true</code>), a table is not presented to the user, * instances where the completion is not unique (<code>false</code>), a table is presented to the * user. * * @param proposalText * @param uniqueCompletion <code>true</code> if there is only one possible completion, and * <code>false</code> otherwise */ public void codeComplete(String proposalText, boolean uniqueCompletion) { String simpleText = proposalText; if (simpleText.endsWith(".*")) { simpleText = simpleText.substring(0, simpleText.length() - 2); } simpleText = simpleText.replace("\\", ""); long start = System.currentTimeMillis(); activateAutoCompleteShell(); if (uniqueCompletion) { SwtBotPerformance.CODE_COMPLETION.log(start, simpleText); } else { WaitForObjectCondition<SWTBotTable> autoCompleteTable = autoCompleteAppears(tableWithRow(proposalText)); waitUntil(autoCompleteTable); SwtBotPerformance.CODE_COMPLETION.log(start, simpleText); selectProposal(autoCompleteTable.get(0), proposalText); } } public void codeComplete(String insertText, String proposalText, boolean uniqueCompletion) { editor.typeText(insertText); codeComplete(proposalText, uniqueCompletion); } public SWTBotEclipseEditor editor() { return editor; } /** * Moves the cursor to the end of the line containing the passed text. */ public void moveToEndOfLineContaining(String text) { moveToStartOfLineContaining(text); editor.pressShortcut(Keystrokes.DOWN, Keystrokes.LEFT); } /** * Position the cursor at the beginning of the line containing the specified text */ public void moveToStartOfLineContaining(String text) { int line = 0; while (true) { if (editor.getTextOnLine(line).contains(text)) { break; } if (line > 50) { fail("Could not find line in editor containing \"" + text + "\""); } line++; } editor.navigateTo(line, 0); } /** * Save the editor and log the time for incremental compilation. */ public void save(String... comments) { editor.save(); app.logFullAnalysisTime(comments); } /** * Type the specified text. Interpret '!' as request for code completion. * <p> * This method simply calls {@link DartEditorHelper#typeLine(String, boolean)} with * <code>true</code>. */ public void typeLine(String text) { typeLine(text, true); } /** * Type the specified text. Interpret '!' as request for code completion. The passed boolean * should be <code>true</code> if there is only one possible completion, and <code>false</code> * otherwise. */ public void typeLine(String text, boolean uniqueCompletion) { editor.pressShortcut(Keystrokes.LF); int start = 0; int end = text.indexOf('!'); while (end != -1) { editor.typeText(text.substring(start, end)); if (text.length() > end + 1 && text.charAt(end + 1) == '!') { editor.typeText("!"); end += 2; } else { // Trigger code completion at '!' start = end - 1; while (start > 0) { char ch = text.charAt(start); if (!Character.isLetter(ch) && !Character.isDigit(ch)) { break; } start--; } String proposalText = text.substring(start, end); end++; start = end; while (end < text.length()) { char ch = text.charAt(end); if (!Character.isLetter(ch) && !Character.isDigit(ch)) { break; } end++; } proposalText += text.substring(start, end); if (end < text.length() && text.charAt(end) == '(') { proposalText += "\\("; end++; } proposalText += ".*"; codeComplete(proposalText, uniqueCompletion); } start = end; end = text.indexOf('!', start); } editor.typeText(text.substring(start)); } /** * @param matcher a matcher. * @return a widget within the parent widget that matches the specified matcher. */ protected <S extends Widget> List<? extends S> findWidgets(Matcher<S> matcher) { Finder finder = bot.getFinder(); Control control = getControl(); boolean shouldFindInvisibleControls = finder.shouldFindInvisibleControls(); finder.setShouldFindInvisibleControls(true); try { return bot.widgets(matcher, control); } catch (Exception e) { throw new WidgetNotFoundException( "Could not find any control inside the view " + editor.getReference().getPartName(), e); //$NON-NLS-1$ } finally { finder.setShouldFindInvisibleControls(shouldFindInvisibleControls); } } private void activateAutoCompleteShell() { invokeAction("ContentAssistProposal"); } /** * This activates the popup shell. * * @return The shell. */ private SWTBotShell activatePopupShell() { try { Shell mainWindow = syncExec(new WidgetResult<Shell>() { @Override public Shell run() { return styledText.widget.getShell(); } }); SWTBotShell shell = bot.shell("", mainWindow); //$NON-NLS-1$ shell.activate(); return shell; } catch (Exception e) { throw new QuickFixNotFoundException("Quickfix popup not found. Giving up.", e); //$NON-NLS-1$ } } private WaitForObjectCondition<SWTBotTable> autoCompleteAppears(Matcher<SWTBotTable> tableMatcher) { return new WaitForObjectCondition<SWTBotTable>(tableMatcher) { @Override public String getFailureMessage() { return "Could not find auto complete proposal using matcher " + matcher; } @Override protected List<SWTBotTable> findMatches() { try { activateAutoCompleteShell(); SWTBotTable autoCompleteTable = getProposalTable(); if (matcher.matches(autoCompleteTable)) { return Arrays.asList(autoCompleteTable); } } catch (Throwable e) { //makeProposalsDisappear(); editor.setFocus(); } return null; } }; } /** * Returns the workbench pane control. * * @return returns the workbench pane control. */ private Control getControl() { return ((WorkbenchPartReference) editor.getReference()).getPane().getControl(); } /** * Gets the quick fix table. * * @param proposalShell the shell containing the quickfixes. * @return the table containing the quickfix. */ private SWTBotTable getProposalTable() { try { Table table = bot.widget(widgetOfType(Table.class), activatePopupShell().widget); return new SWTBotTable(table); } catch (Exception e) { throw new QuickFixNotFoundException("Quickfix options not found. Giving up.", e); //$NON-NLS-1$ } } private void invokeAction(final String actionId) { final IAction action = ((ITextEditor) editor.getReference().getEditor(false)).getAction(actionId); syncExec(new VoidResult() { @Override public void run() { action.run(); } }); } /** * Applies the specified quickfix. * * @param proposalTable the table containing the quickfix. * @param proposalIndex the index of the quickfix. */ private void selectProposal(final SWTBotTable proposalTable, final int proposalIndex) { UIThreadRunnable.asyncExec(new VoidResult() { @Override public void run() { Table table = proposalTable.widget; table.setSelection(proposalIndex); Event event = new Event(); event.type = SWT.Selection; event.widget = table; event.item = table.getItem(proposalIndex); table.notifyListeners(SWT.Selection, event); table.notifyListeners(SWT.DefaultSelection, event); } }); } /** * Attempt to apply the quick fix. * <p> * FIXME: this needs a lot of optimization. * </p> * * @param proposalTable the table containing the quickfix. * @param proposalText the name of the quickfix to apply. */ private void selectProposal(SWTBotTable proposalTable, String proposalText) { int index = TableItemWithText.indexOf(proposalTable, proposalText); if (index != -1) { selectProposal(proposalTable, index); return; } throw new QuickFixNotFoundException("Quickfix options not found. Giving up."); //$NON-NLS-1$ } private Matcher<SWTBotTable> tableWithRow(final String itemText) { return new TableItemWithText(itemText); } private void waitUntil(WaitForObjectCondition<SWTBotTable> table) { bot.waitUntil(table, 10000); } private StyledText widget() { List<? extends Widget> findWidgets = findWidgets(widgetOfType(StyledText.class)); return (StyledText) findWidgets.get(findWidgets.size() - 1); } }