/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.editor; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.ListResourceBundle; import java.util.Set; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.DeviceResourceException; import org.eclipse.jface.resource.FontDescriptor; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.LineNumberRulerColumn; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.editors.text.TextFileDocumentProvider; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.progress.UIJob; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.eclipse.ui.texteditor.ContentAssistAction; import org.eclipse.ui.texteditor.DefaultRangeIndicator; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.IEditorStatusLine; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.ITextEditorExtension2; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.osgi.framework.Version; import org.python.pydev.changed_lines.ChangedLinesComputer; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.FileUtilsFileBuffer; import org.python.pydev.core.ICodeCompletionASTManager; import org.python.pydev.core.IDefinition; import org.python.pydev.core.IGrammarVersionProvider; import org.python.pydev.core.IIndentPrefs; import org.python.pydev.core.IModulesManager; import org.python.pydev.core.IPyEdit; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.ITabChangedListener; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.NotConfiguredInterpreterException; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.docutils.PythonPairMatcher; import org.python.pydev.core.docutils.SyntaxErrorException; import org.python.pydev.core.log.Log; import org.python.pydev.core.partition.PyPartitionScanner; import org.python.pydev.editor.actions.FirstCharAction; import org.python.pydev.editor.actions.IExecuteLineAction; import org.python.pydev.editor.actions.OfflineAction; import org.python.pydev.editor.actions.OfflineActionTarget; import org.python.pydev.editor.actions.PyBackspace; import org.python.pydev.editor.actions.PyFormatStd; import org.python.pydev.editor.actions.PyFormatStd.FormatStd; import org.python.pydev.editor.actions.PyMoveLineDownAction; import org.python.pydev.editor.actions.PyMoveLineUpAction; import org.python.pydev.editor.actions.PyOpenAction; import org.python.pydev.editor.actions.PyOrganizeImports; import org.python.pydev.editor.actions.PyPeerLinker; import org.python.pydev.editor.autoedit.PyAutoIndentStrategy; import org.python.pydev.editor.codecompletion.revisited.CompletionCache; import org.python.pydev.editor.codecompletion.revisited.CompletionStateFactory; import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper; import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule; import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule; import org.python.pydev.editor.codecompletion.shell.AbstractShell; import org.python.pydev.editor.codefolding.CodeFoldingSetter; import org.python.pydev.editor.codefolding.PyEditProjection; import org.python.pydev.editor.codefolding.PySourceViewer; import org.python.pydev.editor.correctionassist.PythonCorrectionProcessor; import org.python.pydev.editor.model.ItemPointer; import org.python.pydev.editor.preferences.PydevEditorPrefs; import org.python.pydev.editor.refactoring.PyRefactoringFindDefinition; import org.python.pydev.editor.saveactions.PydevSaveActionsPrefPage; import org.python.pydev.editor.scripting.PyEditScripting; import org.python.pydev.editorinput.PyOpenEditor; import org.python.pydev.outline.ParsedModel; import org.python.pydev.outline.PyOutlinePage; import org.python.pydev.parser.PyParser; import org.python.pydev.parser.PyParserManager; import org.python.pydev.parser.fastparser.FastParser; import org.python.pydev.parser.fastparser.ScopesParser; import org.python.pydev.parser.jython.ParseException; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.stmtType; import org.python.pydev.parser.visitors.NodeUtils; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.plugin.preferences.CheckDefaultPreferencesDialog; import org.python.pydev.plugin.preferences.PyCodeFormatterPage; import org.python.pydev.plugin.preferences.PydevPrefs; import org.python.pydev.shared_core.callbacks.CallbackWithListeners; import org.python.pydev.shared_core.callbacks.ICallback; import org.python.pydev.shared_core.callbacks.ICallbackWithListeners; import org.python.pydev.shared_core.model.ErrorDescription; import org.python.pydev.shared_core.model.ISimpleNode; import org.python.pydev.shared_core.parsing.BaseParser.ParseOutput; import org.python.pydev.shared_core.parsing.BaseParserManager; import org.python.pydev.shared_core.parsing.ChangedParserInfoForObservers; import org.python.pydev.shared_core.parsing.ErrorParserInfoForObservers; import org.python.pydev.shared_core.parsing.IParserObserver3; import org.python.pydev.shared_core.parsing.IScopesParser; import org.python.pydev.shared_core.string.ICharacterPairMatcher2; import org.python.pydev.shared_core.string.TextSelectionUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_core.structure.Tuple3; import org.python.pydev.shared_interactive_console.console.ui.ScriptConsole; import org.python.pydev.shared_ui.EditorUtils; import org.python.pydev.shared_ui.ImageCache; import org.python.pydev.shared_ui.UIConstants; import org.python.pydev.shared_ui.editor.IPyEditListener; import org.python.pydev.shared_ui.editor_input.PydevFileEditorInput; import org.python.pydev.shared_ui.outline.IOutlineModel; import org.python.pydev.shared_ui.proposals.IPyCompletionProposal; import org.python.pydev.shared_ui.proposals.PyCompletionProposal; import org.python.pydev.shared_ui.utils.PyMarkerUtils; import org.python.pydev.shared_ui.utils.PyMarkerUtils.MarkerInfo; import org.python.pydev.shared_ui.utils.RunInUiThread; import org.python.pydev.ui.ColorAndStyleCache; import org.python.pydev.ui.dialogs.PyDialogHelpers; import org.python.pydev.ui.filetypes.FileTypesPreferencesPage; /** * The TextWidget. * * <p> * Ties together all the main classes in this plugin. * <li>The {@link org.python.pydev.editor.PyEditConfiguration PyEditConfiguration}does preliminary partitioning. * <li>The {@link org.python.pydev.parser.PyParser PyParser}does a lazy validating python parse. * <li>The {@link org.python.pydev.outline.PyOutlinePage PyOutlinePage}shows the outline * * <p> * Listens to the parser's events, and displays error markers from the parser. * * <p> * General notes: * <p> * TextWidget creates SourceViewer, an SWT control * * @see <a href="http://dev.eclipse.org/newslists/news.eclipse.tools/msg61594.html">This eclipse article was an inspiration </a> * */ public class PyEdit extends PyEditProjection implements IPyEdit, IGrammarVersionProvider, IPySyntaxHighlightingAndCodeCompletionEditor, IParserObserver3, ITabChangedListener { public static final String PYDEV_EDITOR_KEYBINDINGS_CONTEXT_ID = "org.python.pydev.ui.editor.scope"; static { ParseException.verboseExceptions = true; } public static final String PY_EDIT_CONTEXT = "#PyEditContext"; public static final String PY_EDIT_RULER_CONTEXT = "#PyEditRulerContext"; static public final String EDITOR_ID = "org.python.pydev.editor.PythonEditor"; static public final String ACTION_OPEN = "OpenEditor"; static private final Set<PyEdit> currentlyOpenedEditors = new HashSet<PyEdit>(); static private final Object currentlyOpenedEditorsLock = new Object(); /** color cache */ private ColorAndStyleCache colorCache; // Listener waits for tab/spaces preferences that affect sourceViewer private IPropertyChangeListener prefListener; /** need it to support GUESS_TAB_SUBSTITUTION preference */ private PyAutoIndentStrategy indentStrategy; /** need to hold onto it to support indentPrefix change through preferences */ private PyEditConfiguration editConfiguration; @Override public PyEditConfiguration getEditConfiguration() { return editConfiguration; } @Override public ColorAndStyleCache getColorCache() { return colorCache; } /** * Important: keep for scripting */ @Override public PySelection createPySelection() { return new PySelection(this); } @Override public TextSelectionUtils createTextSelectionUtils() { return new PySelection(this); } /** * AST that created python model */ private volatile SimpleNode ast; private volatile long astModificationTimeStamp = -1; /** * The last parsing error description we got. */ private volatile ErrorDescription errorDescription; // ---------------------------- listeners stuff /** * Those are the ones that register with the PYDEV_PYEDIT_LISTENER extension point */ private static List<IPyEditListener> editListeners; /** * This is the scripting engine that is binded to this interpreter. */ private PyEditScripting pyEditScripting; public final ICallbackWithListeners<Composite> onCreatePartControl = new CallbackWithListeners<Composite>(); public final ICallbackWithListeners<ISourceViewer> onAfterCreatePartControl = new CallbackWithListeners<ISourceViewer>(); public final ICallbackWithListeners<PyEdit> onCreateActions = new CallbackWithListeners<PyEdit>(); public final ICallbackWithListeners<Class<?>> onGetAdapter = new CallbackWithListeners<Class<?>>(); public final ICallbackWithListeners<LineNumberRulerColumn> onInitializeLineNumberRulerColumn = new CallbackWithListeners<LineNumberRulerColumn>(); public final ICallbackWithListeners<?> onDispose = new CallbackWithListeners<Object>(); public final ICallbackWithListeners<PropertyChangeEvent> onHandlePreferenceStoreChanged = new CallbackWithListeners<PropertyChangeEvent>(); public final ICallbackWithListeners<PySourceViewer> onCreateSourceViewer = new CallbackWithListeners<PySourceViewer>(); public ISourceViewer getISourceViewer() { return getSourceViewer(); } public IVerticalRuler getIVerticalRuler() { return getVerticalRuler(); } @Override protected void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) { super.initializeLineNumberRulerColumn(rulerColumn); this.onInitializeLineNumberRulerColumn.call(rulerColumn); } @Override protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { super.handlePreferenceStoreChanged(event); this.onHandlePreferenceStoreChanged.call(event); updateHoverBehavior(); } @Override public void createPartControl(Composite parent) { Composite newParent = (Composite) this.onCreatePartControl.call(parent); if (newParent != null) { parent = newParent; } super.createPartControl(parent); this.onAfterCreatePartControl.call(getSourceViewer()); } private boolean disposed = false; private CodeFoldingSetter codeFoldingSetter; public boolean isDisposed() { return disposed; } /** * Anyone may register to know when PyEdits are created. */ public static final ICallbackWithListeners<PyEdit> onPyEditCreated = new CallbackWithListeners<PyEdit>(); // ---------------------------- end listeners stuff @SuppressWarnings("unchecked") public PyEdit() { super(); synchronized (currentlyOpenedEditorsLock) { currentlyOpenedEditors.add(this); } try { onPyEditCreated.call(this); } catch (Throwable e) { Log.log(e); } try { //initialize the 'save' listeners of PyEdit if (editListeners == null) { editListeners = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PYEDIT_LISTENER); } notifier.notifyEditorCreated(); colorCache = new ColorAndStyleCache(PydevPrefs.getChainedPrefStore()); editConfiguration = new PyEditConfiguration(colorCache, this, PydevPrefs.getChainedPrefStore()); setSourceViewerConfiguration(editConfiguration); indentStrategy = editConfiguration.getPyAutoIndentStrategy(this); setRangeIndicator(new DefaultRangeIndicator()); // enables standard // vertical ruler //Added to set the code folding. this.codeFoldingSetter = new CodeFoldingSetter(this); CheckDefaultPreferencesDialog.askAboutSettings(); //Ask for people to consider funding PyDev. PydevShowBrowserMessage.show(); //Warn about the Eclipse version we require. checkEclipseRunning(); } catch (Throwable e) { Log.log(e); } } private boolean checkedEclipseVersion = false; private void checkEclipseRunning() { if (checkedEclipseVersion) { return; } checkedEclipseVersion = true; try { String string = Platform.getBundle("org.eclipse.jface.text").getHeaders().get("Bundle-Version"); Version version = new Version(string); if (version.compareTo(new Version("3.11.0")) < 0) { Log.log("Error: This version of PyDev requires a newer version of Eclipse to run properly."); RunInUiThread.async(new Runnable() { @Override public void run() { PyDialogHelpers.openCritical("Eclipse version too old (4.6 -- Neon -- required).", "This version of PyDev requires a newer version of Eclipse to run properly.\n\n" + "Please upgrade Eclipse or use an older version of PyDev.\n\n" + "See: http://www.pydev.org/download.html for requirements\n" + "(for this version or older versions)."); } }, false); } } catch (Exception e) { Log.log(e); } } @Override protected List<IPyEditListener> getAdditionalEditorListeners() { return editListeners; } /** * Overridden so that we can: * - Set up the cursor listener (notifies changes in the cursor position) * - Make the backspace handling in a way that the incremental find works (note: having the listener in the * textWidget does not work for that, as the event used in the IncrementalFindTarget is not the same event * that goes to the textWidget). */ @Override protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { PySourceViewer viewer = (PySourceViewer) super.createSourceViewer(parent, ruler, styles); viewer.appendVerifyKeyListener(PyPeerLinker.createVerifyKeyListener(viewer)); viewer.appendVerifyKeyListener(PyBackspace.createVerifyKeyListener(viewer, this)); VerifyKeyListener createVerifyKeyListener = FirstCharAction.createVerifyKeyListener(viewer, this.getSite(), false); if (createVerifyKeyListener != null) { viewer.appendVerifyKeyListener(createVerifyKeyListener); } this.onCreateSourceViewer.call(viewer); return viewer; } /** * Sets the forceTabs preference for auto-indentation. * * <p> * This is the preference that overrides "use spaces" preference when file contains tabs (like mine do). * <p> * If the first indented line starts with a tab, then tabs override spaces. */ @Override public void resetForceTabs() { IDocument doc = getDocumentProvider().getDocument(getEditorInput()); if (doc == null) { return; } IIndentPrefs indentPrefs = getIndentPrefs(); if (!indentPrefs.getGuessTabSubstitution()) { indentPrefs.setForceTabs(false); return; } int lines = doc.getNumberOfLines(); boolean forceTabs = false; int i = 0; // look for the first line that starts with ' ', or '\t' while (i < lines) { try { IRegion r = doc.getLineInformation(i); String text = doc.get(r.getOffset(), r.getLength()); if (text != null) { if (text.startsWith("\t")) { forceTabs = true; break; } else if (text.startsWith(" ")) { forceTabs = false; break; } } } catch (BadLocationException e) { Log.log(IStatus.ERROR, "Unexpected error forcing tabs", e); break; } i++; } indentPrefs.setForceTabs(forceTabs); editConfiguration.resetIndentPrefixes(); // display a message in the status line if (forceTabs) { updateForceTabsMessage(); } } public void updateForceTabsMessage() { boolean forceTabs = getIndentPrefs().getForceTabs(); ImageCache imageCache = PydevPlugin.getImageCache(); ImageDescriptor desc; if (forceTabs) { desc = imageCache.getDescriptor(UIConstants.FORCE_TABS_ACTIVE); } else { desc = imageCache.getDescriptor(UIConstants.FORCE_TABS_INACTIVE); } IEditorStatusLine statusLine = (IEditorStatusLine) getAdapter(IEditorStatusLine.class); if (statusLine != null) { statusLine.setMessage(false, forceTabs ? "Forcing tabs" : "Not forcing tabs.", desc.createImage()); } } /** * @return the indentation preferences. Any action writing something should use this one in the editor because * we want to make sure that the indent must be the one bounded to this editor (because tabs may be forced in a given * editor, even if does not match the global settings). */ @Override public IIndentPrefs getIndentPrefs() { return indentStrategy.getIndentPrefs(); } public PyAutoIndentStrategy getAutoEditStrategy() { return indentStrategy; } //Just making interface public @Override public void resetIndentPrefixes() { super.updateIndentPrefixes(); } /** * Overriden because pydev already handles spaces -> tabs */ @Override protected void installTabsToSpacesConverter() { //Do nothing (pydev already handles that) updateIndentPrefixes(); } /** * Overriden becaus pydev already handles spaces -> tabs */ @Override protected void uninstallTabsToSpacesConverter() { //Do nothing (pydev already handles that) updateIndentPrefixes(); } /** * Initializes everyone that needs document access * */ @Override public void init(final IEditorSite site, final IEditorInput input) throws PartInitException { try { super.init(site, input); final IDocument document = getDocument(input); // check the document partitioner (sanity check / fix) PyPartitionScanner.checkPartitionScanner(document); // Also adds Python nature to the project. // The reason this is done here is because I want to assign python // nature automatically to any project that has active python files. final IPythonNature nature = PythonNature.addNature(input); //we also want to initialize our shells... //we use 2: one for the main thread and one for the other threads. //just preemptively start the one for the main thread. final int mainThreadShellId = AbstractShell.getShellId(); Thread thread2 = new Thread() { @Override public void run() { try { try { AbstractShell.getServerShell(nature, mainThreadShellId); } catch (RuntimeException e1) { } } catch (Exception e) { } } }; thread2.setName("Shell starter"); thread2.start(); // listen to changes in TAB_WIDTH preference prefListener = createPrefChangeListener(this); this.getIndentPrefs().addTabChangedListener(this); resetForceTabs(); PydevPrefs.getChainedPrefStore().addPropertyChangeListener(prefListener); Runnable runnable = new Runnable() { @Override public void run() { try { //let's do that in a thread, so that we don't have any delays in setting up the editor pyEditScripting = new PyEditScripting(); addPyeditListener(pyEditScripting); } finally { //if it fails, still mark it as finished. markInitFinished(); } } }; Thread thread = new Thread(runnable); thread.setName("PyEdit initializer"); thread.start(); } catch (Throwable e) { //never fail in the init Log.log(e); } } @Override public void onTabSettingsChanged(IIndentPrefs prefs) { onTabSettingsChanged(this); } private static void onTabSettingsChanged(final IPySyntaxHighlightingAndCodeCompletionEditor editor) { ISourceViewer sourceViewer = editor.getEditorSourceViewer(); if (sourceViewer == null) { return; } IIndentPrefs indentPrefs = editor.getIndentPrefs(); indentPrefs.regenerateIndentString(); sourceViewer.getTextWidget().setTabs(indentPrefs.getTabWidth()); editor.resetForceTabs(); editor.resetIndentPrefixes(); } public static IPropertyChangeListener createPrefChangeListener( final IPySyntaxHighlightingAndCodeCompletionEditor editor) { return new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { try { String property = event.getProperty(); //tab width if (property.equals(PydevEditorPrefs.TAB_WIDTH)) { onTabSettingsChanged(editor); } else if (property.equals(PydevEditorPrefs.SUBSTITUTE_TABS)) { onTabSettingsChanged(editor); //auto adjust for file tabs } else if (property.equals(PydevEditorPrefs.GUESS_TAB_SUBSTITUTION)) { onTabSettingsChanged(editor); //colors and styles } else if (ColorAndStyleCache.isColorOrStyleProperty(property)) { editor.getColorCache().reloadProperty(property); //all reference this cache editor.getEditConfiguration().updateSyntaxColorAndStyle(); //the style needs no reloading editor.getEditorSourceViewer().invalidateTextPresentation(); } } catch (Exception e) { Log.log(e); } } }; } //Deal with notifying the user that the opened file is invalid ----------------------------------------------------- private static final String INVALID_MODULE_MARKER_TYPE = "org.python.pydev.invalidpythonfilemarker"; private void checkAddInvalidModuleNameMarker(IDocument doc, IFile file) { try { String name = file.getName(); int i = name.lastIndexOf('.'); if (i > 0) { String modName = name.substring(0, i); if (!PythonPathHelper.isValidModuleLastPart(modName)) { addInvalidModuleMarker(doc, file, "Invalid name for Python module: " + modName + " (it'll not be analyzed)"); return; } else if (!PythonPathHelper.isValidSourceFile(name)) { addInvalidModuleMarker(doc, file, "Module: " + modName + " does not have a valid Python extension (it'll not be analyzed)."); return; } } //if it still hasn't returned, remove any existing marker (i.e.: rename operation) removeInvalidModuleMarkers(file); } catch (Exception e) { Log.log(e); } } private void removeInvalidModuleMarkers(IFile file) { try { if (file.exists()) { file.deleteMarkers(INVALID_MODULE_MARKER_TYPE, true, IResource.DEPTH_ZERO); } } catch (Exception e) { Log.log(e); } } private void addInvalidModuleMarker(IDocument doc, IFile fileAdapter, String msg) { MarkerInfo markerInfo = new PyMarkerUtils.MarkerInfo(doc, msg, INVALID_MODULE_MARKER_TYPE, IMarker.SEVERITY_WARNING, false, true, 0, 0, 0, 0, null); ArrayList<MarkerInfo> lst = new ArrayList<MarkerInfo>(); lst.add(markerInfo); PyMarkerUtils.replaceMarkers(lst, fileAdapter, INVALID_MODULE_MARKER_TYPE, true, new NullProgressMonitor()); } /* * Update the hovering behavior depending on the preferences. */ private void updateHoverBehavior() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer == null) { return; } SourceViewerConfiguration configuration = getSourceViewerConfiguration(); String[] types = configuration.getConfiguredContentTypes(getSourceViewer()); for (int i = 0; i < types.length; i++) { String t = types[i]; if (sourceViewer instanceof ITextViewerExtension2) { // Remove existing hovers ((ITextViewerExtension2) sourceViewer).removeTextHovers(t); int[] stateMasks = configuration.getConfiguredTextHoverStateMasks(getSourceViewer(), t); if (stateMasks != null) { for (int j = 0; j < stateMasks.length; j++) { int stateMask = stateMasks[j]; ITextHover textHover = configuration.getTextHover(sourceViewer, t, stateMask); ((ITextViewerExtension2) sourceViewer).setTextHover(textHover, t, stateMask); } } else { ITextHover textHover = configuration.getTextHover(sourceViewer, t); ((ITextViewerExtension2) sourceViewer).setTextHover(textHover, t, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK); } } else { sourceViewer.setTextHover(configuration.getTextHover(sourceViewer, t), t); } } } /** * When we have the editor input re-set, we have to change the parser and the partition scanner to * the new document. This happens in 3 cases: * - when the editor has been created * - when the editor is reused in the search window * - when we create a file, and make a save as, to change its name * * there were related bugs in each of these cases: * https://sourceforge.net/tracker/?func=detail&atid=577329&aid=1250307&group_id=85796 * https://sourceforge.net/tracker/?func=detail&atid=577329&aid=1251271&group_id=85796 * * @see org.eclipse.ui.texteditor.AbstractTextEditor#doSetInput(org.eclipse.ui.IEditorInput) */ @Override protected void doSetInput(IEditorInput input) throws CoreException { IEditorInput oldInput = this.getEditorInput(); //Remove markers from the old if (oldInput != null) { IFile oldFile = oldInput.getAdapter(IFile.class); if (oldFile != null) { removeInvalidModuleMarkers(oldFile); } } synchronized (lockHandle) { releaseCurrentHandle(); } super.doSetInput(input); try { IDocument document = getDocument(input); if (input != null) { IFile newFile = input.getAdapter(IFile.class); if (newFile != null) { //Add invalid module name markers to the new. checkAddInvalidModuleNameMarker(document, newFile); } //see if we have to change the encoding of the file on load fixEncoding(input, document); PyParserManager.getPyParserManager(PydevPrefs.getPreferences()).attachParserTo(this); if (document != null) { PyPartitionScanner.checkPartitionScanner(document, this.getGrammarVersionProvider()); } } notifier.notifyInputChanged(oldInput, input); notifier.notifyOnSetDocument(document); } catch (Throwable e) { Log.log(e); } try { PyEditTitle.invalidateTitle(this, input); } catch (Throwable e) { Log.log(e); } try { if (this.isCythonFile()) { this.setTitleImage(PydevPlugin.getImageCache().get(UIConstants.CYTHON_FILE_ICON)); this.getAutoEditStrategy().setCythonFile(true); } else { this.getAutoEditStrategy().setCythonFile(false); } } catch (Throwable e) { Log.log(e); } } /* default */void setEditorTitle(String title) { setPartName(title); firePropertyChange(PROP_DIRTY); } /* default */void setEditorImage(Image image) { setTitleImage(image); } /** * @param input the input from where we want to get the document * @return the document for the passed input */ private IDocument getDocument(final IEditorInput input) { return getDocumentProvider().getDocument(input); } /** * @see org.eclipse.ui.texteditor.AbstractTextEditor#performSave(boolean, org.eclipse.core.runtime.IProgressMonitor) */ @Override protected void performSave(boolean overwrite, IProgressMonitor progressMonitor) { final IDocument document = getDocument(); boolean keepOn; try { keepOn = true; if (PydevSaveActionsPrefPage.getAutoformatOnlyWorkspaceFiles(this)) { if (getIFile() == null) { //not a workspace file and user has chosen to only auto-format workspace files. keepOn = false; } } } catch (Exception e1) { Log.log(e1); // Shouldn't happen: let's skip the save actions... keepOn = false; } // Save actions before code-formatting (so that we apply the formatting to it afterwards). try { if (keepOn) { executeSaveActions(document); } } catch (final Throwable e) { Log.log(e); } //Before saving, let's see if the auto-code formatting is turned on. try { //TODO CYTHON: support code-formatter. if (keepOn && PydevSaveActionsPrefPage.getFormatBeforeSaving(this) && !isCythonFile()) { IStatusLineManager statusLineManager = this.getStatusLineManager(); IDocumentProvider documentProvider = getDocumentProvider(); int[] regionsForSave = null; if (PyCodeFormatterPage.getFormatOnlyChangedLines(this)) { if (documentProvider instanceof PyDocumentProvider) { PyDocumentProvider pyDocumentProvider = (PyDocumentProvider) documentProvider; ITextFileBuffer fileBuffer = pyDocumentProvider.getFileBuffer(getEditorInput()); if (fileBuffer != null) { regionsForSave = ChangedLinesComputer.calculateChangedLines(fileBuffer, progressMonitor == null ? new NullProgressMonitor() : progressMonitor); } } else { Log.log("Was expecting PyDocumentProvider. Found: " + documentProvider); } } if (regionsForSave == null || regionsForSave.length > 0) { //Note: auto-format should only take place if we're always formatting everything or //if we have some region to update (regionsForSave.length == 0 means that we only //had deleted lines, in which case we can't really do anything). ITextSelection selection = (ITextSelection) this.getSelectionProvider().getSelection(); PySelection ps = new PySelection(document, selection); if (!hasSyntaxError(ps.getDoc())) { PyFormatStd std = new PyFormatStd(); boolean throwSyntaxError = true; try { std.applyFormatAction(this, ps, regionsForSave, throwSyntaxError, this.getSelectionProvider()); statusLineManager.setErrorMessage(null); } catch (SyntaxErrorException e) { statusLineManager.setErrorMessage(e.getMessage()); } } } } } catch (Throwable e) { //can never fail Log.log(e); } try { fixEncoding(getEditorInput(), document); } catch (Throwable e) { //can never fail Log.log(e); } //will provide notifications super.performSave(overwrite, progressMonitor); } private void executeSaveActions(IDocument document) throws BadLocationException { if (PydevSaveActionsPrefPage.getDateFieldActionEnabled(this)) { final String contents = document.get(); final String fieldName = PydevSaveActionsPrefPage.getDateFieldName(this); final String fieldPattern = String .format("^%s(\\s*)=(\\s*[ur]{0,2}['\"]{1,3})(.+?)(['\"]{1,3})", fieldName); final Pattern pattern = Pattern.compile(fieldPattern, Pattern.MULTILINE); final Matcher matcher = pattern.matcher(contents); if (matcher.find()) { final MatchResult matchResult = matcher.toMatchResult(); if (matchResult.groupCount() == 4) { final String spBefore = matchResult.group(1); final String spAfterQuoteBegin = matchResult.group(2); final String dateStr = matchResult.group(3); final String quoteEnd = matchResult.group(4); final String dateFormat = PydevSaveActionsPrefPage.getDateFieldFormat(this); final Date nowDate = new Date(); final SimpleDateFormat ft = new SimpleDateFormat(dateFormat); try { final Date fieldDate = ft.parse(dateStr); // don't touch future dates if (fieldDate.before(nowDate)) { final String newDateStr = ft.format(nowDate); final String replacement = fieldName + spBefore + "=" + spAfterQuoteBegin + newDateStr + quoteEnd; document.replace(matchResult.start(), matchResult.end() - matchResult.start(), replacement); } } catch (final java.text.ParseException pe) { // do nothing } } } } if (PydevSaveActionsPrefPage.getSortImportsOnSave(this)) { boolean automatic = true; PyOrganizeImports organizeImports = new PyOrganizeImports(automatic); try { organizeImports.formatAll(getDocument(), this, getIFile(), true, true); } catch (SyntaxErrorException e) { Log.log(e); } } } @Override protected BaseParserManager getParserManager() { return PyParserManager.getPyParserManager(null); } /** * Checks if there's a syntax error at the document... if there is, returns false. * * Note: This function will also set the status line error message if there's an error message. * Note: This function will actually do a parse operation when called (so, it should be called with care). */ public boolean hasSyntaxError(IDocument doc) throws MisconfigurationException { ParseOutput reparse = PyParser.reparseDocument(new PyParser.ParserInfo(doc, this, false)); if (reparse.error != null) { this.getStatusLineManager().setErrorMessage(reparse.error.getMessage()); return true; } return false; } /** * Just to make it public. */ @Override public void doSave(IProgressMonitor progressMonitor) { super.doSave(progressMonitor); } /** * Forces the encoding to the one specified in the file * * @param input * @param document */ private void fixEncoding(final IEditorInput input, IDocument document) { if (input instanceof FileEditorInput) { final IFile file = ((FileEditorInput) input).getAdapter(IFile.class); try { final String encoding = FileUtilsFileBuffer.getPythonFileEncoding(document, file.getFullPath() .toOSString()); if (encoding != null) { try { if (encoding.equals(file.getCharset()) == false) { new Job("Change encoding") { @Override protected IStatus run(IProgressMonitor monitor) { try { file.setCharset(encoding, monitor); ((TextFileDocumentProvider) getDocumentProvider()).setEncoding(input, encoding); //refresh it... file.refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e) { Log.log(e); } return Status.OK_STATUS; } }.schedule(); } } catch (CoreException e) { Log.log(e); } } } catch (Exception e) { Log.log(e); } } } /** * @return the File being edited */ @Override public File getEditorFile() { File editorFile = super.getEditorFile(); if (editorFile == null) { IEditorInput editorInput = this.getEditorInput(); if (editorInput instanceof PydevFileEditorInput) { PydevFileEditorInput pyEditorInput = (PydevFileEditorInput) editorInput; return pyEditorInput.getPath().toFile(); } } return editorFile; } // cleanup @Override public void dispose() { synchronized (lockHandle) { releaseCurrentHandle(); } if (!this.disposed) { this.disposed = true; synchronized (currentlyOpenedEditorsLock) { currentlyOpenedEditors.remove(this); } this.outlinePage = null; this.codeFoldingSetter = null; try { IFile iFile = this.getIFile(); if (iFile != null) { removeInvalidModuleMarkers(iFile); } } catch (Throwable e1) { Log.log(e1); } try { this.onDispose.call(null); notifier.notifyOnDispose(); PydevPrefs.getChainedPrefStore().removePropertyChangeListener(prefListener); PyParserManager.getPyParserManager(null).notifyEditorDisposed(this); colorCache.dispose(); pyEditScripting = null; cache.clear(); cache = null; if (this.resourceManager != null) { this.resourceManager.dispose(); this.resourceManager = null; } synchronized (registeredEditListeners) { registeredEditListeners.clear(); } } catch (Throwable e) { Log.log(e); } } super.dispose(); } public static class MyResources extends ListResourceBundle { @Override public Object[][] getContents() { return contents; } static final Object[][] contents = { { "CorrectionAssist", "CorrectionAssist" }, { "ContentAssistProposal", "ContentAssistProposal" }, { "TemplateProposals", "TemplateProposals" }, }; } /* * (non-Javadoc) * * @see org.eclipse.ui.texteditor.AbstractTextEditor#createActions() * * TODO: Fix content assist to work in emacs mode: * http://wiki.eclipse.org/index.php/FAQ_How_do_I_add_Content_Assist_to_my_editor%3F * http://www.eclipse.org/newsportal/article.php?id=61744&group=eclipse.platform#61744 */ @Override protected void createActions() { super.createActions(); try { MyResources resources = new MyResources(); IAction action; //Quick-Assist: it's added to the platform as of Eclipse 3.2, so, we do not have to put the binding here // ------------------------------------------------------------------------------------- // This action will fire a CONTENTASSIST_PROPOSALS operation // when executed action = new ContentAssistAction(resources, "ContentAssistProposal.", this); action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction("ContentAssistProposal", action); markAsStateDependentAction("ContentAssistProposal", true); // ------------------------------------------------------------------------------------- //open action IAction openAction = new PyOpenAction(); setAction(ACTION_OPEN, openAction); // ------------------------------------------------------------------------------------- // Offline action action = new OfflineAction(resources, "Pyedit.ScriptEngine.", this); action.setActionDefinitionId("org.python.pydev.editor.actions.scriptEngine"); action.setId("org.python.pydev.editor.actions.scriptEngine"); setAction("PyDevScriptEngine", action); // ------------------------------------------------------------------------------------- //move lines if (this.getIndentPrefs().getSmartLineMove()) { //Don't even bind the action if the smart line move is not set. //This means 2 things: //- Uses the default action when asked //- An editor restart will be needed to have it applied action = new PyMoveLineUpAction(resources, "Pyedit.MoveLinesUp.", this); action.setActionDefinitionId(ITextEditorActionDefinitionIds.MOVE_LINES_UP); action.setId("org.python.pydev.editor.actions.moveLineUp"); setAction(ITextEditorActionConstants.MOVE_LINE_UP, action); action = new PyMoveLineDownAction(resources, "Pyedit.MoveLinesDown.", this); action.setActionDefinitionId(ITextEditorActionDefinitionIds.MOVE_LINES_DOWN); action.setId("org.python.pydev.editor.actions.moveLineDown"); setAction(ITextEditorActionConstants.MOVE_LINE_DOWN, action); } notifier.notifyOnCreateActions(resources); onCreateActions.call(this); } catch (Throwable e) { Log.log(e); } } @Override protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { PYDEV_EDITOR_KEYBINDINGS_CONTEXT_ID }); } /** * Used to: request a reparse / add listener / remove listener * @return the parser that is being used in this editor. */ public PyParser getParser() { return (PyParser) PyParserManager.getPyParserManager(null).getParser(this); } /** * Returns the status line manager of this editor. * @return the status line manager of this editor * @since 2.0 * * copied from superclass, as it is private there... */ @Override public IStatusLineManager getStatusLineManager() { return EditorUtils.getStatusLineManager(this); } /** * This is the 'offline' action */ protected OfflineActionTarget fOfflineActionTarget; private PyOutlinePage outlinePage; /** * @return an outline view */ @Override @SuppressWarnings("rawtypes") public Object getAdapter(Class adapter) { if (OfflineActionTarget.class.equals(adapter)) { if (fOfflineActionTarget == null) { IStatusLineManager manager = getStatusLineManager(); if (manager != null) { fOfflineActionTarget = (getSourceViewer() == null ? null : new OfflineActionTarget( getSourceViewer(), manager, this)); } } return fOfflineActionTarget; } if (IProject.class.equals(adapter)) { return this.getProject(); } if (ICodeScannerKeywords.class.equals(adapter)) { return new PyEditBasedCodeScannerKeywords(this); } if (IContentOutlinePage.class.equals(adapter)) { return getOutlinePage(); } else { Object adaptable = this.onGetAdapter.call(adapter); if (adaptable != null) { return adaptable; } return super.getAdapter(adapter); } } @Override public IOutlineModel createOutlineModel() { return new ParsedModel(this); } private IContentOutlinePage getOutlinePage() { if (this.outlinePage == null) { this.outlinePage = new PyOutlinePage(this); } return this.outlinePage; } @Override public void setSelection(int offset, int length) { super.setSelection(offset, length); } /** * Selects more than one node, making a selection from the 1st node to the last node passed. */ @Override public void revealModelNodes(ISimpleNode[] nodes) { if (nodes == null) { return; // nothing to see here } IDocument document = getDocumentProvider().getDocument(getEditorInput()); if (document == null) { return; } try { int startOffset = -1, endOffset = -1; PySelection selection = new PySelection(this); for (ISimpleNode inode : nodes) { SimpleNode node = (SimpleNode) inode; int nodeStartoffset = selection.getLineOffset(node.beginLine - 1) + node.beginColumn - 1; int[] colLineEnd = NodeUtils.getColLineEnd(node); int nodeEndOffset = selection.getLineOffset(colLineEnd[0] - 1) + colLineEnd[1] - 1; if (startOffset == -1 || nodeStartoffset < startOffset) { startOffset = nodeStartoffset; } if (endOffset == -1 || nodeEndOffset > endOffset) { endOffset = nodeEndOffset; } } setSelection(startOffset, endOffset - startOffset); } catch (Exception e) { Log.log(e); } } /** * Shows some node in the editor. * @param node the node to be shown. */ public void revealModelNode(SimpleNode node) { if (node == null) { return; // nothing to see here } IDocument document = getDocumentProvider().getDocument(getEditorInput()); if (document == null) { return; } int offset, length, endOffset; try { PySelection selection = new PySelection(this); offset = selection.getLineOffset(node.beginLine - 1) + node.beginColumn - 1; int[] colLineEnd = NodeUtils.getColLineEnd(node); endOffset = selection.getLineOffset(colLineEnd[0] - 1) + colLineEnd[1] - 1; length = endOffset - offset; setSelection(offset, length); } catch (Exception e) { Log.log(e); } } private Tuple3<Integer, IModulesManager, String> handle; private final Object lockHandle = new Object(); /** * Note that the lockHandle must be already synched before this method is called. */ private void releaseCurrentHandle() { if (this.handle != null) { this.handle.o2.popTemporaryModule(this.handle.o3, this.handle.o1); this.handle = null; } } /** * This event comes when document was parsed (with or without errors) * * Removes all the error markers */ @Override public void parserChanged(ChangedParserInfoForObservers info) { if (info.errorInfo != null) { errorDescription = PyParser.createParserErrorMarkers(info.errorInfo.error, info.file, info.doc); } else { errorDescription = null; } ast = (SimpleNode) info.root; astModificationTimeStamp = info.docModificationStamp; try { IPythonNature pythonNature = this.getPythonNature(); if (pythonNature != null) { ICodeCompletionASTManager astManager = pythonNature.getAstManager(); if (astManager != null) { IModulesManager modulesManager = astManager.getModulesManager(); if (modulesManager != null) { File editorFile = this.getEditorFile(); if (editorFile != null) { String moduleName = pythonNature.resolveModule(editorFile); if (moduleName != null) { synchronized (lockHandle) { releaseCurrentHandle(); int modHandle = modulesManager.pushTemporaryModule(moduleName, new SourceModule( moduleName, editorFile, ast, null, pythonNature)); this.handle = new Tuple3<Integer, IModulesManager, String>(modHandle, modulesManager, moduleName); } } } } } } } catch (MisconfigurationException e) { Log.log(e); } fireModelChanged(ast); invalidateTextPresentationAsync(); } private void invalidateTextPresentationAsync() { //Trying to fix issue where it seems that the text presentation is not properly updated after markers are //changed (i.e.: red lines remain there when they shouldn't). //I couldn't really reproduce this issue, so, this may not fix it... // //Details: https://sourceforge.net/projects/pydev/forums/forum/293649/topic/4477776 RunInUiThread.async(new Runnable() { @Override public void run() { if (!isDisposed()) { getSourceViewer().invalidateTextPresentation(); } } }); } @Override public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc, long docModificationStamp) { throw new AssertionError("Implementing IParserObserver3: this should not be called anymore"); } /** * This event comes when parse ended in an error * * Generates an error marker on the document */ @Override public void parserError(Throwable error, IAdaptable original, IDocument doc) { throw new AssertionError("Implementing IParserObserver3: this should not be called anymore"); } @Override public void parserError(ErrorParserInfoForObservers info) { //Note: if the ast was not generated, just the error, we have to make sure we're properly set //(even if it was set in the ast too). errorDescription = PyParser.createParserErrorMarkers(info.error, info.file, info.doc); fireParseErrorChanged(errorDescription); } /** * @return the last ast generated in this editor (even if we had some other error after that) * Note: could be null! */ public SimpleNode getAST() { return ast; } public long getAstModificationTimeStamp() { return astModificationTimeStamp; } /** * @return a list of tuples, where the 1st element in the tuple is a String and the 2nd element is * an icon, ordered so that the 1st item is the topmost and the last is the innermost. */ public List<String[]> getInnerStructureFromLine(int line) { ArrayList<String[]> ret = new ArrayList<String[]>(); List<stmtType> parseToKnowGloballyAccessiblePath = FastParser.parseToKnowGloballyAccessiblePath(getDocument(), line); for (stmtType stmtType : parseToKnowGloballyAccessiblePath) { String rep = NodeUtils.getRepresentationString(stmtType); String image; if (stmtType instanceof ClassDef) { image = UIConstants.CLASS_ICON; } else if (stmtType instanceof FunctionDef) { image = UIConstants.METHOD_ICON; } else { image = UIConstants.ERROR; } ret.add(new String[] { rep, image }); } return ret; } /** * This function will open an editor given the passed parameters * * @param projectName * @param path * @param innerStructure * @throws MisconfigurationException */ public static void openWithPathAndInnerStructure(String projectName, IPath path, List<String> innerStructure) throws MisconfigurationException { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IProject project = workspace.getRoot().getProject(projectName); if (project != null) { IFile file = project.getFile(path); if (file != null) { IEditorPart editor = PyOpenEditor.doOpenEditor(file); if (editor instanceof PyEdit) { PyEdit pyEdit = (PyEdit) editor; IPythonNature nature = pyEdit.getPythonNature(); AbstractModule mod = AbstractModule.createModuleFromDoc(nature.resolveModule(file), file .getLocation().toFile(), pyEdit.getDocument(), nature, false); StringBuffer tok = new StringBuffer(80); for (String s : innerStructure) { if (tok.length() > 0) { tok.append('.'); } tok.append(s); } try { IDefinition[] definitions = mod.findDefinition(CompletionStateFactory.getEmptyCompletionState( tok.toString(), nature, new CompletionCache()), -1, -1, nature); List<ItemPointer> pointers = new ArrayList<ItemPointer>(); PyRefactoringFindDefinition.getAsPointers(pointers, definitions); if (pointers.size() > 0) { new PyOpenAction().run(pointers.get(0)); } } catch (Exception e) { Log.log(e); } } } } } /** * @return the last error description found (may be null) */ public ErrorDescription getErrorDescription() { return errorDescription; } /** * Only used if we weren't able */ @Override public int getGrammarVersion() throws MisconfigurationException { if (isCythonFile()) { return IPythonNature.GRAMMAR_PYTHON_VERSION_CYTHON; } IPythonNature nature; nature = getPythonNature(); if (nature != null) { return nature.getGrammarVersion(); } File editorFile = getEditorFile(); if (editorFile == null) { throw new MisconfigurationException(); } Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(editorFile); if (infoForFile == null || infoForFile.o1 == null) { throw new MisconfigurationException(); } return infoForFile.o1.getGrammarVersion(); } @Override public AdditionalGrammarVersionsToCheck getAdditionalGrammarVersions() throws MisconfigurationException { IPythonNature pythonNature = getPythonNature(); if (pythonNature != null) { return pythonNature.getAdditionalGrammarVersions(); } return null; } @Override public IGrammarVersionProvider getGrammarVersionProvider() { return new IGrammarVersionProvider() { @Override public int getGrammarVersion() throws MisconfigurationException { //Always calculate at the present time based on the editor configuration. return PyEdit.this.getGrammarVersion(); } @Override public AdditionalGrammarVersionsToCheck getAdditionalGrammarVersions() throws MisconfigurationException { return PyEdit.this.getAdditionalGrammarVersions(); } }; } public boolean isCythonFile() { IFile iFile = getIFile(); String fileName = null; if (iFile != null) { fileName = iFile.getName(); } else { File editorFile = getEditorFile(); if (editorFile != null) { fileName = editorFile.getName(); } } return FileTypesPreferencesPage.isCythonFile(fileName); } /** * @return the python nature associated with this editor. * @throws NotConfiguredInterpreterException */ @Override public IPythonNature getPythonNature() throws MisconfigurationException { IProject project = getProject(); if (project == null || !project.isOpen()) { return null; } IPythonNature pythonNature = PythonNature.getPythonNature(project); if (pythonNature != null) { return pythonNature; } //if it's an external file, there's the possibility that it won't be added even here. pythonNature = PythonNature.addNature(this.getEditorInput()); if (pythonNature != null) { return pythonNature; } File editorFile = getEditorFile(); if (editorFile == null) { return null; } Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(editorFile); if (infoForFile == null) { NotConfiguredInterpreterException e = new NotConfiguredInterpreterException(); ErrorDialog.openError(EditorUtils.getShell(), "Error: no interpreter configured", "Interpreter not configured\n(Please, Configure it under window->preferences->PyDev)", PydevPlugin.makeStatus(IStatus.ERROR, e.getMessage(), e)); throw e; } pythonNature = infoForFile.o1; return pythonNature; } @Override protected void initializeEditor() { super.initializeEditor(); try { this.setPreferenceStore(PydevPrefs.getChainedPrefStore()); setEditorContextMenuId(PY_EDIT_CONTEXT); setRulerContextMenuId(PY_EDIT_RULER_CONTEXT); setDocumentProvider(PyDocumentProvider.instance); } catch (Throwable e) { Log.log(e); } } //------------------------------------------------------------------- START: actions that are activated after Ctrl+2 OfflineActionsManager offlineActionsManager = new OfflineActionsManager(); public Collection<ActionInfo> getOfflineActionDescriptions() { return offlineActionsManager.getOfflineActionDescriptions(); } public void addOfflineActionListener(String key, IAction action) { offlineActionsManager.addOfflineActionListener(key, action); } public void addOfflineActionListener(String key, IAction action, String description, boolean needsEnter) { offlineActionsManager.addOfflineActionListener(key, action, description, needsEnter); } public boolean activatesAutomaticallyOn(String key) { return offlineActionsManager.activatesAutomaticallyOn(key); } public boolean hasOfflineAction(String key) { return offlineActionsManager.hasOfflineAction(key); } /** * @return if an action was binded and was successfully executed */ public boolean onOfflineAction(String requestedStr, OfflineActionTarget target) { return offlineActionsManager.onOfflineAction(requestedStr, target); } private LocalResourceManager resourceManager; public synchronized LocalResourceManager getResourceManager() { if (resourceManager == null) { resourceManager = new LocalResourceManager(JFaceResources.getResources()); } return resourceManager; } /** * Used in the script pyedit_list_bindings.py */ public Font getFont(FontData descriptor) throws DeviceResourceException { Font font = getResourceManager().createFont(FontDescriptor.createFrom(descriptor)); // Old implementation (for Eclipse 3.3) // Font font = (Font) SWTResourceUtil.getFontTable().get(descriptor); // if (font == null) { // font = new Font(Display.getCurrent(), descriptor); // SWTResourceUtil.getFontTable().put(descriptor, font); // } return font; } //--------------------------------------------------------------------- END: actions that are activated after Ctrl+2 public static void checkValidateState(IEditorPart iEditorPart) { if (iEditorPart instanceof ITextEditorExtension2) { ITextEditorExtension2 editor = (ITextEditorExtension2) iEditorPart; editor.validateEditorInputState(); } } public static Object iterOpenEditorsUntilFirstReturn(ICallback<Object, PyEdit> callback) { HashSet<PyEdit> hashSet; synchronized (currentlyOpenedEditorsLock) { hashSet = new HashSet<>(currentlyOpenedEditors); } // Iterate in unsynchronized copy for (PyEdit edit : hashSet) { Object ret = callback.call(edit); if (ret != null) { return ret; } } return null; } public static boolean isEditorOpenForResource(IResource r) { HashSet<PyEdit> hashSet; synchronized (currentlyOpenedEditorsLock) { hashSet = new HashSet<>(currentlyOpenedEditors); } // Iterate in unsynchronized copy for (PyEdit edit : hashSet) { IEditorInput input = edit.getEditorInput(); if (input != null) { Object adapter = input.getAdapter(IResource.class); if (adapter != null && r.equals(adapter)) { return true; } } } return false; } @Override public FormatStd getFormatStd() { return PyFormatStd.getFormat(this); } /** * Important: keep for scripting */ public void setMessage(boolean error, String message) { IEditorStatusLine statusLine = (IEditorStatusLine) this.getAdapter(IEditorStatusLine.class); statusLine.setMessage(error, message, null); } /** * Important: keep for scripting */ public void showInformationDialog(String title, String message) { MessageDialog.openInformation(getSite().getShell(), title, message); } /** * Important: keep for scripting */ public int getPrintMarginColums() { return PydevPrefs.getChainedPrefStore() .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); } /** * Important: keep for scripting */ public void asyncExec(Runnable runnable) { RunInUiThread.async(runnable); } /** * Important: keep for scripting */ public Class<Action> getActionClass() { return Action.class; } /** * Important: keep for scripting */ public Class<IPyCompletionProposal> getIPyCompletionProposalClass() { return IPyCompletionProposal.class; } /** * Important: keep for scripting */ public Class<PyCompletionProposal> getPyCompletionProposalClass() { return PyCompletionProposal.class; } /** * Important: keep for scripting */ public Class<UIConstants> getUIConstantsClass() { return UIConstants.class; } /** * Important: keep for scripting */ public Class<ScriptConsole> getScriptConsoleClass() { return ScriptConsole.class; } /** * Important: keep for scripting */ public Class<Display> getDisplayClass() { return Display.class; } /** * Important: keep for scripting */ public Class<Runnable> getRunnableClass() { return Runnable.class; } /** * Important: keep for scripting */ public Class<PySelection> getPySelectionClass() { return PySelection.class; } /** * Important: keep for scripting */ public Class<UIJob> getUIJobClass() { return UIJob.class; } /** * Important: keep for scripting */ public Class<IDocumentListener> getIDocumentListenerClass() { return IDocumentListener.class; } /** * Important: keep for scripting */ public Class<PythonCorrectionProcessor> getPythonCorrectionProcessorClass() { return PythonCorrectionProcessor.class; } /** * Important: keep for scripting */ public Class<IExecuteLineAction> getIExecuteLineActionClass() { return IExecuteLineAction.class; } /** * Important: keep for scripting */ public IStatus getOkStatus() { return Status.OK_STATUS; } @Override public String toString() { return "PyEdit[" + getEditorFile() + "]"; } @Override public ICharacterPairMatcher2 getPairMatcher() { return new PythonPairMatcher(); } @Override public IScopesParser createScopesParser() { return new ScopesParser(); } }