Source code for leo.core.leoCommands

# -*- coding: utf-8 -*-
#@+node:ekr.20031218072017.2810: * @file
#@+<< imports >>
#@+node:ekr.20040712045933: ** << imports >> (leoCommands)
import leo.core.leoGlobals as g
import leo.core.leoNodes as leoNodes
    # The leoCommands ctor now does most leo.core.leo* imports.
    # This breaks circular dependencies.
    import builtins # Python 3
except ImportError:
    import __builtin__ as builtins # Python 2.
import itertools
import os
import re
import sys
import time
import tokenize # for c.checkAllPythonCode
    import tabnanny # for Check Python command # Does not exist in jython
except ImportError:
    tabnanny = None
#@-<< imports >>

[docs]def cmd(name): '''Command decorator for the Commands class.''' return g.new_cmd_decorator(name, ['c',])
#@+others #@+node:ekr.20160514120615.1: ** class Commands (object)
[docs]class Commands(object): """ A per-outline class that implements most of Leo's commands. The "c" predefined object is an instance of this class. c.initObjects() creates sucommanders corresponding to files in the leo/core and leo/commands. All of Leo's core code is accessible via this class and its subcommanders. is Leo's plugins controller. Many plugins inject controllers objects into the Commands class. These are another kind of subcommander. The @g..commander_command decorator injects methods into this class. """ #@+others #@+node:ekr.20031218072017.2811: *3* c.Birth & death #@+node:ekr.20031218072017.2812: *4* c.__init__ & helpers def __init__(self, fileName, relativeFileName=None, gui=None, previousSettings=None): # tag = 'Commands.__init__ %s' % (g.shortFileName(fileName)) c = self # Official ivars. self._currentPosition = None self._topPosition = None self.frame = None self.gui = gui or self.ipythonController = None # Set only by the ipython plugin. # The order of these calls does not matter. c.initCommandIvars() c.initDebugIvars() c.initDocumentIvars() c.initEventIvars() c.initFileIvars(fileName, relativeFileName) c.initOptionsIvars() c.initObjectIvars() c.initSettings(previousSettings) # Init the settings *before* initing the objects. # Initialize all subsidiary objects, including subcommanders. c.initObjects(self.gui) assert c.frame assert c.frame.c # Complete the init! c.finishCreate() #@+node:ekr.20120217070122.10475: *5* c.computeWindowTitle
[docs] def computeWindowTitle(self, fileName): '''Set the window title and fileName.''' if fileName: title = g.computeWindowTitle(fileName) else: s = "untitled" n = if n > 0: s += str(n) title = g.computeWindowTitle(s) = n + 1 return title
#@+node:ekr.20120217070122.10473: *5* c.initCommandIvars
[docs] def initCommandIvars(self): '''Init ivars used while executing a command.''' self.commandsDict = {} # Keys are command names, values are functions. self.disableCommandsMessage = '' # The presence of this message disables all commands. self.hookFunction = None # One of three places that g.doHook looks for hook functions. self.ignoreChangedPaths = False # True: disable path changed message in at.WriteAllHelper. self.inCommand = False # Interlocks to prevent premature closing of a window. self.isZipped = False # Set by g.openWithFileName. self.outlineToNowebDefaultFileName = "noweb.nw" # For Outline To Noweb dialog. # For tangle/untangle self.tangle_errors = 0 # Default Tangle options self.use_header_flag = False self.output_doc_flag = False # For hoist/dehoist commands. self.hoistStack = [] # Stack of nodes to be root of drawn tree. # Affects drawing routines and find commands. # For outline navigation. self.navPrefix = g.u('') # Must always be a string. self.navTime = None self.sqlite_connection = None
#@+node:ekr.20120217070122.10466: *5* c.initDebugIvars
[docs] def initDebugIvars(self): '''Init Commander debugging ivars.''' self.command_count = 0 self.scanAtPathDirectivesCount = 0 self.trace_focus_count = 0
#@+node:ekr.20120217070122.10471: *5* c.initDocumentIvars
[docs] def initDocumentIvars(self): '''Init per-document ivars.''' self.expansionLevel = 0 # The expansion level of this outline. self.expansionNode = None # The last node we expanded or contracted. self.nodeConflictList = [] # List of nodes with conflicting read-time data. self.nodeConflictFileName = None # The fileName for c.nodeConflictList. self.user_dict = {}
# Non-persistent dictionary for free use by scripts and plugins. #@+node:ekr.20120217070122.10467: *5* c.initEventIvars
[docs] def initEventIvars(self): '''Init ivars relating to gui events.''' self.configInited = False self.doubleClickFlag = False self.exists = True # Indicate that this class exists and has not been destroyed. # Do this early in the startup process so we can call hooks. self.in_qt_dialog = False # True: in a qt dialog. self.loading = False # True: we are loading a file: disables c.setChanged() self.promptingForClose = False # True: lock out additional closing dialogs. self.suppressHeadChanged = False # True: prevent setting c.changed when switching chapters. # Flags for c.outerUpdate... self.enableRedrawFlag = True self.requestCloseWindow = False self.requestedFocusWidget = None self.requestLaterRedraw = False
#@+node:ekr.20120217070122.10472: *5* c.initFileIvars
[docs] def initFileIvars(self, fileName, relativeFileName): '''Init file-related ivars of the commander.''' self.changed = False # True: the ouline has changed since the last save. self.ignored_at_file_nodes = [] # List of nodes for error dialog. self.import_error_nodes = [] # self.last_dir = None # The last used directory. self.mFileName = fileName or '' # Do _not_ use os_path_norm: it converts an empty path to '.' (!!) self.mRelativeFileName = relativeFileName or '' # self.openDirectory = None # self.wrappedFileName = None
# The name of the wrapped file, for wrapper commanders. # Set by LM.initWrapperLeoFile #@+node:ekr.20120217070122.10469: *5* c.initOptionsIvars
[docs] def initOptionsIvars(self): '''Init Commander ivars corresponding to user options.''' self.fixed = False self.fixedWindowPosition = [] self.forceExecuteEntireBody = False self.focus_border_color = 'white' self.focus_border_width = 1 # pixels self.outlineHasInitialFocus = False self.page_width = 132 self.sparse_find = True self.sparse_move = True self.sparse_spell = True self.stayInTreeAfterSelect = False self.tab_width = -4 self.tangle_batch_flag = False self.target_language = "python" self.untangle_batch_flag = False # self.use_body_focus_border = True # self.use_focus_border = False # Replaced by style-sheet entries. self.vim_mode = False
#@+node:ekr.20120217070122.10468: *5* c.initObjectIvars
[docs] def initObjectIvars(self): # These ivars are set later by leoEditCommands.createEditCommanders self.abbrevCommands = None self.editCommands = None self.db = {} # May be set to a PickleShare instance later. self.chapterCommands = None self.controlCommands = None self.convertCommands = None self.debugCommands = None self.editFileCommands = None self.evalController = None self.gotoCommands = None self.helpCommands = None self.keyHandler = self.k = None self.keyHandlerCommands = None self.killBufferCommands = None self.leoCommands = None self.macroCommands = None self.miniBufferWidget = None self.printingController = None self.queryReplaceCommands = None self.rectangleCommands = None self.searchCommands = None self.spellCommands = None self.leoTestManager = None self.vimCommands = None
#@+node:ekr.20120217070122.10470: *5* c.initObjects #@@nobeautify
[docs] def initObjects(self, gui): c = self gnx = 'hidden-root-vnode-gnx' assert not hasattr(c, 'fileCommands'), c.fileCommands class DummyFileCommands: def __init__(self): self.gnxDict = {} c.fileCommands = DummyFileCommands() self.hiddenRootNode = leoNodes.VNode(context=c, gnx=gnx) self.hiddenRootNode.h = "<hidden root vnode>" c.fileCommands = None # Create the gui frame. title = c.computeWindowTitle(c.mFileName) if not g.doHook("before-create-leo-frame", c=c) self.frame = gui.createLeoFrame(c, title) assert self.frame assert self.frame.c == c import leo.core.leoHistory as leoHistory self.nodeHistory = leoHistory.NodeHistory(c) self.initConfigSettings() c.setWindowPosition() # Do this after initing settings. # Break circular import dependencies by doing imports here. # These imports take almost 3/4 sec in the leoBridge. import leo.core.leoAtFile as leoAtFile import leo.core.leoBeautify as leoBeautify # So decorators are executed. assert leoBeautify # for pyflakes. import leo.core.leoCache as leoCache import leo.core.leoChapters as leoChapters # User commands... import leo.commands.abbrevCommands as abbrevCommands import leo.commands.checkerCommands as checkerCommands assert checkerCommands # To suppress a pyflakes warning. # The import *is* required to define commands. import leo.commands.controlCommands as controlCommands import leo.commands.convertCommands as convertCommands import leo.commands.debugCommands as debugCommands import leo.commands.editCommands as editCommands import leo.commands.editFileCommands as editFileCommands import leo.commands.gotoCommands as gotoCommands import leo.commands.helpCommands as helpCommands import leo.commands.keyCommands as keyCommands import leo.commands.killBufferCommands as killBufferCommands import leo.commands.rectangleCommands as rectangleCommands import leo.commands.spellCommands as spellCommands # Import files to execute @g.commander_command decorators import leo.core.leoCompare as leoCompare assert leoCompare import leo.commands.commanderEditCommands as commanderEditCommands assert commanderEditCommands import leo.commands.commanderFileCommands as commanderFileCommands assert commanderFileCommands import leo.commands.commanderFindCommands as commanderFindCommands assert commanderFindCommands import leo.commands.commanderHelpCommands as commanderHelpCommands assert commanderHelpCommands import leo.commands.commanderOutlineCommands as commanderOutlineCommands assert commanderOutlineCommands # Other subcommanders. import leo.core.leoFind as leoFind # Leo 4.11.1 import leo.core.leoKeys as leoKeys import leo.core.leoFileCommands as leoFileCommands import leo.core.leoImport as leoImport import leo.core.leoPersistence as leoPersistence import leo.core.leoPrinting as leoPrinting import leo.core.leoRst as leoRst import leo.core.leoShadow as leoShadow import leo.core.leoTangle as leoTangle import leo.core.leoTest as leoTest import leo.core.leoUndo as leoUndo import leo.core.leoVim as leoVim # Define the subcommanders. self.keyHandler = self.k = leoKeys.KeyHandlerClass(c) self.chapterController = leoChapters.ChapterController(c) self.shadowController = leoShadow.ShadowController(c) self.fileCommands = leoFileCommands.FileCommands(c) self.findCommands = leoFind.LeoFind(c) self.atFileCommands = leoAtFile.AtFile(c) self.importCommands = leoImport.LeoImportCommands(c) self.persistenceController = leoPersistence.PersistenceDataController(c) self.printingController = leoPrinting.PrintingController(c) self.rstCommands = leoRst.RstCommands(c) self.tangleCommands = leoTangle.TangleCommands(c) self.testManager = leoTest.TestManager(c) self.vimCommands = leoVim.VimCommands(c) # User commands self.abbrevCommands = abbrevCommands.AbbrevCommandsClass(c) self.controlCommands = controlCommands.ControlCommandsClass(c) self.convertCommands = convertCommands.ConvertCommandsClass(c) self.debugCommands = debugCommands.DebugCommandsClass(c) self.editCommands = editCommands.EditCommandsClass(c) self.editFileCommands = editFileCommands.EditFileCommandsClass(c) self.gotoCommands = gotoCommands.GoToCommands(c) self.helpCommands = helpCommands.HelpCommandsClass(c) self.keyHandlerCommands = keyCommands.KeyHandlerCommandsClass(c) self.killBufferCommands = killBufferCommands.KillBufferCommandsClass(c) self.rectangleCommands = rectangleCommands.RectangleCommandsClass(c) self.spellCommands = spellCommands.SpellCommandsClass(c) self.undoer = leoUndo.Undoer(c) # Create the list of subcommanders. self.subCommanders = [ self.abbrevCommands, self.atFileCommands, self.chapterController, self.controlCommands, self.convertCommands, self.debugCommands, self.editCommands, self.editFileCommands, self.fileCommands, self.findCommands, self.gotoCommands, self.helpCommands, self.importCommands, self.keyHandler, self.keyHandlerCommands, self.killBufferCommands, self.persistenceController, self.printingController, self.rectangleCommands, self.rstCommands, self.shadowController, self.spellCommands, self.tangleCommands, self.testManager, self.vimCommands, self.undoer, ] # Other objects c.configurables = c.subCommanders[:] # A list of other classes that have a reloadSettings method self.cacher = leoCache.Cacher(c) self.cacher.initFileDB(self.mFileName) import leo.plugins.free_layout as free_layout self.free_layout = free_layout.FreeLayoutController(c) if hasattr(, 'styleSheetManagerClass'): self.styleSheetManager = self.subCommanders.append(self.styleSheetManager) else: self.styleSheetManager = None
#@+node:ekr.20140815160132.18837: *5* c.initSettings
[docs] def initSettings(self, previousSettings): '''Init the settings *before* initing the objects.''' c = self import leo.core.leoConfig as leoConfig c.config = leoConfig.LocalConfigManager(c, previousSettings)
#@+node:ekr.20031218072017.2814: *4* c.__repr__ & __str__ def __repr__(self): return "Commander %d: %s" % (id(self), repr(self.mFileName)) __str__ = __repr__ #@+node:ekr.20050920093543: *4* c.finishCreate & helpers
[docs] def finishCreate(self): ''' Finish creating the commander and all sub-objects. This is the last step in the startup process. ''' c, k = self, self.k assert c.gui assert k c.frame.finishCreate() c.miniBufferWidget = c.frame.miniBufferWidget # Will be None for nullGui. # Only c.abbrevCommands needs a finishCreate method. c.abbrevCommands.finishCreate() # Finish other objects... c.createCommandNames() k.finishCreate() c.findCommands.finishCreate() if not c.gui.isNullGui: g.registerHandler('idle', c.idle_focus_helper) c.frame.log.finishCreate() c.undoer.clearUndoState() if c.vimCommands and c.vim_mode: c.vimCommands.finishCreate() # Menus must exist at this point. # Do not call chapterController.finishCreate here: # It must be called after the first real redraw. g.check_cmd_instance_dict(c, g) c.bodyWantsFocus()
#@+node:ekr.20140815160132.18835: *5* c.createCommandNames
[docs] def createCommandNames(self): ''' Create all entries in c.commandsDict. Do *not* clear c.commandsDict here. ''' for commandName, func in g.global_commands_dict.items(): self.k.registerCommand(commandName, func)
#@+node:ekr.20051007143620: *5* c.printCommandsDict
[docs] def printCommandsDict(self): c = self print('Commands...') for key in sorted(c.commandsDict): command = c.commandsDict.get(key) print('%30s = %s' % ( key, command.__name__ if command else '<None>')) print('')
#@+node:ekr.20041130173135: *4* c.hash # This is a bad idea.
[docs] def hash(self): c = self if c.mFileName: return c.os_path_finalize(c.mFileName).lower() else: return 0
#@+node:ekr.20110509064011.14563: *4* c.idle_focus_helper & helpers idle_focus_count = 0
[docs] def idle_focus_helper(self, tag, keys): '''An idle-tme handler that ensures that focus is *somewhere*.''' trace = 'focus' in trace_inactive_focus = False # Too disruptive for --trace-focus trace_in_dialog = False # Not useful enough for --trace-focus c = self assert tag == 'idle' if return if keys.get('c') != c: if trace: g.trace('no c') return self.idle_focus_count += 1 if c.in_qt_dialog: if trace and trace_in_dialog: g.trace('in_qt_dialog') return w = = True) if # Always call trace_idle_focus. self.trace_idle_focus(w) if w and self.is_unusual_focus(w): if trace: w_class = w and w.__class__.__name__ g.trace('***** unusual focus', w_class) # Fix bug 270: Leo's keyboard events doesn't work after "Insert" # on headline and Alt+Tab, Alt+Tab # Presumably, intricate details of Qt event handling are involved. # The focus was in the tree, so put the focus back in the tree. c.treeWantsFocusNow() # elif not w and active: # c.bodyWantsFocusNow() elif trace and trace_inactive_focus: w_class = w and w.__class__.__name__ count = c.idle_focus_count g.trace('%s inactive focus: %s' % (count, w_class))
#@+node:ekr.20160427062131.1: *5* c.is_unusual_focus
[docs] def is_unusual_focus(self, w): '''Return True if w is not in an expected place.''' if 1: # Fix bug 270: Leo's keyboard events doesn't work after "Insert" # on headline and Alt+Tab, Alt+Tab # Fix #276: Focus Nav text input import leo.plugins.qt_frame as qt_frame return isinstance(w, qt_frame.QtTabBarWrapper) else: # Wrong: we can't list all the "usual" widgets. from leo.core.leoQt import QtWidgets table = ( QtWidgets.QListWidget, QtWidgets.QTextEdit, QtWidgets.QLineEdit, qt_frame.LeoQTreeWidget, ) return not isinstance(w, table)
#@+node:ekr.20150403063658.1: *5* c.trace_idle_focus last_unusual_focus = None # last_no_focus = False
[docs] def trace_idle_focus(self, w): '''Trace the focus for w, minimizing chatter.''' from leo.core.leoQt import QtWidgets import leo.plugins.qt_frame as qt_frame trace = 'focus' in trace_known = False c = self table = ( QtWidgets.QWidget, qt_frame.LeoQTreeWidget, ) count = c.idle_focus_count if w: w_class = w and w.__class__.__name__ c.last_no_focus = False if self.is_unusual_focus(w): if trace: g.trace('%s unusual focus: %s' % (count, w_class)) else: c.last_unusual_focus = None if isinstance(w, table): if trace and trace_known: g.trace('%s known focus: %s' % (count, w_class)) elif trace: g.trace('%s unknown focus: %s' % (count, w_class)) else: if trace: g.trace('%3s no focus' % (count))
#@+node:ekr.20081005065934.1: *4* c.initAfterLoad
[docs] def initAfterLoad(self): '''Provide an offical hook for late inits of the commander.''' pass
#@+node:ekr.20090213065933.6: *4* c.initConfigSettings
[docs] def initConfigSettings(self): '''Init all cached commander config settings.''' c = self getBool = c.config.getBool getColor = c.config.getColor getData = c.config.getData getInt = c.config.getInt # c.allow_at_in_paragraphs = getBool('allow-at-in-paragraphs', default=False) c.autoindent_in_nocolor = getBool('autoindent_in_nocolor_mode') c.collapse_nodes_after_move = getBool('collapse_nodes_after_move') c.collapse_on_lt_arrow = getBool('collapse_on_lt_arrow', default=True) c.contractVisitedNodes = getBool('contractVisitedNodes') c.fixed = getBool('fixedWindow', default=False) c.fixedWindowPositionData = getData('fixedWindowPosition') c.focus_border_color = getColor('focus_border_color') or 'red' c.focus_border_command_state_color = getColor('focus_border_command_state_color') or 'blue' c.focus_border_overwrite_state_color = getColor('focus_border_overwrite_state_color') or 'green' c.focus_border_width = getInt('focus_border_width') or 1 # pixels c.forceExecuteEntireBody = getBool('force_execute_entire_body', default=False) c.make_node_conflicts_node = getBool('make-node-conflicts-node', default=True) c.max_pre_loaded_body_chars = c.config.getInt('max-pre-loaded-body-chars') or 0 c.outlineHasInitialFocus = getBool('outline_pane_has_initial_focus') c.page_width = getInt('page_width') or 132 c.putBitsFlag = getBool('put_expansion_bits_in_leo_files', default=True) c.sparse_move = getBool('sparse_move_outline_left') c.sparse_find = getBool('collapse_nodes_during_finds') c.sparce_spell = getBool('collapse_nodes_while_spelling') c.stayInTreeAfterSelect = getBool('stayInTreeAfterSelect') c.smart_tab = getBool('smart_tab') c.tab_width = getInt('tab_width') or -4 # c.use_body_focus_border = getBool('use_body_focus_border', default=True) # c.use_focus_border = getBool('use_focus_border', default=True) # Not used: replaced by stylesheet settings. c.verbose_check_outline = getBool('verbose_check_outline', default=False) c.vim_mode = getBool('vim_mode', default=False) c.write_script_file = getBool('write_script_file')
#@+node:ekr.20090213065933.7: *4* c.setWindowPosition
[docs] def setWindowPosition(self): c = self if c.fixedWindowPositionData: try: aList = [z.strip() for z in c.fixedWindowPositionData if z.strip()] w, h, l, t = aList c.fixedWindowPosition = int(w), int(h), int(l), int(t) except Exception: g.error('bad @data fixedWindowPosition', repr(self.fixedWindowPosition)) else: c.windowPosition = 500, 700, 50, 50 # width,height,left,top.
#@+node:ekr.20171123135625.4: *3* @cmd c.executeScript & public helpers
[docs] @cmd('execute-script') def executeScript(self, event=None, args=None, p=None, script=None, useSelectedText=True, define_g=True, define_name='__main__', silent=False, namespace=None, raiseFlag=False, ): ''' Execute a *Leo* script. Keyword args: args=None Not None: set script_args in the execution environment. p=None Get the script from p.b, unless script is given. script=None None: use script in p.b or c.p.b useSelectedText=True False: use all the text in p.b or c.p.b. define_g=True True: define g for the script. define_name='__main__' Not None: define the name symbol. silent=False No longer used. namespace=None Not None: execute the script in this namespace. raiseFlag=False True: reraise any exceptions. ''' c, script1 = self, script run_pyflakes = c.config.getBool('run-pyflakes-on-write', default=False) if not script: if c.forceExecuteEntireBody: useSelectedText = False script = g.getScript(c, p or c.p, useSelectedText=useSelectedText) script_p = p or c.p # Only for error reporting below. # #532: check all scripts with pyflakes. if run_pyflakes and not g.unitTesting: import leo.commands.checkerCommands as cc # at = c.atFileCommands prefix = ('c,g,p,script_gnx=None,None,None,None;' 'assert c and g and p and script_gnx;\n') cc.PyflakesCommand(c).check_script(script_p, prefix+script) self.redirectScriptOutput() try: oldLog = log = c.frame.log = log if script.strip(): sys.path.insert(0, '.') # New in Leo 5.0 sys.path.insert(0, c.frame.openDirectory) # per SegundoBob script += '\n' # Make sure we end the script properly. try: if not namespace or namespace.get('script_gnx') is None: namespace = namespace or {} namespace.update(script_gnx=script_p.gnx) # We *always* execute the script with p = c.p. c.executeScriptHelper(args, define_g, define_name, namespace, script) except Exception: if raiseFlag: raise else: g.handleScriptException(c, script_p, script, script1) finally: del sys.path[0] del sys.path[0] else: tabName = log and hasattr(log, 'tabName') and log.tabName or 'Log' g.warning("no script selected", tabName=tabName) finally: = oldLog self.unredirectScriptOutput()
#@+node:ekr.20171123135625.5: *4* c.executeScriptHelper
[docs] def executeScriptHelper(self, args, define_g, define_name, namespace, script): c = self if c.p: p = c.p.copy() # *Always* use c.p and pass c.p to script. c.setCurrentDirectoryFromContext(p) else: p = None # Do NOT define a subfunction here! # # On some, python 2.x versions it causes exec to cause a syntax error # Workarounds that avoid the syntax error hurt performance. # See # def g_input_wrapper(message, c=c): # return g.input_(message, c=c) d = {'c': c, 'g': g, 'input': g.input_, 'p': p} if define_g else {} if define_name: d['__name__'] = define_name d['script_args'] = args or [] d['script_gnx'] ='script_gnx') if namespace: d.update(namespace) # A kludge: reset c.inCommand here to handle the case where we *never* return. # (This can happen when there are multiple event loops.) # This does not prevent zombie windows if the script puts up a dialog... try: c.inCommand = False g.inScript = = True # g.inScript is a synonym for if c.write_script_file: scriptFile = self.writeScriptFile(script) # pylint: disable=undefined-variable, no-member if g.isPython3: exec(compile(script, scriptFile, 'exec'), d) else: builtins.execfile(scriptFile, d) else: exec(script, d) finally: g.inScript = = False
#@+node:ekr.20171123135625.6: *4* c.redirectScriptOutput
[docs] def redirectScriptOutput(self): c = self if c.config.redirect_execute_script_output_to_log_pane: g.redirectStdout() # Redirect stdout g.redirectStderr() # Redirect stderr
#@+node:ekr.20171123135625.7: *4* c.setCurrentDirectoryFromContext
[docs] def setCurrentDirectoryFromContext(self, p): c = self aList = g.get_directives_dict_list(p) path = c.scanAtPathDirectives(aList) curDir = g.os_path_abspath(os.getcwd()) if path and path != curDir: try: os.chdir(path) except Exception: pass
#@+node:ekr.20171123135625.8: *4* c.unredirectScriptOutput
[docs] def unredirectScriptOutput(self): c = self if c.exists and c.config.redirect_execute_script_output_to_log_pane: g.restoreStderr() g.restoreStdout()
#@+node:ekr.20080514131122.12: *3* @cmd c.recolorCommand
[docs] @cmd('recolor') def recolorCommand(self, event=None): '''Force a full recolor.''' c = self wrapper = c.frame.body.wrapper # Setting all text appears to be the only way. i, j = wrapper.getSelectionRange() ins = wrapper.getInsertPoint() wrapper.setAllText(c.p.b) wrapper.setSelectionRange(i, j, insert=ins)
#@+node:ekr.20171124100654.1: *3* c.API # These methods are a fundamental, unchanging, part of Leo's API. #@+node:ekr.20091001141621.6061: *4* c.Generators #@+node:ekr.20091001141621.6043: *5* c.all_nodes & all_unique_nodes
[docs] def all_nodes(self): '''A generator returning all vnodes in the outline, in outline order.''' c = self for p in c.all_positions(): yield p.v
[docs] def all_unique_nodes(self): '''A generator returning each vnode of the outline.''' c = self for p in c.all_unique_positions(): yield p.v
# Compatibility with old code... all_tnodes_iter = all_nodes all_vnodes_iter = all_nodes all_unique_tnodes_iter = all_unique_nodes all_unique_vnodes_iter = all_unique_nodes #@+node:ekr.20091001141621.6044: *5* c.all_positions
[docs] def all_positions(self): '''A generator return all positions of the outline, in outline order.''' c = self p = c.rootPosition() while p: yield p.copy() p.moveToThreadNext()
# Compatibility with old code... all_positions_iter = all_positions allNodes_iter = all_positions #@+node:ekr.20161120121226.1: *5* c.all_roots
[docs] def all_roots(self, predicate=None): ''' A generator yielding *all* the root positions in the outline that satisfy the given predicate. p.isAnyAtFileNode is the default predicate. The generator yields all **root** anywhere in the outline that satisfy the predicate. Once a root is found, the generator skips its subtree. ''' c = self if predicate is None: # pylint: disable=function-redefined def predicate(p): return p.isAnyAtFileNode() p = c.rootPosition() while p: if predicate(p): yield p.copy() # 2017/02/19 p.moveToNodeAfterTree() else: p.moveToThreadNext()
#@+node:ekr.20161120125322.1: *5* c.all_unique_roots
[docs] def all_unique_roots(self, predicate=None): ''' A generator yielding all unique root positions in the outline that satisfy the given predicate. p.isAnyAtFileNode is the default predicate. The generator yields all **root** anywhere in the outline that satisfy the predicate. Once a root is found, the generator skips its subtree. ''' c = self if predicate is None: # pylint: disable=function-redefined def predicate(p): return p.isAnyAtFileNode() seen = set() p = c.rootPosition() while p: if p.v not in seen and predicate(p): seen.add(p.v) yield p.copy() # 2017/02/19 p.moveToNodeAfterTree() else: p.moveToThreadNext()
#@+node:ekr.20091001141621.6062: *5* c.all_unique_positions
[docs] def all_unique_positions(self): ''' A generator return all positions of the outline, in outline order. Returns only the first position for each vnode. ''' c = self p = c.rootPosition() seen = set() while p: if p.v in seen: p.moveToNodeAfterTree() else: seen.add(p.v) yield p.copy() p.moveToThreadNext()
# Compatibility with old code... all_positions_with_unique_tnodes_iter = all_unique_positions all_positions_with_unique_vnodes_iter = all_unique_positions #@+node:ekr.20150316175921.5: *5* c.safe_all_positions
[docs] def safe_all_positions(self): ''' A generator returning all positions of the outline. This generator does *not* assume that vnodes are never their own ancestors. ''' c = self p = c.rootPosition() # Make one copy. while p: yield p.copy() p.safeMoveToThreadNext()
#@+node:ekr.20060906211747: *4* c.Getters #@+node:ekr.20040803140033: *5* c.currentPosition
[docs] def currentPosition(self): """ Return a copy of the presently selected position or a new null position. So c.p.copy() is never necessary. """ c = self if hasattr(c, '_currentPosition') and getattr(c, '_currentPosition'): # New in Leo 4.4.2: *always* return a copy. return c._currentPosition.copy() else: return c.rootPosition()
# For compatibiility with old scripts... currentVnode = currentPosition #@+node:ekr.20040306220230.1: *5* c.edit_widget
[docs] def edit_widget(self, p): c = self return p and c.frame.tree.edit_widget(p)
#@+node:ekr.20031218072017.2986: *5* c.fileName & relativeFileName & shortFileName # Compatibility with scripts
[docs] def fileName(self): s = self.mFileName if g.isWindows: s = s.replace('\\','/') return s
[docs] def relativeFileName(self): return self.mRelativeFileName or self.mFileName
[docs] def shortFileName(self): return g.shortFileName(self.mFileName)
shortFilename = shortFileName #@+node:ekr.20070615070925.1: *5* c.firstVisible
[docs] def firstVisible(self): """Move to the first visible node of the present chapter or hoist.""" c = self; p = c.p while 1: back = p.visBack(c) if back and back.isVisible(c): p = back else: break return p
#@+node:ekr.20171123135625.29: *5* c.getBodyLines
[docs] def getBodyLines(self, expandSelection=False): """ Return head,lines,tail where: before is string containg all the lines before the selected text (or the text before the insert point if no selection) lines is a list of lines containing the selected text (or the line containing the insert point if no selection) after is a string all lines after the selected text (or the text after the insert point if no selection) """ c = self body = c.frame.body w = body.wrapper oldVview = w.getYScrollPosition() if expandSelection: s = w.getAllText() head = tail = '' oldSel = 0, len(s) lines = g.splitLines(s) # Retain the newlines of each line. else: # Note: lines is the entire line containing the insert point if no selection. head, s, tail = body.getSelectionLines() lines = g.splitLines(s) # Retain the newlines of each line. # Expand the selection. i = len(head) j = max(i, len(head) + len(s) - 1) oldSel = i, j return head, lines, tail, oldSel, oldVview # string,list,string,tuple.
#@+node:ekr.20150417073117.1: *5* c.getTabWidth
[docs] def getTabWidth(self, p): '''Return the tab width in effect at p.''' c = self val = g.scanAllAtTabWidthDirectives(c, p) return val
#@+node:ekr.20040803112200: *5* #@+node:ekr.20040803155551: *6* c.currentPositionIsRootPosition
[docs] def currentPositionIsRootPosition(self): """Return True if the current position is the root position. This method is called during idle time, so not generating positions here fixes a major leak. """ c = self root = c.rootPosition() return c._currentPosition and root and c._currentPosition == root
# return ( # c._currentPosition and c._rootPosition and # c._currentPosition == c._rootPosition) #@+node:ekr.20040803160656: *6* c.currentPositionHasNext
[docs] def currentPositionHasNext(self): """Return True if the current position is the root position. This method is called during idle time, so not generating positions here fixes a major leak. """ c = self; current = c._currentPosition return current and current.hasNext()
#@+node:ekr.20040803112450: *6* c.isCurrentPosition
[docs] def isCurrentPosition(self, p): c = self if p is None or c._currentPosition is None: return False else: return p == c._currentPosition
#@+node:ekr.20040803112450.1: *6* c.isRootPosition
[docs] def isRootPosition(self, p): c = self root = c.rootPosition() return p and root and p == root # 2011/03/03
#@+node:ekr.20031218072017.2987: *5* c.isChanged
[docs] def isChanged(self): return self.changed
#@+node:ekr.20140106215321.16676: *5* c.lastTopLevel
[docs] def lastTopLevel(self): '''Return the last top-level position in the outline.''' c = self p = c.rootPosition() while p.hasNext(): p.moveToNext() return p
#@+node:ekr.20031218072017.4146: *5* c.lastVisible
[docs] def lastVisible(self): """Move to the last visible node of the present chapter or hoist.""" c = self; p = c.p while 1: next = p.visNext(c) if next and next.isVisible(c): p = next else: break return p
#@+node:ekr.20040311094927: *5* c.nullPosition
[docs] def nullPosition(self): ''' New in Leo 5.5: Return None. Using empty positions masks problems in program logic. In fact, there are no longer any calls to this method in Leo's core. ''' g.trace('This method is deprecated. Instead, just use None.') return None
#@+node:ekr.20040307104131.3: *5* c.positionExists
[docs] def positionExists(self, p, root=None, trace=False): """Return True if a position exists in c's tree""" # Important: do not call p.isAncestorOf here. c = self if not p or not p.v: return False if root and p == root: return True p = p.copy() while p.hasParent(): old_n, old_v = p._childIndex, p.v p.moveToParent() if root and p == root: return True elif not old_v.isNthChildOf(old_n, p.v): return False if root: exists = p == root else: exists = p.v.isNthChildOf(p._childIndex, c.hiddenRootNode) return exists
#@+node:ekr.20160427153457.1: *6* c.dumpPosition
[docs] def dumpPosition(self, p): '''Dump position p and it's ancestors.''' g.trace('=====',p.h, p._childIndex) for i, data in enumerate(p.stack): v, childIndex = data print('%s %s %s' % (i, childIndex, v._headString))
#@+node:ekr.20040803140033.2: *5* c.rootPosition _rootCount = 0
[docs] def rootPosition(self): """Return the root position. Root position is the first position in the document. Other top level positions are siblings of this node. """ c = self # 2011/02/25: Compute the position directly. if c.hiddenRootNode.children: v = c.hiddenRootNode.children[0] return leoNodes.Position(v, childIndex=0, stack=None) else: return None
# For compatibiility with old scripts... rootVnode = rootPosition findRootPosition = rootPosition #@+node:ekr.20131017174814.17480: *5* c.shouldBeExpanded
[docs] def shouldBeExpanded(self, p): '''Return True if the node at position p should be expanded.''' c, v = self, p.v if not p.hasChildren(): return False # Always clear non-existent positions. v.expandedPositions = [z for z in v.expandedPositions if c.positionExists(z)] if not p.isCloned(): # Do not call p.isExpanded here! It calls this method. return p.v.isExpanded() if p.isAncestorOf(c.p): return True for p2 in v.expandedPositions: if p == p2: return True return False
#@+node:ekr.20070609122713: *5* c.visLimit
[docs] def visLimit(self): ''' Return the topmost visible node. This is affected by chapters and hoists. ''' c = self; cc = c.chapterController if c.hoistStack: bunch = c.hoistStack[-1] p = bunch.p limitIsVisible = not cc or not p.h.startswith('@chapter') return p, limitIsVisible else: return None, None
#@+node:tbrown.20091206142842.10296: *5* c.vnode2allPositions
[docs] def vnode2allPositions(self, v): '''Given a VNode v, find all valid positions p such that p.v = v. Not really all, just all for each of v's distinct immediate parents. ''' c = self context = v.context # v's commander. assert(c == context) positions = [] for immediate in v.parents: if v in immediate.children: n = immediate.children.index(v) else: continue stack = [(v, n)] while immediate.parents: parent = immediate.parents[0] if immediate in parent.children: n = parent.children.index(immediate) else: break stack.insert(0, (immediate, n),) immediate = parent else: v, n = stack.pop() p = leoNodes.Position(v, n, stack) positions.append(p) return positions
#@+node:ekr.20090107113956.1: *5* c.vnode2position
[docs] def vnode2position(self, v): '''Given a VNode v, construct a valid position p such that p.v = v. ''' c = self context = v.context # v's commander. assert(c == context) stack = [] while v.parents: parent = v.parents[0] if v in parent.children: n = parent.children.index(v) else: return None stack.insert(0, (v, n),) v = parent # v.parents includes the hidden root node. if not stack: # a VNode not in the tree return None v, n = stack.pop() p = leoNodes.Position(v, n, stack) return p
#@+node:ekr.20090130135126.1: *4* c.Properties def __get_p(self): c = self return c.currentPosition() p = property( __get_p, # No setter. doc="commander current position property") #@+node:ekr.20060906211747.1: *4* c.Setters #@+node:ekr.20040315032503: *5* c.appendStringToBody
[docs] def appendStringToBody(self, p, s): c = self if not s: return body = p.b assert(g.isUnicode(body)) s = g.toUnicode(s) c.setBodyString(p, body + s)
#@+node:ekr.20031218072017.2984: *5* c.clearAllMarked
[docs] def clearAllMarked(self): c = self for p in c.all_unique_positions(): p.v.clearMarked()
#@+node:ekr.20031218072017.2985: *5* c.clearAllVisited
[docs] def clearAllVisited(self): c = self for p in c.all_unique_positions(): p.v.clearVisited() p.v.clearWriteBit()
#@+node:ekr.20060906211138: *5* c.clearMarked
[docs] def clearMarked(self, p): c = self p.v.clearMarked() g.doHook("clear-mark", c=c, p=p)
#@+node:ekr.20040305223522: *5* c.setBodyString
[docs] def setBodyString(self, p, s): c = self; v = p.v if not c or not v: return s = g.toUnicode(s) current = c.p # 1/22/05: Major change: the previous test was: 'if p == current:' # This worked because commands work on the presently selected node. # But setRecentFiles may change a _clone_ of the selected node! if current and p.v == current.v: # Revert to previous code, but force an empty selection. c.frame.body.setSelectionAreas(s, None, None) w = c.frame.body.wrapper i = w.getInsertPoint() w.setSelectionRange(i, i) # This code destoys all tags, so we must recolor. c.recolor() # Keep the body text in the VNode up-to-date. if v.b != s: v.setBodyString(s) v.setSelection(0, 0) p.setDirty() if not c.isChanged(): c.setChanged(True) c.redraw_after_icons_changed()
#@+node:ekr.20031218072017.2989: *5* c.setChanged
[docs] def setChanged(self, changedFlag=True, redrawFlag=True): '''Set or clear the marker that indicates that the .leo file has been changed.''' c = self if not c.frame: return c.changed = changedFlag if c.loading: return # don't update while loading. # Clear all dirty bits _before_ setting the caption. if not changedFlag: for v in c.all_unique_nodes(): if v.isDirty(): v.clearDirty() # Do nothing for null frames. assert c.gui if c.gui.guiName() == 'nullGui': return if not return master = hasattr(, 'leo_master') and if redrawFlag: # Prevent flash when fixing #387. if master: # Call LeoTabbedTopLevel.setChanged. master.setChanged(c, changedFlag) s = c.frame.getTitle() if len(s) > 2: if changedFlag: if s[0] != '*': c.frame.setTitle("* " + s) else: if s[0: 2] == "* ": c.frame.setTitle(s[2:])
#@+node:ekr.20040803140033.1: *5* c.setCurrentPosition _currentCount = 0
[docs] def setCurrentPosition(self, p): """ Set the presently selected position. For internal use only. Client code should use c.selectPosition instead. """ c = self if not p: g.trace('===== no p', g.callers()) return if c.positionExists(p): if c._currentPosition and p == c._currentPosition: pass # We have already made a copy. else: # Make a copy _now_ c._currentPosition = p.copy() else: # 2011/02/25: c._currentPosition = c.rootPosition() g.trace('Invalid position: %r, root: %r' % ( p and p.h, c._currentPosition and c._currentPosition.h), g.callers(), )
# Don't kill unit tests for this kind of problem. # For compatibiility with old scripts. setCurrentVnode = setCurrentPosition #@+node:ekr.20040305223225: *5* c.setHeadString
[docs] def setHeadString(self, p, s): '''Set the p's headline and the corresponding tree widget to s. This is used in by unit tests to restore the outline.''' c = self p.initHeadString(s) p.setDirty() # Change the actual tree widget so # A later call to c.endEditing or c.redraw will use s. c.frame.tree.setHeadline(p, s)
#@+node:ekr.20060109164136: *5* c.setLog
[docs] def setLog(self): c = self if c.exists: try: # c.frame or c.frame.log may not exist. except AttributeError: pass
#@+node:ekr.20060906211138.1: *5* c.setMarked
[docs] def setMarked(self, p): c = self p.v.setMarked() g.doHook("set-mark", c=c, p=p)
#@+node:ekr.20040803140033.3: *5* c.setRootPosition (A do-nothing)
[docs] def setRootPosition(self, unused_p=None): """Set c._rootPosition."""
# 2011/03/03: No longer used. #@+node:ekr.20060906131836: *5* c.setRootVnode (A do-nothing)
[docs] def setRootVnode(self, v): pass
# c = self # # 2011/02/25: c.setRootPosition needs no arguments. # c.setRootPosition() #@+node:ekr.20040311173238: *5* c.topPosition & c.setTopPosition
[docs] def topPosition(self): """Return the root position.""" c = self if c._topPosition: return c._topPosition.copy() else: return None
[docs] def setTopPosition(self, p): """Set the root positioin.""" c = self if p: c._topPosition = p.copy() else: c._topPosition = None
# Define these for compatibiility with old scripts... topVnode = topPosition setTopVnode = setTopPosition #@+node:ekr.20031218072017.3404: *5* c.trimTrailingLines
[docs] def trimTrailingLines(self, p): """Trims trailing blank lines from a node. It is surprising difficult to do this during Untangle.""" c = self body = p.b lines = body.split('\n') i = len(lines) - 1; changed = False while i >= 0: line = lines[i] j = g.skip_ws(line, 0) if j + 1 == len(line): del lines[i] i -= 1; changed = True else: break if changed: body = ''.join(body) + '\n' # Add back one last newline. c.setBodyString(p, body)
# Don't set the dirty bit: it would just be annoying. #@+node:ekr.20171124081419.1: *3* c.Check Outline... #@+node:ekr.20141024211256.22: *4* c.checkGnxs
[docs] def checkGnxs(self): ''' Check the consistency of all gnx's and remove any tnodeLists. Reallocate gnx's for duplicates or empty gnx's. Return the number of structure_errors found. ''' c = self d = {} # Keys are gnx's; values are lists of vnodes with that gnx. ni = t1 = time.time() def new_gnx(v): '''Set v.fileIndex.''' v.fileIndex = ni.getNewIndex(v) count, gnx_errors = 0, 0 for p in c.safe_all_positions(): count += 1 v = p.v if hasattr(v, "tnodeList"): delattr(v, "tnodeList") v._p_changed = True gnx = v.fileIndex if gnx: aSet = d.get(gnx, set()) aSet.add(v) d[gnx] = aSet else: gnx_errors += 1 new_gnx(v) g.es_print('empty v.fileIndex: %s new: %r' % (v, p.v.gnx), color='red') for gnx in sorted(d.keys()): aList = list(d.get(gnx)) if len(aList) != 1: g.es_print('multiple vnodes with gnx: %r' % (gnx), color='red') for v in aList: gnx_errors += 1 g.es_print('new gnx: %s %s' % (v.fileIndex, v), color='red') new_gnx(v) ok = not gnx_errors and not t2 = time.time() if not ok: g.es_print('check-outline ERROR! %s %s nodes, %s gnx errors, %s structure errors' % ( c.shortFileName(), count, gnx_errors,, color='red') elif c.verbose_check_outline and not g.unitTesting: print('check-outline OK: %4.2f sec. %s %s nodes' % (t2 - t1, c.shortFileName(), count)) return
#@+node:ekr.20150318131947.7: *4* c.checkLinks & helpers #@+node:ekr.20040314035615.2: *5* c.checkParentAndChildren
[docs] def checkParentAndChildren(self, p): '''Check consistency of parent and child data structures.''' c = self def _assert(condition): return g._assert(condition, show_callers=False) def dump(p): if p and p.v: p.v.dump() elif p: print('<no p.v>') else: print('<no p>') if g.unitTesting: assert False, g.callers() if p.hasParent(): n = p.childIndex() if not _assert(p == p.parent().moveToNthChild(n)): g.trace("p != parent().moveToNthChild(%s)" % (n)) dump(p) dump(p.parent()) return False if p.level() > 0 and not _assert(p.v.parents): g.trace("no parents") dump(p) return False for child in p.children(): if not c.checkParentAndChildren(child): return False if not _assert(p == child.parent()): g.trace("p != child.parent()") dump(p) dump(child.parent()) return False if p.hasNext(): if not _assert( == p.parent()): g.trace(" != p.parent()") dump( dump(p.parent()) return False if p.hasBack(): if not _assert(p.back().parent() == p.parent()): g.trace("p.back().parent() != parent()") dump(p.back().parent()) dump(p.parent()) return False # Check consistency of parent and children arrays. # Every nodes gets visited, so a strong test need only check consistency # between p and its parent, not between p and its children. parent_v = p._parentVnode() n = p.childIndex() if not _assert(parent_v.children[n] == p.v): g.trace("parent_v.children[n] != p.v") parent_v.dump() p.v.dump() return False return True
#@+node:ekr.20040314035615.1: *5* c.checkSiblings
[docs] def checkSiblings(self, p): '''Check the consistency of next and back links.''' back = p.back() next = if back: if not g._assert(p == g.trace('p!=p.back().next(), back: %s\ %s' % ( back, return False if next: if not g._assert(p == next.back()): g.trace('p!, next: %s\nnext.back: %s' % ( next, next.back())) return False return True
#@+node:ekr.20040314035615: *5* c.checkThreadLinks #@+node:ekr.20031218072017.1760: *4* c.checkMoveWithParentWithWarning & c.checkDrag #@+node:ekr.20070910105044: *5* c.checkMoveWithParentWithWarning
[docs] def checkMoveWithParentWithWarning(self, root, parent, warningFlag): """Return False if root or any of root's descendents is a clone of parent or any of parents ancestors.""" c = self message = "Illegal move or drag: no clone may contain a clone of itself" clonedVnodes = {} for ancestor in parent.self_and_parents(): if ancestor.isCloned(): v = ancestor.v clonedVnodes[v] = v if not clonedVnodes: return True for p in root.self_and_subtree(): if p.isCloned() and clonedVnodes.get(p.v): if['checkMoveWithParentWithWarning'] = True elif warningFlag: c.alert(message) return False return True
#@+node:ekr.20070910105044.1: *5* c.checkDrag
[docs] def checkDrag(self, root, target): """Return False if target is any descendant of root.""" c = self message = "Can not drag a node into its descendant tree." for z in root.subtree(): if z == target: if['checkMoveWithParentWithWarning'] = True else: c.alert(message) return False return True
#@+node:ekr.20031218072017.2072: *4* c.checkOutline
[docs] def checkOutline(self, event=None, check_links=False): """ Check for errors in the outline. Return the count of serious structure errors. """ # The check-outline command sets check_links = True. c = self = 0 structure_errors = c.checkGnxs() if check_links and not structure_errors: structure_errors += c.checkLinks() return structure_errors
#@+node:ekr.20031218072017.1765: *4* c.validateOutline # Makes sure all nodes are valid.
[docs] def validateOutline(self, event=None): c = self if not return True root = c.rootPosition() parent = None if root: return root.validateOutlineWithParent(parent) else: return True
#@+node:ekr.20040723094220: *3* c.Check Python code # This code is no longer used by any Leo command, # but it will be retained for use of scripts. #@+node:ekr.20040723094220.1: *4* c.checkAllPythonCode
[docs] def checkAllPythonCode(self, event=None, unittest=False, ignoreAtIgnore=True): '''Check all nodes in the selected tree for syntax and tab errors.''' c = self; count = 0; result = "ok" for p in c.all_unique_positions(): count += 1 if not unittest: #@+<< print dots >> #@+node:ekr.20040723094220.2: *5* << print dots >> if count % 100 == 0:'', '.', newline=False) if count % 2000 == 0: g.enl() #@-<< print dots >> if g.scanForAtLanguage(c, p) == "python": if not g.scanForAtSettings(p) and ( not ignoreAtIgnore or not g.scanForAtIgnore(c, p) ): try: c.checkPythonNode(p, unittest) except(SyntaxError, tokenize.TokenError, tabnanny.NannyNag): result = "error" # Continue to check. except Exception: return "surprise" # abort if unittest and result != "ok":"Syntax error in %s" % p.cleanHeadString()) return result # End the unit test: it has failed. if not unittest:"check complete") return result
#@+node:ekr.20040723094220.3: *4* c.checkPythonCode
[docs] def checkPythonCode(self, event=None, unittest=False, ignoreAtIgnore=True, suppressErrors=False, checkOnSave=False ): '''Check the selected tree for syntax and tab errors.''' c = self; count = 0; result = "ok" if not unittest:"checking Python code ") for p in c.p.self_and_subtree(): count += 1 if not unittest and not checkOnSave: #@+<< print dots >> #@+node:ekr.20040723094220.4: *5* << print dots >> if count % 100 == 0:'', '.', newline=False) if count % 2000 == 0: g.enl() #@-<< print dots >> if g.scanForAtLanguage(c, p) == "python": if not ignoreAtIgnore or not g.scanForAtIgnore(c, p): try: c.checkPythonNode(p, unittest, suppressErrors) except(SyntaxError, tokenize.TokenError, tabnanny.NannyNag): result = "error" # Continue to check. except Exception: return "surprise" # abort if not unittest:"check complete") # We _can_ return a result for unit tests because we aren't using doCommand. return result
#@+node:ekr.20040723094220.5: *4* c.checkPythonNode
[docs] def checkPythonNode(self, p, unittest=False, suppressErrors=False): c = self; h = p.h # Call getScript to ignore directives and section references. body = g.getScript(c, p.copy()) if not body: return try: fn = '<node: %s>' % p.h if not g.isPython3: body = g.toEncodedString(body) compile(body + '\n', fn, 'exec') c.tabNannyNode(p, h, body, unittest, suppressErrors) except SyntaxError: if not suppressErrors: g.warning("Syntax error in: %s" % h) g.es_exception(full=False, color="black") if unittest: raise except Exception: g.es_print('unexpected exception') g.es_exception() if unittest: raise
#@+node:ekr.20040723094220.6: *4* c.tabNannyNode # This code is based on tabnanny.check.
[docs] def tabNannyNode(self, p, headline, body, unittest=False, suppressErrors=False): """Check indentation using tabnanny.""" # c = self try: readline = g.ReadLinesClass(body).next tabnanny.process_tokens(tokenize.generate_tokens(readline)) except IndentationError: junk, msg, junk = sys.exc_info() if not suppressErrors: g.warning("IndentationError in", headline)'', msg) if unittest: raise except tokenize.TokenError: junk, msg, junk = sys.exc_info() if not suppressErrors: g.warning("TokenError in", headline)'', msg) if unittest: raise except tabnanny.NannyNag: junk, nag, junk = sys.exc_info() if not suppressErrors: badline = nag.get_lineno() line = nag.get_line() message = nag.get_msg() g.warning("indentation error in", headline, "line", badline) line2 = repr(str(line))[1: -1]"offending line:\n", line2) if unittest: raise except Exception: g.trace("unexpected exception") g.es_exception() if unittest: raise
#@+node:ekr.20171123200644.1: *3* c.Convenience methods #@+node:ekr.20171123135625.39: *4* c.getTime
[docs] def getTime(self, body=True): c = self default_format = "%m/%d/%Y %H:%M:%S" # E.g., 1/30/2003 8:31:55 # Try to get the format string from settings. if body: format = c.config.getString("body_time_format_string") gmt = c.config.getBool("body_gmt_time") else: format = c.config.getString("headline_time_format_string") gmt = c.config.getBool("headline_gmt_time") if format is None: format = default_format try: # import time if gmt: s = time.strftime(format, time.gmtime()) else: s = time.strftime(format, time.localtime()) except(ImportError, NameError): g.warning("time.strftime not available on this platform") return "" except Exception: g.es_exception() # Probably a bad format string in leoSettings.leo. s = time.strftime(default_format, time.gmtime()) return s
#@+node:ekr.20171123135625.10: *4* c.goToLineNumber & goToScriptLineNumber
[docs] def goToLineNumber(self, n): """ Go to line n (zero-based) of a script. A convenience method called from g.handleScriptException. """ c = self c.gotoCommands.find_file_line(n)
[docs] def goToScriptLineNumber(self, n, p): """ Go to line n (zero-based) of a script. A convenience method called from g.handleScriptException. """ c = self c.gotoCommands.find_script_line(n, p)
#@+node:ekr.20090103070824.9: *4* c.setFileTimeStamp
[docs] def setFileTimeStamp(self, fn): '''Update the timestamp for fn..''' # c = self if
#@+node:ekr.20031218072017.3000: *4* c.updateSyntaxColorer
[docs] def updateSyntaxColorer(self, v): self.frame.body.updateSyntaxColorer(v)
#@+node:ekr.20180503110307.1: *4* c.interactive* #@+node:ekr.20180504075937.1: *5* c.interactive
[docs] def interactive(self, callback, event, prompts): #@+<< c.interactive docstring >> #@+node:ekr.20180503131222.1: *6* << c.interactive docstring >> ''' c.interactive: Prompt for up to three arguments from the minibuffer. The number of prompts determines the number of arguments. Use the @command decorator to define commands. Examples: @g.command('i3') def i3_command(event): c = event.get('c') if not c: return def callback(args, c, event): g.trace(args) c.bodyWantsFocus() c.interactive(callback, event, prompts=['Arg1: ', ' Arg2: ', ' Arg3: ']) ''' #@-<< c.interactive docstring >> # # This pathetic code should be generalized, # but it's not as easy as one might imagine. c = self d = { 1: c.interactive1, 2: c.interactive2, 3: c.interactive3, } f = d.get(len(prompts)) if f: f(callback, event, prompts) else: g.trace('At most 3 arguments are supported.')
#@+node:ekr.20180503111213.1: *5* c.interactive1
[docs] def interactive1(self, callback, event, prompts): c, k = self, self.k prompt = prompts[0] def state1(event): callback(args=[k.arg], c=c, event=event) k.clearState() k.resetLabel() k.showStateAndMode() k.setLabelBlue(prompt) k.get1Arg(event, handler=state1)
#@+node:ekr.20180503111249.1: *5* c.interactive2
[docs] def interactive2(self, callback, event, prompts): c, d, k = self, {}, self.k prompt1, prompt2 = prompts def state1(event): d['arg1'] = k.arg k.extendLabel(prompt2, select=False, protect=True) k.getNextArg(handler=state2) def state2(event): callback(args=[d.get('arg1'), k.arg], c=c, event=event) k.clearState() k.resetLabel() k.showStateAndMode() k.setLabelBlue(prompt1) k.get1Arg(event, handler=state1)
#@+node:ekr.20180503111249.2: *5* c.interactive3
[docs] def interactive3(self, callback, event, prompts): c, d, k = self, {}, self.k prompt1, prompt2, prompt3 = prompts def state1(event): d ['arg1'] = k.arg k.extendLabel(prompt2, select=False, protect=True) k.getNextArg(handler=state2) def state2(event): d ['arg2'] = k.arg k.extendLabel(prompt3, select=False, protect=True) k.get1Arg(event, handler=state3) # Restart. def state3(event): args=[d.get('arg1'), d.get('arg2'), k.arg] callback(args=args, c=c, event=event) k.clearState() k.resetLabel() k.showStateAndMode() k.setLabelBlue(prompt1) k.get1Arg(event, handler=state1)
#@+node:ekr.20080901124540.1: *3* c.Directive scanning # These are all new in Leo 4.5.1. #@+node:ekr.20171123135625.33: *4* c.getLanguageAtCursor
[docs] def getLanguageAtCursor(self, p, language): ''' Return the language in effect at the present insert point. Use the language argument as a default if no @language directive seen. ''' c = self tag = '@language' w = c.frame.body.wrapper ins = w.getInsertPoint() n = 0 for s in g.splitLines(p.b): if g.match_word(s, 0, tag): i = g.skip_ws(s, len(tag)) j = g.skip_id(s, i) language = s[i: j] if n <= ins < n + len(s): break else: n += len(s) return language
#@+node:ekr.20081006100835.1: *4* c.getNodePath & c.getNodeFileName # Not used in Leo's core. # Used by the UNl plugin. Does not need to create a path.
[docs] def getNodePath(self, p): '''Return the path in effect at node p.''' c = self aList = g.get_directives_dict_list(p) path = c.scanAtPathDirectives(aList) return path
# Not used in Leo's core.
[docs] def getNodeFileName(self, p): ''' Return the full file name at node p, including effects of all @path directives. Return None if p is no kind of @file node. ''' c = self path = g.scanAllAtPathDirectives(c, p) name = '' for p in p.self_and_parents(): name = p.anyAtFileNodeName() if name: break if name: # The commander method supports {{expr}}; the global function does not. name = c.os_path_finalize_join(path, name) return name
#@+node:ekr.20171123135625.32: *4* c.hasAmbiguousLangauge
[docs] def hasAmbiguousLanguage(self, p): '''Return True if p.b contains different @language directives.''' # c = self languages, tag = set(), '@language' for s in g.splitLines(p.b): if g.match_word(s, 0, tag): i = g.skip_ws(s, len(tag)) j = g.skip_id(s, i) word = s[i: j] languages.add(word) return len(list(languages)) > 1
#@+node:ekr.20080922124033.5: *4* c.os_path_finalize and c.os_path_finalize_join
[docs] def os_path_finalize(self, path, **keys): c = self keys['c'] = c return g.os_path_finalize(path, **keys)
[docs] def os_path_finalize_join(self, *args, **keys): c = self keys['c'] = c return g.os_path_finalize_join(*args, **keys)
#@+node:ekr.20080827175609.39: *4* c.scanAllDirectives #@@nobeautify
[docs] def scanAllDirectives(self,p=None): ''' Scan p and ancestors for directives. Returns a dict containing the results, including defaults. ''' c = self p = p or c.p # Set defaults language = c.target_language and c.target_language.lower() lang_dict = { 'language':language, 'delims':g.set_delims_from_language(language), } wrap = c.config.getBool("body_pane_wraps") table = ( ('encoding', None, g.scanAtEncodingDirectives), ('lang-dict', lang_dict, g.scanAtCommentAndAtLanguageDirectives), ('lineending', None, g.scanAtLineendingDirectives), ('pagewidth', c.page_width, g.scanAtPagewidthDirectives), ('path', None, c.scanAtPathDirectives), ('tabwidth', c.tab_width, g.scanAtTabwidthDirectives), ('wrap', wrap, g.scanAtWrapDirectives), ) # Set d by scanning all directives. aList = g.get_directives_dict_list(p) d = {} for key,default,func in table: val = func(aList) d[key] = default if val is None else val # Post process: do *not* set commander ivars. lang_dict = d.get('lang-dict') d = { "delims": lang_dict.get('delims'), "encoding": d.get('encoding'), "language": lang_dict.get('language'), "lineending": d.get('lineending'), "pagewidth": d.get('pagewidth'), "path": d.get('path'), # Redundant: or g.getBaseDirectory(c), "tabwidth": d.get('tabwidth'), "pluginsList": [], # No longer used. "wrap": d.get('wrap'), } return d
#@+node:ekr.20080828103146.15: *4* c.scanAtPathDirectives
[docs] def scanAtPathDirectives(self, aList): '''Scan aList for @path directives. Return a reasonable default if no @path directive is found.''' c = self c.scanAtPathDirectivesCount += 1 # An important statistic. # Step 1: Compute the starting path. # The correct fallback directory is the absolute path to the base. if c.openDirectory: # Bug fix: 2008/9/18 base = c.openDirectory else: base = if base and base == "!": base = elif base and base == ".": base = c.openDirectory absbase = c.os_path_finalize_join(, base) # Step 2: look for @path directives. paths = [] for d in aList: # Look for @path directives. path = d.get('path') warning = d.get('@path_in_body') if path is not None: # retain empty paths for warnings. # Convert "path" or <path> to path. path = g.stripPathCruft(path) if path and not warning: paths.append(path) # We will silently ignore empty @path directives. # Add absbase and reverse the list. paths.append(absbase) paths.reverse() # Step 3: Compute the full, effective, absolute path. path = c.os_path_finalize_join(*paths) return path or g.getBaseDirectory(c)
# 2010/10/22: A useful default. #@+node:ekr.20080828103146.12: *4* c.scanAtRootDirectives (no longer used) # No longer used. Was called only by scanLanguageDirectives.
[docs] def scanAtRootDirectives(self, aList): '''Scan aList for @root-code and @root-doc directives.''' c = self # To keep pylint happy. tag = 'at_root_bodies_start_in_doc_mode' start_in_doc = hasattr(c.config, tag) and getattr(c.config, tag) # New in Leo 4.6: dashes are valid in directive names. for d in aList: if 'root-code' in d: return 'code' elif 'root-doc' in d: return 'doc' elif 'root' in d: return 'doc' if start_in_doc else 'code' return None
#@+node:ekr.20171123201514.1: *3* c.Executing commands & scripts #@+node:ekr.20110605040658.17005: *4* c.check_event
[docs] def check_event(self, event): '''Check an event object.''' # c = self import leo.core.leoGui as leoGui if not event: return stroke = event.stroke got = event.char if g.unitTesting: return if stroke and (stroke.find('Alt+') > -1 or stroke.find('Ctrl+') > -1): expected = event.char # Alas, Alt and Ctrl bindings must *retain* the char field, # so there is no way to know what char field to expect. else: expected = event.char # disable the test. # We will use the (weird) key value for, say, Ctrl-s, # if there is no binding for Ctrl-s. if not isinstance(event, leoGui.LeoKeyEvent): g.trace('not leo event: %r, callers: %s' % (event, g.callers())) if expected != got: g.trace('stroke: %r, expected char: %r, got: %r' % ( stroke, expected, got))
#@+node:ekr.20031218072017.2817: *4* c.doCommand command_count = 0
[docs] def doCommand(self, command, label, event=None): """ Execute the given command, invoking hooks and catching exceptions. The code assumes that the "command1" hook has completely handled the command if g.doHook("command1") returns False. This provides a simple mechanism for overriding commands. """ c, p = self, self.p c.setLog() self.command_count += 1 # The presence of this message disables all commands. if c.disableCommandsMessage: return if c.exists and c.inCommand and not g.unitTesting: = True g.error('ignoring command: already executing a command.') return = False if label and event is None: # Do this only for legacy commands. if label == "cantredo": label = "redo" if label == "cantundo": label = "undo" = label if not g.doHook("command1", c=c, p=p, label=label): try: c.inCommand = True val = c.executeAnyCommand(command, event) if c and c.exists: # Be careful: the command could destroy c. c.inCommand = False c.k.funcReturn = val except Exception: c.inCommand = False if raise else: g.es_print("exception executing command") g.es_exception(c=c) if c and c.exists: if c.requestCloseWindow: c.requestCloseWindow = False else: c.outerUpdate() # Be careful: the command could destroy c. if c and c.exists: p = c.p g.doHook("command2", c=c, p=p, label=label)
#@+node:ekr.20171124074112.1: *4* c.executeAnyCommand
[docs] def executeAnyCommand(self, command, event): ''' Execute a command, no matter how defined. Supports @g.commander_command and @g.new_cmd_decorator and plain methods. ''' try: return command(event) except Exception: g.es_exception() return None
#@+node:ekr.20051106040126: *4* c.executeMinibufferCommand
[docs] def executeMinibufferCommand(self, commandName): c = self; k = c.k func = c.commandsDict.get(commandName) if func: event = k.masterCommand(commandName=None, event=event, func=func) return k.funcReturn else: g.error('no such command: %s %s' % (commandName, g.callers())) return None
#@+node:ekr.20131016084446.16724: *4* c.setComplexCommand
[docs] def setComplexCommand(self, commandName): '''Make commandName the command to be executed by repeat-complex-command.''' c = self c.k.mb_history.insert(0, commandName)
#@+node:bobjack.20080509080123.2: *4* c.universalCallback & minibufferCallback
[docs] def universalCallback(self, source_c, function): """Create a universal command callback. Create and return a callback that wraps a function with an rClick signature in a callback which adapts standard minibufer command callbacks to a compatible format. This also serves to allow rClick callback functions to handle minibuffer commands from sources other than rClick menus so allowing a single function to handle calls from all sources. A function wrapped in this wrapper can handle rclick generator and invocation commands and commands typed in the minibuffer. It will also be able to handle commands from the minibuffer even if rclick is not installed. """ def minibufferCallback(event, function=function): # Avoid a pylint complaint. if hasattr(self, 'theContextMenuController'): cm = getattr(self, 'theContextMenuController') keywords = cm.mb_keywords else: cm = keywords = None if not keywords: # If rClick is not loaded or no keywords dict was provided # then the command must have been issued in a minibuffer # context. keywords = {'c': self, 'rc_phase': 'minibuffer'} keywords['mb_event'] = event retval = None try: retval = function(keywords) finally: if cm: # Even if there is an error: # clear mb_keywords prior to next command and # ensure mb_retval from last command is wiped cm.mb_keywords = None cm.mb_retval = retval minibufferCallback.__doc__ = function.__doc__ # For g.getDocStringForFunction minibufferCallback.source_c = source_c # For GetArgs.command_source return minibufferCallback
#fix bobjack's spelling error universallCallback = universalCallback #@+node:ekr.20070115135502: *4* c.writeScriptFile
[docs] def writeScriptFile(self, script): # Get the path to the file. c = self path = c.config.getString('script_file_path') if path: isAbsPath = os.path.isabs(path) driveSpec, path = os.path.splitdrive(path) parts = path.split('/') # xxx bad idea, loadDir is often read only! path = if isAbsPath: # make the first element absolute parts[0] = driveSpec + os.sep + parts[0] allParts = [path] + parts path = c.os_path_finalize_join(*allParts) else: path = c.os_path_finalize_join(, '') # Write the file. try: if g.isPython3: # Use the default encoding. f = open(path, encoding='utf-8', mode='w') else: f = open(path, 'w') s = script if not g.isPython3: # 2010/08/27 s = g.toEncodedString(s, reportErrors=True) f.write(s) f.close() except Exception: g.es_exception()"Failed to write script to %s" % path) #"Check your configuration of script_file_path, currently %s" % # c.config.getString('script_file_path')) path = None return path
#@+node:ekr.20171124101444.1: *3* c.File #@+node:ekr.20150422080541.1: *4* c.backup
[docs] def backup(self, fileName=None, prefix=None, silent=False, useTimeStamp=True): ''' Back up given fileName or c.fileName(). If useTimeStamp is True, append a timestamp to the filename. ''' c = self fn = fileName or c.fileName() if not fn: return theDir, base = g.os_path_split(fn) if useTimeStamp: if base.endswith('.leo'): base = base[: -4] stamp = time.strftime("%Y%m%d-%H%M%S") branch = prefix + '-' if prefix else '' fn = '%s%s-%s.leo' % (branch, base, stamp) path = g.os_path_finalize_join(theDir, fn) else: path = fn if path: # pylint: disable=no-member # Defined in c.saveTo(fileName=path, silent=silent) # Issues saved message. #'in', theDir) return path
#@+node:ekr.20180210092235.1: *4* c.backup_helper
[docs] def backup_helper(self, base_dir=None, env_key='LEO_BACKUP', sub_dir=None, use_git_prefix=True, ): ''' A helper for scripts that back up a .leo file. Use os.environ[env_key] as the base_dir only if base_dir is not given. Backup to base_dir or join(base_dir, sub_dir). ''' c = self join = g.os_path_finalize_join if not base_dir: if env_key: try: base_dir = os.environ[env_key] except KeyError: print('No environment var: %s' % env_key) base_dir = None if base_dir and g.os_path_exists(base_dir): if use_git_prefix: git_branch, junk = g.gitInfo() else: git_branch = None theDir, fn = g.os_path_split(c.fileName()) backup_dir = join(base_dir, sub_dir) if sub_dir else base_dir path = join(backup_dir, fn) if g.os_path_exists(backup_dir): written_fn = c.backup(path, prefix=git_branch, silent=True, useTimeStamp=True) g.es_print('wrote: %s' % written_fn) else: g.es_print('backup_dir not found: %r' % backup_dir) else: g.es_print('base_dir not found: %r' % base_dir)
#@+node:ekr.20090103070824.11: *4* c.checkFileTimeStamp
[docs] def checkFileTimeStamp(self, fn): ''' Return True if the file given by fn has not been changed since Leo read it or if the user agrees to overwrite it. ''' c = self if return, fn) else: return True
#@+node:ekr.20090212054250.9: *4* c.createNodeFromExternalFile
[docs] def createNodeFromExternalFile(self, fn): ''' Read the file into a node. Return None, indicating that should set focus. ''' c = self s, e = g.readFileIntoString(fn) if s is None: return head, ext = g.os_path_splitext(fn) if ext.startswith('.'): ext = ext[1:] language = if language: prefix = '@color\n@language %s\n\n' % language else: prefix = '@killcolor\n\n' # pylint: disable=no-member # Defined in p2 = c.insertHeadline(op_name='Open File', as_child=False) p2.h = '@edit %s' % fn # g.shortFileName(fn) p2.b = prefix + s w = c.frame.body.wrapper if w: w.setInsertPoint(0) c.redraw() c.recolor()
#@+node:ekr.20110530124245.18248: *4* c.looksLikeDerivedFile
[docs] def looksLikeDerivedFile(self, fn): '''Return True if fn names a file that looks like an external file written by Leo.''' # c = self try: with open(fn, 'r') as f: s = return s.find('@+leo-ver=') > -1 except Exception: return False
#@+node:ekr.20031218072017.2925: *4* c.markAllAtFileNodesDirty
[docs] def markAllAtFileNodesDirty(self, event=None): '''Mark all @file nodes as changed.''' c = self; p = c.rootPosition() c.endEditing() while p: if p.isAtFileNode() and not p.isDirty(): p.setDirty() c.setChanged(True) p.moveToNodeAfterTree() else: p.moveToThreadNext() c.redraw_after_icons_changed()
#@+node:ekr.20031218072017.2926: *4* c.markAtFileNodesDirty
[docs] def markAtFileNodesDirty(self, event=None): '''Mark all @file nodes in the selected tree as changed.''' c = self p = c.p if not p: return c.endEditing() after = p.nodeAfterTree() while p and p != after: if p.isAtFileNode() and not p.isDirty(): p.setDirty() c.setChanged(True) p.moveToNodeAfterTree() else: p.moveToThreadNext() c.redraw_after_icons_changed()
#@+node:ekr.20031218072017.2823: *4* c.openWith
[docs] def openWith(self, event=None, d=None): ''' This is *not* a command. Handles the items in the Open With... menu. See ExternalFilesController.open_with for details about d. ''' c = self if d and # Select an ancestor @<file> node if possible. if not d.get('p'): p = c.p while p: if p.isAnyAtFileNode(): d ['p'] = p break p.moveToParent(), d) elif not d: g.trace('can not happen: no d', g.callers())
#@+node:ekr.20140717074441.17770: *4* c.recreateGnxDict
[docs] def recreateGnxDict(self): '''Recreate the gnx dict prior to refreshing nodes from disk.''' c, d = self, {} for v in c.all_unique_nodes(): gnxString = v.fileIndex assert g.isUnicode(gnxString) d[gnxString] = v if 'gnx' in g.trace(c.shortFileName(), gnxString, v) c.fileCommands.gnxDict = d
#@+node:ekr.20180508111544.1: *3* c.Git #@+node:ekr.20180510104805.1: *4* c.diff_file (new)
[docs] def diff_file(self, fn, rev1='HEAD', rev2='', directory=None): ''' Create an outline describing the git diffs for all files changed between rev1 and rev2. ''' import leo.commands.editFileCommands as efc efc.GitDiffController(c=self).diff_file( directory=directory, fn=fn, rev1=rev1, rev2=rev2, )
#@+node:ekr.20180508110755.1: *4* c.diff_two_revs
[docs] def diff_two_revs(self, directory=None, rev1='', rev2=''): ''' Create an outline describing the git diffs for all files changed between rev1 and rev2. ''' import leo.commands.editFileCommands as efc efc.GitDiffController(c=self).diff_two_revs( directory=directory, rev1=rev1, rev2=rev2, )
#@+node:ekr.20180510103923.1: *4* c.diff_two_branches (new)
[docs] def diff_two_branches(self, branch1, branch2, fn, directory=None): ''' Create an outline describing the git diffs for all files changed between rev1 and rev2. ''' import leo.commands.editFileCommands as efc efc.GitDiffController(c=self).diff_two_branches( branch1=branch1, branch2=branch2, directory=directory, fn=fn, )
#@+node:ekr.20180510105125.1: *4* c.git_diff (new)
[docs] def git_diff(self, rev1='HEAD', rev2='', directory=None): import leo.commands.editFileCommands as efc efc.GitDiffController(c=self).git_diff( directory=directory, rev1=rev1, rev2=rev2, )
#@+node:ekr.20171124100534.1: *3* c.Gui #@+node:ekr.20111217154130.10286: *4* c.Dialogs & messages #@+node:ekr.20110510052422.14618: *5* c.alert
[docs] def alert(self, message): c = self # The unit tests just tests the args. if not g.unitTesting:, message)
#@+node:ekr.20111217154130.10284: *5* c.init_error_dialogs
[docs] def init_error_dialogs(self): c = self c.import_error_nodes = [] c.ignored_at_file_nodes = [] if g.unitTesting: d = tag = 'init_error_dialogs' d[tag] = 1 + d.get(tag, 0)
#@+node:ekr.20171123135805.1: *5* c.notValidInBatchMode
[docs] def notValidInBatchMode(self, commandName):'the', commandName, "command is not valid in batch mode")
#@+node:ekr.20110530082209.18250: *5* c.putHelpFor
[docs] def putHelpFor(self, s, short_title=''): '''Helper for various help commands.''' c = self, s, short_title)
#@+node:ekr.20111217154130.10285: *5* c.raise_error_dialogs
[docs] def raise_error_dialogs(self, kind='read'): '''Warn abouit read/write failures.''' c = self use_dialogs = True if g.unitTesting: d = tag = 'raise_error_dialogs' d[tag] = 1 + d.get(tag, 0) # This trace catches all too-many-calls failures. # g.trace(g.callers()) else: # Issue one or two dialogs or messages. if c.import_error_nodes or c.ignored_at_file_nodes: if c.import_error_nodes: files = '\n'.join(sorted(set(c.import_error_nodes))) if use_dialogs:, title='Import errors', message='The following were not imported properly. ' '@ignore was inserted:\n%s' % (files)) else:'import errors...', color='red')'\n'.join(sorted(files)), color='blue') if c.ignored_at_file_nodes: files = '\n'.join(sorted(set(c.ignored_at_file_nodes))) kind = 'read' if kind.startswith('read') else 'written' if use_dialogs:, title='Not read', message='The following were not %s because they contain @ignore:\n%s' % ( kind, files)) else:'not %s (@ignore)...' % (kind), color='red'), color='blue') c.init_error_dialogs()
#@+node:ekr.20150710083827.1: *5* c.syntaxErrorDialog
[docs] def syntaxErrorDialog(self): '''Warn about syntax errors in files.''' c = self if and c.config.getBool('syntax-error-popup', default=False): aList = sorted(set( = [] message = 'Python errors in:\n\n%s' % '\n'.join(aList), title='Python Errors', message=message, text="Ok")
#@+node:ekr.20031218072017.2945: *4* c.Dragging #@+node:ekr.20031218072017.2353: *5* c.dragAfter
[docs] def dragAfter(self, p, after): c = self; u = self.undoer; undoType = 'Drag' current = c.p inAtIgnoreRange = p.inAtIgnoreRange() if not c.checkDrag(p, after): return if not c.checkMoveWithParentWithWarning(p, after.parent(), True): return c.endEditing() undoData = u.beforeMoveNode(current) dirtyVnodeList = p.setAllAncestorAtFileNodesDirty() p.moveAfter(after) if inAtIgnoreRange and not p.inAtIgnoreRange(): # The moved nodes have just become newly unignored. dirtyVnodeList2 = p.setDirty() # Mark descendent @thin nodes dirty. dirtyVnodeList.extend(dirtyVnodeList2) else: # No need to mark descendents dirty. dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty() dirtyVnodeList.extend(dirtyVnodeList2) c.setChanged(True) u.afterMoveNode(p, undoType, undoData, dirtyVnodeList=dirtyVnodeList) c.redraw(p) c.updateSyntaxColorer(p) # Dragging can change syntax coloring.
#@+node:ekr.20031218072017.2947: *5* c.dragToNthChildOf
[docs] def dragToNthChildOf(self, p, parent, n): c = self; u = c.undoer; undoType = 'Drag' current = c.p inAtIgnoreRange = p.inAtIgnoreRange() if not c.checkDrag(p, parent): return if not c.checkMoveWithParentWithWarning(p, parent, True): return c.endEditing() undoData = u.beforeMoveNode(current) dirtyVnodeList = p.setAllAncestorAtFileNodesDirty() p.moveToNthChildOf(parent, n) if inAtIgnoreRange and not p.inAtIgnoreRange(): # The moved nodes have just become newly unignored. dirtyVnodeList2 = p.setDirty() # Mark descendent @thin nodes dirty. dirtyVnodeList.extend(dirtyVnodeList2) else: # No need to mark descendents dirty. dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty() dirtyVnodeList.extend(dirtyVnodeList2) c.setChanged(True) u.afterMoveNode(p, undoType, undoData, dirtyVnodeList=dirtyVnodeList) c.redraw(p) c.updateSyntaxColorer(p) # Dragging can change syntax coloring.
#@+node:ekr.20031218072017.2946: *5* c.dragCloneToNthChildOf
[docs] def dragCloneToNthChildOf(self, p, parent, n): c = self; u = c.undoer; undoType = 'Clone Drag' current = c.p inAtIgnoreRange = p.inAtIgnoreRange() clone = p.clone() # Creates clone & dependents, does not set undo. if ( not c.checkDrag(p, parent) or not c.checkMoveWithParentWithWarning(clone, parent, True) ): clone.doDelete(newNode=p) # Destroys clone and makes p the current node. c.selectPosition(p) # Also sets root position. return c.endEditing() undoData = u.beforeInsertNode(current) dirtyVnodeList = clone.setAllAncestorAtFileNodesDirty() clone.moveToNthChildOf(parent, n) if inAtIgnoreRange and not p.inAtIgnoreRange(): # The moved nodes have just become newly unignored. dirtyVnodeList2 = p.setDirty() # Mark descendent @thin nodes dirty. dirtyVnodeList.extend(dirtyVnodeList2) else: # No need to mark descendents dirty. dirtyVnodeList2 = p.setAllAncestorAtFileNodesDirty() dirtyVnodeList.extend(dirtyVnodeList2) c.setChanged(True) u.afterInsertNode(clone, undoType, undoData, dirtyVnodeList=dirtyVnodeList) c.redraw(clone) c.updateSyntaxColorer(clone) # Dragging can change syntax coloring.
#@+node:ekr.20031218072017.2948: *5* c.dragCloneAfter
[docs] def dragCloneAfter(self, p, after): c = self; u = c.undoer; undoType = 'Clone Drag' current = c.p clone = p.clone() # Creates clone. Does not set undo. if c.checkDrag(p, after) and c.checkMoveWithParentWithWarning(clone, after.parent(), True): inAtIgnoreRange = clone.inAtIgnoreRange() c.endEditing() undoData = u.beforeInsertNode(current) dirtyVnodeList = clone.setAllAncestorAtFileNodesDirty() clone.moveAfter(after) if inAtIgnoreRange and not clone.inAtIgnoreRange(): # The moved node have just become newly unignored. dirtyVnodeList2 = clone.setDirty() # Mark descendent @thin nodes dirty. dirtyVnodeList.extend(dirtyVnodeList2) else: # No need to mark descendents dirty. dirtyVnodeList2 = clone.setAllAncestorAtFileNodesDirty() dirtyVnodeList.extend(dirtyVnodeList2) c.setChanged(True) u.afterInsertNode(clone, undoType, undoData, dirtyVnodeList=dirtyVnodeList) p = clone else: clone.doDelete(newNode=p) c.redraw(p) c.updateSyntaxColorer(clone) # Dragging can change syntax coloring.
#@+node:ekr.20031218072017.2949: *4* c.Drawing #@+node:ekr.20080514131122.7: *5* c.begin/endUpdate
[docs] def beginUpdate(self): '''Deprecated: does nothing.''' g.trace('***** c.beginUpdate is deprecated', g.callers()) if assert(False)
[docs] def endUpdate(self, flag=True): '''Request a redraw of the screen if flag is True.''' g.trace('***** c.endUpdate is deprecated', g.callers()) if assert(False) c = self if flag: c.requestRedrawFlag = True
BeginUpdate = beginUpdate # Compatibility with old scripts EndUpdate = endUpdate # Compatibility with old scripts #@+node:ekr.20080514131122.8: *5* c.bringToFront
[docs] def bringToFront(self, c2=None, set_focus=True): c = self c2 = c2 or c
BringToFront = bringToFront # Compatibility with old scripts #@+node:ekr.20040803072955.143: *5* c.expandAllAncestors
[docs] def expandAllAncestors(self, p): ''' Expand all ancestors without redrawing. Return a flag telling whether a redraw is needed. ''' # c = self redraw_flag = False for p in p.parents(): if not p.v.isExpanded(): p.v.expand() p.expand() redraw_flag = True elif p.isExpanded(): p.v.expand() else: p.expand() redraw_flag = True return redraw_flag
#@+node:ekr.20080514131122.20: *5* c.outerUpdate
[docs] def outerUpdate(self): '''Handle delayed focus requests and modified events.''' c = self if not c.exists or not c.k: return # New in Leo 5.6: Delayed redraws are useful in utility methods. if c.requestLaterRedraw: if c.enableRedrawFlag: c.requestLaterRedraw = False c.redraw() # Delayed focus requests will always be useful. if c.requestedFocusWidget: w = c.requestedFocusWidget c.set_focus(w) c.requestedFocusWidget = None table = ( ("childrenModified", g.childrenModifiedSet), ("contentModified", g.contentModifiedSet), ) for kind, mods in table: if mods: g.doHook(kind, c=c, nodes=mods) mods.clear()
#@+node:ekr.20080514131122.13: *5* c.recolor
[docs] def recolor(self, **kwargs): # Support QScintillaColorizer.colorize. c = self p = kwargs.get('p') for name in ('incremental', 'interruptable'): if name in kwargs: print('c.recolor_now: "%s" keyword arg is deprecated' % name) colorizer = c.frame.body.colorizer if colorizer and hasattr(colorizer, 'colorize'): colorizer.colorize(p or c.p)
recolor_now = recolor #@+node:ekr.20080514131122.14: *5* c.redrawing... #@+node:ekr.20170808014610.1: *6* c.enable/disable_redraw (New in Leo 5.6)
[docs] def disable_redraw(self): '''Disable all redrawing until enabled.''' c = self c.enableRedrawFlag = False
[docs] def enable_redraw(self): c = self c.enableRedrawFlag = True
#@+node:ekr.20090110073010.1: *6* c.redraw
[docs] def redraw(self, p=None, setFocus=False): '''Redraw the screen immediately.''' c = self # New in Leo 5.6: clear the redraw request. c.requestLaterRedraw = False if not p: p = c.p or c.rootPosition() if not p: return c.expandAllAncestors(p) if p: # Fix bug # This looks redundant, but it is probably the only safe fix. # tree.redraw will change the position if p is a hoisted @chapter node. p2 = c.frame.tree.redraw(p) # Be careful. NullTree.redraw returns None. # #503: NullTree.redraw(p) now returns p. c.selectPosition(p2 or p) if setFocus: c.treeFocusHelper() # New in Leo 5.6: clear the redraw request, again. c.requestLaterRedraw = False
# Compatibility with old scripts force_redraw = redraw redraw_now = redraw #@+node:ekr.20090110073010.3: *6* c.redraw_afer_icons_changed
[docs] def redraw_after_icons_changed(self): '''Update the icon for the presently selected node''' c = self if c.enableRedrawFlag: c.frame.tree.redraw_after_icons_changed() # c.treeFocusHelper() else: c.requestLaterRedraw = True
#@+node:ekr.20090110131802.2: *6* c.redraw_after_contract
[docs] def redraw_after_contract(self, p=None, setFocus=False): c = self c.endEditing() if c.enableRedrawFlag: if p: c.setCurrentPosition(p) else: p = c.currentPosition() if p.isCloned(): c.redraw(p=p, setFocus=setFocus) else: c.frame.tree.redraw_after_contract(p) if setFocus: c.treeFocusHelper() else: c.requestLaterRedraw = True
#@+node:ekr.20090112065525.1: *6* c.redraw_after_expand
[docs] def redraw_after_expand(self, p=None, setFocus=False): c = self c.endEditing() if c.enableRedrawFlag: if p: c.setCurrentPosition(p) else: p = c.currentPosition() if p.isCloned(): c.redraw(p=p, setFocus=setFocus) else: c.frame.tree.redraw_after_expand(p) if setFocus: c.treeFocusHelper() else: c.requestLaterRedraw = True
#@+node:ekr.20090110073010.2: *6* c.redraw_after_head_changed
[docs] def redraw_after_head_changed(self): ''' Redraw the screen (if needed) when editing ends. This may be a do-nothing for some gui's. ''' c = self if c.enableRedrawFlag: return self.frame.tree.redraw_after_head_changed() else: c.requestLaterRedraw = True
#@+node:ekr.20090110073010.4: *6* c.redraw_after_select
[docs] def redraw_after_select(self, p): '''Redraw the screen after node p has been selected.''' c = self if c.enableRedrawFlag: flag = c.expandAllAncestors(p) if flag: c.frame.tree.redraw_after_select(p) else: c.requestLaterRedraw = True
#@+node:ekr.20170908081918.1: *6* c.redraw_later
[docs] def redraw_later(self): ''' Ensure that c.redraw() will be called eventually. c.outerUpdate will call c.redraw() only if no other code calls c.redraw(). ''' c = self c.requestLaterRedraw = True
#@+node:ekr.20080514131122.17: *5* c.widget_name
[docs] def widget_name(self, widget): # c = self return if else '<no widget>'
#@+node:ekr.20171124101045.1: *4* c.Events #@+node:ekr.20060923202156: *5* c.onCanvasKey
[docs] def onCanvasKey(self, event): '''Navigate to the next headline starting with ch = event.char. If ch is uppercase, search all headlines; otherwise search only visible headlines. This is modelled on Windows explorer.''' if not event or not event.char or not event.char.isalnum(): return c = self; p = c.p; p1 = p.copy() invisible = c.config.getBool('invisible_outline_navigation') ch = event.char if event else '' allFlag = ch.isupper() and invisible # all is a global (!?) if not invisible: ch = ch.lower() found = False extend = self.navQuickKey() attempts = (True, False) if extend else (False,) for extend2 in attempts: p = p1.copy() while 1: if allFlag: p.moveToThreadNext() else: p.moveToVisNext(c) if not p: p = c.rootPosition() if p == p1: # Never try to match the same position. found = False; break newPrefix = c.navHelper(p, ch, extend2) if newPrefix: found = True; break if found: break if found: c.selectPosition(p) c.redraw_after_select(p) c.navTime = time.time() c.navPrefix = newPrefix else: c.navTime = None c.navPrefix = '' c.treeWantsFocus()
#@+node:ekr.20061002095711.1: *6* c.navQuickKey
[docs] def navQuickKey(self): '''return true if there are two quick outline navigation keys in quick succession. Returns False if @float outline_nav_extend_delay setting is 0.0 or unspecified.''' c = self deltaTime = c.config.getFloat('outline_nav_extend_delay') if deltaTime in (None, 0.0): return False else: nearTime = c.navTime and time.time() - c.navTime < deltaTime return nearTime
#@+node:ekr.20061002095711: *6* c.navHelper
[docs] def navHelper(self, p, ch, extend): c = self; h = p.h.lower() if extend: prefix = c.navPrefix + ch return h.startswith(prefix.lower()) and prefix if h.startswith(ch): return ch # New feature: search for first non-blank character after @x for common x. if ch != '@' and h.startswith('@'): for s in ('button', 'command', 'file', 'thin', 'asis', 'nosent',): prefix = '@' + s if h.startswith('@' + s): while 1: n = len(prefix) ch2 = h[n] if n < len(h) else '' if ch2.isspace(): prefix = prefix + ch2 else: break if len(prefix) < len(h) and h.startswith(prefix + ch.lower()): return prefix + ch return ''
#@+node:ekr.20031218072017.2909: *4* c.Expand/contract #@+node:ekr.20171124091426.1: *5* c.contractAllHeadlines
[docs] def contractAllHeadlines(self, event=None, redrawFlag=True): '''Contract all nodes in the outline.''' c = self for p in c.all_positions(): p.contract() # Select the topmost ancestor of the presently selected node. p = c.p while p and p.hasParent(): p.moveToParent() if redrawFlag: c.redraw(p, setFocus=True) c.expansionLevel = 1 # Reset expansion level.
#@+node:ekr.20031218072017.2910: *5* c.contractSubtree
[docs] def contractSubtree(self, p): for p in p.subtree(): p.contract()
#@+node:ekr.20031218072017.2911: *5* c.expandSubtree
[docs] def expandSubtree(self, v): c = self last = v.lastNode() while v and v != last: v.expand() v = v.threadNext() c.redraw()
#@+node:ekr.20031218072017.2912: *5* c.expandToLevel
[docs] def expandToLevel(self, level): c = self n = c.p.level() old_expansion_level = c.expansionLevel max_level = 0 for p in c.p.self_and_subtree(): if p.level() - n + 1 < level: p.expand() max_level = max(max_level, p.level() - n + 1) else: p.contract() c.expansionNode = c.p.copy() c.expansionLevel = max_level + 1 if c.expansionLevel != old_expansion_level: c.redraw() # It's always useful to announce the level. # c.k.setLabelBlue('level: %s' % (max_level+1)) #'level', max_level + 1) c.frame.putStatusLine('level: %s' % (max_level+1))
# bg='red', fg='red') #@+node:ekr.20141028061518.23: *4* c.Focus #@+node:ekr.20080514131122.9: *5* c.get/request/set_focus
[docs] def get_focus(self): c = self w = and if 'focus' in g.trace('(c)', repr(w and, w) g.callers() return w
[docs] def get_requested_focus(self): c = self return c.requestedFocusWidget
[docs] def request_focus(self, w): c = self if w and if 'focus' in g.trace('(c)', repr(, w) g.callers() c.requestedFocusWidget = w
[docs] def set_focus(self, w, force=False): trace = 'focus' in c = self if w and if trace: g.trace('(c)', repr(w and, w) g.callers(), w) else: if trace: g.trace('(c) no w') c.requestedFocusWidget = None
#@+node:ekr.20080514131122.10: *5* c.invalidateFocus
[docs] def invalidateFocus(self): '''Indicate that the focus is in an invalid location, or is unknown.''' # c = self # c.requestedFocusWidget = None pass
#@+node:ekr.20080514131122.16: *5* c.traceFocus (not used)
[docs] def traceFocus(self, w): c = self if 'focus' in c.trace_focus_count += 1'%4d' % (c.trace_focus_count), c.widget_name(w), g.callers(8))
#@+node:ekr.20070226121510: *5* c.xFocusHelper & initialFocusHelper
[docs] def treeFocusHelper(self): c = self if c.stayInTreeAfterSelect: c.treeWantsFocus() else: c.bodyWantsFocus()
[docs] def initialFocusHelper(self): c = self if c.outlineHasInitialFocus: c.treeWantsFocus() else: c.bodyWantsFocus()
#@+node:ekr.20080514131122.18: *5* c.xWantsFocus
[docs] def bodyWantsFocus(self): c = self; body = c.frame.body c.request_focus(body and body.wrapper)
[docs] def logWantsFocus(self): c = self; log = c.frame.log c.request_focus(log and log.logCtrl)
[docs] def minibufferWantsFocus(self): c = self c.request_focus(c.miniBufferWidget)
[docs] def treeWantsFocus(self): c = self; tree = c.frame.tree c.request_focus(tree and tree.canvas)
[docs] def widgetWantsFocus(self, w): c = self; c.request_focus(w)
#@+node:ekr.20080514131122.19: *5* c.xWantsFocusNow # widgetWantsFocusNow does an automatic update.
[docs] def widgetWantsFocusNow(self, w): c = self if w: c.set_focus(w) c.requestedFocusWidget = None
# New in 4.9: all FocusNow methods now *do* call c.outerUpdate().
[docs] def bodyWantsFocusNow(self): c = self; body = c.frame.body c.widgetWantsFocusNow(body and body.wrapper)
[docs] def logWantsFocusNow(self): c = self; log = c.frame.log c.widgetWantsFocusNow(log and log.logCtrl)
[docs] def minibufferWantsFocusNow(self): c = self c.widgetWantsFocusNow(c.miniBufferWidget)
[docs] def treeWantsFocusNow(self): c = self; tree = c.frame.tree c.widgetWantsFocusNow(tree and tree.canvas)
#@+node:ekr.20031218072017.2955: *4* c.Menus #@+node:ekr.20080610085158.2: *5* c.add_command
[docs] def add_command(self, menu, **keys): c = self command = keys.get('command') if command: # Command is always either: # one of two callbacks defined in createMenuEntries or # recentFilesCallback, defined in createRecentFilesMenuItems. def add_commandCallback(c=c, command=command): val = command() # Careful: func may destroy c. if c.exists: c.outerUpdate() return val keys['command'] = add_commandCallback menu.add_command(** keys) else: g.trace('can not happen: no "command" arg')
#@+node:ekr.20171123203044.1: *5* c.Menu Enablers #@+node:ekr.20040131170659: *6* c.canClone
[docs] def canClone(self): c = self if c.hoistStack: current = c.p bunch = c.hoistStack[-1] return current != bunch.p else: return True
#@+node:ekr.20031218072017.2956: *6* c.canContractAllHeadlines
[docs] def canContractAllHeadlines(self): '''Contract all nodes in the tree.''' c = self for p in c.all_positions(): # was c.all_unique_positions() if p.isExpanded(): return True return False
#@+node:ekr.20031218072017.2957: *6* c.canContractAllSubheads
[docs] def canContractAllSubheads(self): c = self; current = c.p for p in current.subtree(): if p != current and p.isExpanded(): return True return False
#@+node:ekr.20031218072017.2958: *6* c.canContractParent
[docs] def canContractParent(self): c = self return c.p.parent()
#@+node:ekr.20031218072017.2959: *6* c.canContractSubheads
[docs] def canContractSubheads(self): c = self; current = c.p for child in current.children(): if child.isExpanded(): return True return False
#@+node:ekr.20031218072017.2960: *6* c.canCutOutline & canDeleteHeadline
[docs] def canDeleteHeadline(self): c = self; p = c.p if c.hoistStack: bunch = c.hoistStack[0] if p == bunch.p: return False if False: # c.config.getBool('select-next-after-delete'): # #721: Optionally select next node after delete. return p.hasParent() or p.hasBack() else: return p.hasParent() or p.hasThreadBack() or p.hasNext()
canCutOutline = canDeleteHeadline #@+node:ekr.20031218072017.2961: *6* c.canDemote
[docs] def canDemote(self): c = self return c.p.hasNext()
#@+node:ekr.20031218072017.2962: *6* c.canExpandAllHeadlines
[docs] def canExpandAllHeadlines(self): '''Return True if the Expand All Nodes menu item should be enabled.''' c = self for p in c.all_positions(): # was c.all_unique_positions() if not p.isExpanded(): return True return False
#@+node:ekr.20031218072017.2963: *6* c.canExpandAllSubheads
[docs] def canExpandAllSubheads(self): c = self for p in c.p.subtree(): if not p.isExpanded(): return True return False
#@+node:ekr.20031218072017.2964: *6* c.canExpandSubheads
[docs] def canExpandSubheads(self): c = self; current = c.p for p in current.children(): if p != current and not p.isExpanded(): return True return False
#@+node:ekr.20031218072017.2287: *6* c.canExtract, canExtractSection & canExtractSectionNames
[docs] def canExtract(self): c = self w = c.frame.body.wrapper return w and w.hasSelection()
canExtractSectionNames = canExtract
[docs] def canExtractSection(self): c = self w = c.frame.body.wrapper if not w: return False s = w.getSelectedText() if not s: return False line = g.get_line(s, 0) i1 = line.find("<<") j1 = line.find(">>") i2 = line.find("@<") j2 = line.find("@>") return -1 < i1 < j1 or -1 < i2 < j2
#@+node:ekr.20031218072017.2965: *6* c.canFindMatchingBracket #@@nobeautify
[docs] def canFindMatchingBracket(self): c = self brackets = "()[]{}" w = c.frame.body.wrapper s = w.getAllText() ins = w.getInsertPoint() c1 = s[ins] if 0 <= ins < len(s) else '' c2 = s[ins-1] if 0 <= ins-1 < len(s) else '' val = (c1 and c1 in brackets) or (c2 and c2 in brackets) return bool(val)
#@+node:ekr.20040303165342: *6* c.canHoist & canDehoist
[docs] def canDehoist(self): ''' Return True if do-hoist should be enabled in a menu. Should not be used in any other context. ''' c = self return bool(c.hoistStack)
[docs] def canHoist(self): # This is called at idle time, so minimizing positions is crucial! ''' Return True if hoist should be enabled in a menu. Should not be used in any other context. ''' return True
# c = self # if c.hoistStack: # p = c.hoistStack[-1].p # return p and not c.isCurrentPosition(p) # elif c.currentPositionIsRootPosition(): # return c.currentPositionHasNext() # else: # return True #@+node:ekr.20031218072017.2970: *6* c.canMoveOutlineDown
[docs] def canMoveOutlineDown(self): c = self; current = c.p return current and current.visNext(c)
#@+node:ekr.20031218072017.2971: *6* c.canMoveOutlineLeft
[docs] def canMoveOutlineLeft(self): c = self; p = c.p if c.hoistStack: bunch = c.hoistStack[-1] if p and p.hasParent(): p.moveToParent() return p != bunch.p and bunch.p.isAncestorOf(p) else: return False else: return p and p.hasParent()
#@+node:ekr.20031218072017.2972: *6* c.canMoveOutlineRight
[docs] def canMoveOutlineRight(self): c = self; p = c.p if c.hoistStack: bunch = c.hoistStack[-1] return p and p.hasBack() and p != bunch.p else: return p and p.hasBack()
#@+node:ekr.20031218072017.2973: *6* c.canMoveOutlineUp
[docs] def canMoveOutlineUp(self): c = self; current = c.p visBack = current and current.visBack(c) if not visBack: return False elif visBack.visBack(c): return True elif c.hoistStack: limit, limitIsVisible = c.visLimit() if limitIsVisible: # A hoist return current != limit else: # A chapter. return current != limit.firstChild() else: return current != c.rootPosition()
#@+node:ekr.20031218072017.2974: *6* c.canPasteOutline
[docs] def canPasteOutline(self, s=None): # c = self if not s: s = if s: if g.match(s, 0, return True else: return False
#@+node:ekr.20031218072017.2975: *6* c.canPromote
[docs] def canPromote(self): c = self; v = c.currentVnode() return v and v.hasChildren()
#@+node:ekr.20031218072017.2977: *6* c.canSelect....
[docs] def canSelectThreadBack(self): c = self; p = c.p return p.hasThreadBack()
[docs] def canSelectThreadNext(self): c = self; p = c.p return p.hasThreadNext()
[docs] def canSelectVisBack(self): c = self; p = c.p return p.visBack(c)
[docs] def canSelectVisNext(self): c = self; p = c.p return p.visNext(c)
#@+node:ekr.20031218072017.2978: *6* c.canShiftBodyLeft/Right
[docs] def canShiftBodyLeft(self): c = self w = c.frame.body.wrapper return w and w.getAllText()
canShiftBodyRight = canShiftBodyLeft #@+node:ekr.20031218072017.2979: *6* c.canSortChildren, canSortSiblings
[docs] def canSortChildren(self): c = self; p = c.p return p and p.hasChildren()
[docs] def canSortSiblings(self): c = self; p = c.p return p and (p.hasNext() or p.hasBack())
#@+node:ekr.20031218072017.2980: *6* c.canUndo & canRedo
[docs] def canUndo(self): c = self return c.undoer.canUndo()
[docs] def canRedo(self): c = self return c.undoer.canRedo()
#@+node:ekr.20031218072017.2981: *6* c.canUnmarkAll
[docs] def canUnmarkAll(self): c = self for p in c.all_unique_positions(): if p.isMarked(): return True return False
#@+node:ekr.20040323172420: *6* Slow routines: no longer used #@+node:ekr.20031218072017.2966: *7* c.canGoToNextDirtyHeadline (slow)
[docs] def canGoToNextDirtyHeadline(self): c = self; current = c.p for p in c.all_unique_positions(): if p != current and p.isDirty(): return True return False
#@+node:ekr.20031218072017.2967: *7* c.canGoToNextMarkedHeadline (slow)
[docs] def canGoToNextMarkedHeadline(self): c = self; current = c.p for p in c.all_unique_positions(): if p != current and p.isMarked(): return True return False
#@+node:ekr.20031218072017.2968: *7* c.canMarkChangedHeadline (slow)
[docs] def canMarkChangedHeadlines(self): c = self for p in c.all_unique_positions(): if p.isDirty(): return True return False
#@+node:ekr.20031218072017.2969: *7* c.canMarkChangedRoots (slow)
[docs] def canMarkChangedRoots(self): c = self for p in c.all_unique_positions(): if p.isDirty and p.isAnyAtFileNode(): return True return False
#@+node:ekr.20031218072017.2990: *4* c.Selecting #@+node:ekr.20031218072017.2992: *5* c.endEditing (calls tree.endEditLabel) # Ends the editing in the outline.
[docs] def endEditing(self): c = self p = c.p if p: c.frame.tree.endEditLabel()
# The following code would be wrong; c.endEditing is a utility method. # k = c.k # if k: # k.setDefaultInputState() # # Recolor the *body* text, **not** the headline. # k.showStateAndMode(w=c.frame.body.wrapper) #@+node:ville.20090525205736.12325: *5* c.getSelectedPositions
[docs] def getSelectedPositions(self): """ Get list (PosList) of currently selected positions So far only makes sense on qt gui (which supports multiselection) """ c = self return c.frame.tree.getSelectedPositions()
#@+node:ekr.20031218072017.2991: *5* c.redrawAndEdit
[docs] def redrawAndEdit(self, p, selectAll=False, selection=None, keepMinibuffer=False): '''Redraw the screen and edit p's headline.''' c, k = self, self.k c.redraw(p) # This *must* be done now. if p: # This should request focus. c.frame.tree.editLabel(p, selectAll=selectAll, selection=selection) if k and not keepMinibuffer: # Setting the input state has no effect on focus. if selectAll: k.setInputState('insert') else: k.setDefaultInputState() # This *does* affect focus. k.showStateAndMode() else: g.trace('** no p') # Update the focus immediately. if not keepMinibuffer: c.outerUpdate()
#@+node:ekr.20031218072017.2997: *5* c.selectPosition
[docs] def selectPosition(self, p, **kwargs): ''' Select a new position, redrawing the screen *only* if we must change chapters. ''' if kwargs: print('c.selectPosition: all keyword args are ignored', g.callers()) c = self cc = c.chapterController if not p: if not # A serious error. g.trace('Warning: no p', g.callers()) return if cc and not cc.selectChapterLockout: cc.selectChapterForPosition(p) # Calls c.redraw only if the chapter changes. # De-hoist as necessary to make p visible. if c.hoistStack: while c.hoistStack: bunch = c.hoistStack[-1] if c.positionExists(p, bunch.p): break else: bunch = c.hoistStack.pop() c.setCurrentPosition(p)
# Do *not* test whether the position exists! # We may be in the midst of an undo. # Compatibility, but confusing. selectVnode = selectPosition #@+node:ekr.20080503055349.1: *5* c.setPositionAfterSort
[docs] def setPositionAfterSort(self, sortChildren): c = self p = c.p p_v = p.v parent = p.parent() parent_v = p._parentVnode() if sortChildren: p = parent or c.rootPosition() else: if parent: p = parent.firstChild() else: p = leoNodes.Position(parent_v.children[0]) while p and p.v != p_v: p.moveToNext() p = p or parent return p
#@+node:ekr.20070226113916: *5* c.treeSelectHelper
[docs] def treeSelectHelper(self, p): c = self if not p: p = c.p if p: # Do not call expandAllAncestors here. c.selectPosition(p) c.redraw_after_select(p) c.treeFocusHelper()
#@+node:ekr.20171123135625.51: *4* c.updateBodyPane (handles changeNodeContents)
[docs] def updateBodyPane(self, head, middle, tail, undoType, oldSel, oldYview, preserveSel=False): '''Handle changed text in the body pane.''' c, p = self, self.p body = c.frame.body # Update the text and notify the event handler. body.setSelectionAreas(head, middle, tail) # Expand the selection. head = head or '' middle = middle or '' tail = tail or '' if preserveSel: # Leo 5.6: just use the computed oldSel. i, j = oldSel else: i = len(head) j = max(i, len(head) + len(middle) - 1) newSel = i, j body.wrapper.setSelectionRange(i, j) # This handles the undo. body.onBodyChanged(undoType, oldSel=oldSel or newSel, oldYview=oldYview) # Update the changed mark and icon. c.setChanged(True) if p.isDirty(): dirtyVnodeList = [] else: dirtyVnodeList = p.setDirty() c.redraw_after_icons_changed() # Scroll as necessary. if oldYview: body.wrapper.setYScrollPosition(oldYview) else: body.wrapper.seeInsertPoint() body.wrapper.setFocus() c.recolor() return dirtyVnodeList
#@+node:ekr.20130823083943.12559: *3* c.recursiveImport
[docs] def recursiveImport(self, dir_, kind, add_path=True, recursive=True, safe_at_file=True, theTypes=None, # force_at_others=False, # tag:no-longer-used ): #@+<< docstring >> #@+node:ekr.20130823083943.12614: *4* << docstring >> ''' Recursively import all python files in a directory and clean the results. Parameters:: dir_ The root directory or file to import. kind One of ('@clean','@edit','@file','@nosent'). add_path=True True: add a full @path directive to @<file> nodes. recursive=True True: recurse into subdirectories. safe_at_file=True True: produce @@file nodes instead of @file nodes. theTypes=None A list of file extensions to import. None is equivalent to ['.py'] This method cleans imported files as follows: - Replace backslashes with forward slashes in headlines. - Remove empty nodes. - Add @path directives that reduce the needed path specifiers in descendant nodes. - Add @file to nodes or replace @file with @@file. ''' #@-<< docstring >> c = self if g.os_path_exists(dir_): # Import all files in dir_ after c.p. try: import leo.core.leoImport as leoImport cc = leoImport.RecursiveImportController(c, kind, add_path=add_path, recursive=recursive, safe_at_file=safe_at_file, theTypes=['.py'] if not theTypes else theTypes, # force_at_others = force_at_others, # tag:no-longer-used ) finally: c.redraw() else: g.es_print('Does not exist: %s' % (dir_))
#@+node:ekr.20171124084149.1: *3* c.Scripting utils #@+node:ekr.20160201072634.1: *4* c.cloneFindByPredicate
[docs] def cloneFindByPredicate(self, generator, # The generator used to traverse the tree. predicate, # A function of one argument p, returning True # if p should be included in the results. failMsg=None, # Failure message. Default is no message. flatten=False, # True: Put all matches at the top level. iconPath=None, # Full path to icon to attach to all matches. redraw=True, # True: redraw the outline, undoType=None, # The undo name, shown in the Edit:Undo menu. # The default is 'clone-find-predicate' ): ''' Traverse the tree given using the generator, cloning all positions for which predicate(p) is True. Undoably move all clones to a new node, created as the last top-level node. Returns the newly-created node. Arguments: generator, The generator used to traverse the tree. predicate, A function of one argument p returning true if p should be included. failMsg=None, Message given if nothing found. Default is no message. flatten=False, True: Move all node to be parents of the root node. iconPath=None, Full path to icon to attach to all matches. redraw=True, True: redraw the screen. undo_type=None, The undo/redo name shown in the Edit:Undo menu. The default is 'clone-find-predicate' ''' c = self u, undoType = c.undoer, undoType or 'clone-find-predicate' clones, root, seen = [], None, set() for p in generator(): if predicate(p) and p.v not in seen: c.setCloneFindByPredicateIcon(iconPath, p) if flatten: seen.add(p.v) else: for p2 in p.self_and_subtree(): seen.add(p2.v) clones.append(p.copy()) if clones: undoData = u.beforeInsertNode(c.p) root = c.createCloneFindPredicateRoot(flatten, undoType) for p in clones: clone = p.clone() clone.moveToLastChildOf(root) u.afterInsertNode(root, undoType, undoData, dirtyVnodeList=[]) if redraw: c.selectPosition(root) c.setChanged(True) c.contractAllHeadlines() root.expand() c.redraw() c.selectPosition(root) elif failMsg: g.es_print(failMsg, color='red') return root
#@+node:ekr.20160304054950.1: *5* c.setCloneFindByPredicateIcon
[docs] def setCloneFindByPredicateIcon(self, iconPath, p): '''Attach an icon to p.v.u.''' if iconPath and g.os_path_exists(iconPath) and not g.os_path_isdir(iconPath): aList = p.v.u.get('icons', []) for d in aList: if d.get('file') == iconPath: break else: aList.append({ 'type': 'file', 'file': iconPath, 'on': 'VNode', # 'relPath': iconPath, 'where': 'beforeHeadline', 'xoffset': 2, 'xpad': 1, 'yoffset': 0, }) p.v.u ['icons'] = aList elif iconPath: g.trace('bad icon path', iconPath)
#@+node:ekr.20160201075438.1: *5* c.createCloneFindPredicateRoot
[docs] def createCloneFindPredicateRoot(self, flatten, undoType): '''Create a root node for clone-find-predicate.''' c = self root = c.lastTopLevel().insertAfter() root.h = undoType + (' (flattened)' if flatten else '') return root
#@+node:peckj.20131023115434.10114: *4* c.createNodeHierarchy
[docs] def createNodeHierarchy(self, heads, parent=None, forcecreate=False): ''' Create the proper hierarchy of nodes with headlines defined in 'heads' under 'parent' params: parent - parent node to start from. Set to None for top-level nodes heads - list of headlines in order to create, i.e. ['foo','bar','baz'] will create: parent -foo --bar ---baz forcecreate - If False (default), will not create nodes unless they don't exist If True, will create nodes regardless of existing nodes returns the final position ('baz' in the above example) ''' u = self.undoer undoType = 'Create Node Hierarchy' undoType2 = 'Insert Node In Hierarchy' u_node = parent or self.rootPosition() undoData = u.beforeChangeGroup(u_node, undoType) changed_node = False for idx, head in enumerate(heads): if parent is None and idx == 0: # if parent = None, create top level node for first head if not forcecreate: for pos in self.all_positions(): if pos.h == head: parent = pos break if parent is None or forcecreate: u_d = u.beforeInsertNode(u_node) n = self.rootPosition().insertAfter() n.h = head u.afterInsertNode(n, undoType2, u_d) parent = n else: # else, simply create child nodes each round if not forcecreate: for ch in parent.children(): if ch.h == head: parent = ch changed_node = True break if parent.h != head or not changed_node or forcecreate: u_d = u.beforeInsertNode(parent) n = parent.insertAsLastChild() n.h = head u.afterInsertNode(n, undoType2, u_d) parent = n changed_node = False u.afterChangeGroup(parent, undoType, undoData) return parent # actually the last created/found position
#@+node:ekr.20100802121531.5804: *4* c.deletePositionsInList
[docs] def deletePositionsInList(self, aList, callback=None, redraw=True): ''' Delete all vnodes corresponding to the positions in aList. If a callback is given, the callback is called for every node in the list. The callback takes one explicit argument, p. As usual, the callback can bind values using keyword arguments. This is *very* tricky code. The theory of operation section explains why. ''' #@+<< theory of operation >> #@+node:ekr.20150312080344.8: *5* << theory of operation >> (deletePositionsInList) #@+at #@@language rest #@@wrap # The Aha: the positions passed to p.deletePositionsInList only # *specify* the desired changes; the only way to *make* those changes is # to operate on vnodes! # # Consider this outline, containing no clones:: # # + ROOT # - A # - B # # The fundamental problem is this. If we delete node A, the index of # node B in ROOT.children will change. This problem has (almost) nothing # to do with clones or positions. # # To make this concrete, let's look at the *vnodes* that represent this # tree. It is the vnodes, and *not* the positions, that represent all of # Leo's data. Let ROOT, A and B be the vnodes corresponding to the nodes # ROOT, A and B. ROOT.children will look like this at first:: # # ROOT.children = [A,B] # # That is, the children array contains references (links) to both A and # B. After deleting A, we will have:: # # ROOT.children = [B] # # As you can see, the reference to B is at index 1 of ROOT.children # before deleting A, and at index 0 of ROOT.children after deleting A. # Thus, *any* position referring to B will become invalid after deleting # A. # # Several people, including myself, have proposed an unsound # solution--just delete positions in reverse order, so that B will be # deleted before A. This idea has appeal, but it is wrong. Here is an # outline that shows that there is *no* correct order for deleting # positions. All A' nodes are clones of each other:: # # + ROOT # + A' # - B # at position p1 # + A' # - B # at position p2 # # **Important**: B is *not* a clone. Also note that there is only *one* # node called A and *one* node called B. The children arrays will look # like:: # # ROOT.children = [A,A] # A.children = [B] # B.children = [] # # It surely must be reasonable to pass either *or both* positions p1 and # p2 to p.deletePositionsInList. But after deleting the B corresponding # to p1, the children arrays will look like: # # ROOT.children = [A,A] # A.children = [] # B.children = [] # B is no longer referenced anywhere! # # So if p.deletePositionsInList attempts to delete position p2 (from A), # B will no longer appear in A.children! # # There are many other cases that we could discuss, but the conclusion # in all cases is that we must use the positions passed to # p.deletePositionsInList only as *hints* about what to do. # # Happily, there is a simple strategy that sidesteps all the # difficulties: # # Step 1. Verify, *before* making any changes to the outline, that all # the positions passed to p.deletePositionsInList *initially* make # sense. # # Step 2. Treat each position as a "request" to delete *some* vnode from # the children array in the *position's* parent vnode. # # This is just a bit subtle. Let me explain it in detail. # # First, recall that vnodes do not have unique parent vnodes. Because of # clones, a vnode may may have *many* parents. Happily, every position # *does* specify a unique parent (vnode) at that position. # # Second, as shown above, there is no way to order positions such that # all later positions remain valid. As the example above shows, deleting # (the vnode corresponding to) a position P may cause *all* later # positions referring to P.v to refer to *already deleted* vnodes. # # In other words, we simply *must* ignore the child indices in # positions. Given a position P, P.parent is well defined. So Step 2 # above will simply delete the *first* element in P.parent.children # containing P.v. # # As we have seen, there may not even *be* any such element of # P.parent.children: a previous delete may have already deleted the last # item of P.parent.children equal to P.v. That should *not* be # considered an error--Step 1 has ensured that all positions # *originally* did make sense. # # Summary # # Positions passed to p.deletePositionsInList specify *vnodes* to be # deleted from specific parents, but they do *not* specify at what index # in the parent.children array (if any!) those vnodes are to be found. # The algorithm will delete the *first* item in the children array that # references the vnode to be deleted. # # This will almost always be good enough. In the unlikely event that # more control is desired, p.deletePositionsInList can not possibly be # used. # # The new emphasis on vnodes at last puts the problem an a completely # solid foundation. Moreover, the new algorithm should be considerably # faster than the old: there is no need to sort positions. #@-<< theory of operation >> c = self # Verify all positions *before* altering the tree. aList2 = [] for p in aList: if c.positionExists(p): aList2.append(p.copy()) else: g.trace('invalid position', p) if not aList2: return # Don't redraw the screen unless necessary! # Delete p.v for all positions p in reversed(sorted(aList2)). if callback: for p in reversed(sorted(aList2)): if c.positionExists(p): callback(p) else: for p in reversed(sorted(aList2)): if c.positionExists(p): v = p.v parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode if v in parent_v.children: childIndex = parent_v.children.index(v) v._cutLink(childIndex, parent_v) # Bug fix 2014/03/13: Make sure c.hiddenRootNode always has at least one child. if not c.hiddenRootNode.children: v = leoNodes.VNode(context=c) v._addLink(childIndex=0, parent_v=c.hiddenRootNode, adjust=False) if redraw: c.selectPosition(c.rootPosition())
# Calls redraw() # c.redraw() #@+node:ekr.20091211111443.6265: *4* c.doBatchOperations & helpers
[docs] def doBatchOperations(self, aList=None): # Validate aList and create the parents dict if aList is None: aList = [] ok, d = self.checkBatchOperationsList(aList) if not ok: return g.error('do-batch-operations: invalid list argument') for v in list(d.keys()): aList2 = d.get(v, []) if aList2: aList.sort()
#@+node:ekr.20091211111443.6266: *5* c.checkBatchOperationsList
[docs] def checkBatchOperationsList(self, aList): ok = True; d = {} for z in aList: try: op, p, n = z ok = (op in ('insert', 'delete') and isinstance(p, leoNodes.position) and g.isInt(n)) if ok: aList2 = d.get(p.v, []) data = n, op aList2.append(data) d[p.v] = aList2 except ValueError: ok = False if not ok: break return ok, d
#@+node:ekr.20091002083910.6106: *4* c.find_b & find_h (PosList) #@+<< PosList doc >> #@+node:bob.20101215134608.5898: *5* << PosList doc >> #@@nocolor-node #@+at # List of positions # # Functions find_h() and find_b() both return an instance of PosList. # # Methods filter_h() and filter_b() refine a PosList. # # Method children() generates a new PosList by descending one level from # all the nodes in a PosList. # # A chain of PosList method calls must begin with find_h() or find_b(). # The rest of the chain can be any combination of filter_h(), # filter_b(), and children(). For example: # # pl = c.find_h('@file.*py').children().filter_h('class.*').filter_b('import (.*)') # # For each position, pos, in the PosList returned, find_h() and # filter_h() set attribute to the match object (see Python # Regular Expression documentation) for the pattern match. # # Caution: The pattern given to find_h() or filter_h() must match zero # or more characters at the beginning of the headline. # # For each position, pos, the postlist returned, find_b() and filter_b() # set attribute pos.matchiter to an iterator that will return a match # object for each of the non-overlapping matches of the pattern in the # body of the node. #@-<< PosList doc >> #@+node:ville.20090311190405.70: *5* c.find_h
[docs] def find_h(self, regex, flags=re.IGNORECASE): """ Return list (a PosList) of all nodes where zero or more characters at the beginning of the headline match regex """ c = self pat = re.compile(regex, flags) res = leoNodes.PosList() for p in c.all_positions(): m = re.match(pat, p.h) if m: pc = p.copy() = m res.append(pc) return res
#@+node:ville.20090311200059.1: *5* c.find_b
[docs] def find_b(self, regex, flags=re.IGNORECASE | re.MULTILINE): """ Return list (a PosList) of all nodes whose body matches regex one or more times. """ c = self pat = re.compile(regex, flags) res = leoNodes.PosList() for p in c.all_positions(): m = re.finditer(pat, p.b) t1, t2 = itertools.tee(m, 2) try: if g.isPython3: t1.__next__() else: except StopIteration: continue pc = p.copy() pc.matchiter = t2 res.append(pc) return res
#@+node:ekr.20150410095543.1: *4* c.findNodeOutsideAnyAtFileTree
[docs] def findNodeOutsideAnyAtFileTree(self, target): '''Select the first clone of target that is outside any @file node.''' c = self if target.isCloned(): v = target.v for p in c.all_positions(): if p.v == v: for parent in p.self_and_parents(): if parent.isAnyAtFileNode(): break else: return p return target
#@+node:ekr.20171124155725.1: *3* c.Settings #@+node:ekr.20171114114908.1: *4* c.registerReloadSettings
[docs] def registerReloadSettings(self, obj): '''Enter object into c.configurables.''' c = self if obj not in c.configurables: c.configurables.append(obj)
#@+node:ekr.20170221040621.1: *4* c.reloadConfigurableSettings
[docs] def reloadConfigurableSettings(self): ''' Call all reloadSettings method in c.subcommanders, c.configurables and other known classes. ''' c = self table = [,, c.k.autoCompleter, c.frame, c.frame.body, c.frame.log, c.frame.tree, c.frame.body.colorizer, getattr(c.frame.body.colorizer, 'highlighter', None), ] for obj in table: if obj: c.registerReloadSettings(obj) c.configurables = list(set(c.configurables)) # Useful now that instances add themselves to c.configurables. c.configurables.sort(key=lambda obj: obj.__class__.__name__.lower()) for obj in c.configurables: func = getattr(obj, 'reloadSettings', None) if func: # pylint: disable=not-callable try: func() except Exception: g.es_exception() c.configurables.remove(obj)
#@-others #@-others #@@language python #@@tabwidth -4 #@@pagewidth 70 #@-leo