/* * CodeSearch.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.workbench.codesearch; import java.util.ArrayList; import org.rstudio.core.client.FilePosition; import org.rstudio.core.client.files.FileSystemItem; import org.rstudio.core.client.widget.SearchDisplay; import org.rstudio.studio.client.application.events.EventBus; import org.rstudio.studio.client.common.filetypes.FileTypeRegistry; import org.rstudio.core.client.CodeNavigationTarget; import org.rstudio.studio.client.workbench.views.files.events.FileChangeEvent; import org.rstudio.studio.client.workbench.views.files.events.FileChangeHandler; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.SuggestBox; import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; public class CodeSearch { public interface Observer { String getCueText(); void onCompleted(); void onCancel(); } public interface Display { SearchDisplay getSearchDisplay(); void setCueText(String text); SuggestBox.DefaultSuggestionDisplay getSuggestionDisplay(); CodeSearchOracle getSearchOracle(); } @Inject public CodeSearch(Display display, final FileTypeRegistry fileTypeRegistry, final EventBus eventBus) { display_ = display; final SearchDisplay searchDisplay = display_.getSearchDisplay(); searchDisplay.setAutoSelectEnabled(true); searchDisplay.addSelectionHandler(new SelectionHandler<Suggestion>() { @Override public void onSelection(SelectionEvent<Suggestion> event) { CodeNavigationTarget target = display_.getSearchOracle().navigationTarget( searchDisplay.getLastValue(), event.getSelectedItem()); // create full file path and position String srcFile = target.getFile(); final FileSystemItem srcItem = FileSystemItem.createFile(srcFile); final FilePosition pos = target.getPosition(); // fire editing event (delayed so the Enter keystroke // doesn't get routed into the source editor) Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { display_.getSearchDisplay().clear(); display_.getSearchOracle().clear(); if (observer_ != null) observer_.onCompleted(); fileTypeRegistry.editFile(srcItem, pos); } }); } }); searchDisplay.addCloseHandler(new CloseHandler<SearchDisplay>() { @Override public void onClose(CloseEvent<SearchDisplay> event) { display_.getSearchDisplay().clear(); if (observer_ != null) observer_.onCancel(); } }); // various conditions invalidate the search oracle's cache searchDisplay.addBlurHandler(new BlurHandler() { @Override public void onBlur(BlurEvent event) { display_.getSearchOracle().clear(); } }); searchDisplay.addFocusHandler(new FocusHandler() { @Override public void onFocus(FocusEvent event) { display_.getSearchOracle().clear(); } }); eventBusHandlers_.add( eventBus.addHandler(FileChangeEvent.TYPE, new FileChangeHandler() { @Override public void onFileChange(FileChangeEvent event) { // if this was an R file then invalide the cache CodeSearchOracle oracle = display_.getSearchOracle(); if (oracle.hasCachedResults()) { FileSystemItem fsi = event.getFileChange().getFile(); if (fsi.getExtension().toLowerCase().equals(".r")) oracle.clear(); } } })); searchDisplay.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { boolean hasSearch = event.getValue().length() != 0; if (!hasSearch) { display_.getSearchOracle().invalidateSearches(); display_.getSuggestionDisplay().hideSuggestions(); } } }); searchDisplay.addKeyDownHandler(new KeyDownHandler() { @Override public void onKeyDown(KeyDownEvent event) { // eat key-up if the suggestions are showing (since the // suggestions menu is taking these and if we take it // the cursor will go to the beginning of the selection) if (display_.getSuggestionDisplay().isSuggestionListShowing() && (event.getNativeKeyCode() == KeyCodes.KEY_UP)) { event.preventDefault(); event.stopPropagation(); } } }); } public Widget getSearchWidget() { return (Widget) display_.getSearchDisplay(); } public void setObserver(Observer observer) { observer_ = observer; if (observer_ != null) { String cueText = observer_.getCueText(); if (cueText != null) display_.setCueText(cueText); } } // notify the CodeSearch that the search is completed so that it // can un-hook from EventBus events public void detachEventBusHandlers() { for (int i=0; i<eventBusHandlers_.size(); i++) eventBusHandlers_.get(i).removeHandler(); eventBusHandlers_.clear(); } private final Display display_; private Observer observer_ = null; private ArrayList<HandlerRegistration> eventBusHandlers_ = new ArrayList<HandlerRegistration>(); }