def skipNewline(self, s, i, kind): ''' Skip whitespace and comments up to a newline, then skip the newline. Unlike the base class: - we always skip to a newline, if any. - we do *not* issue an error if no newline is found. ''' while i < len(s): progress = i i = self.skipWs(s, i) if self.startsComment(s, i): i = self.skipComment(s, i) else: break assert i > progress if i >= len(s): return len(s) elif g.match(s, i, '\n'): return i + 1 else: # A hack, but probably good enough in most cases. while i < len(s) and s[i] in ' \t()};': i += 1 if g.match(s, i, '\n'): i += 1 return i
def skipToEndOfTag(self,s,i,start): '''Skip to the end of an open tag. return i,ok,complete where complete is True if the tag of the form <name/> ''' trace = False complete,ok = False,False while i < len(s): progress = i if i == '"': i = self.skipString(s,i) elif g.match(s,i,'<!--'): i = self.skipComment(s,i) elif g.match(s,i,'<'): complete,ok = False,False ; break elif g.match(s,i,'/>'): i = g.skip_ws(s,i+2) complete,ok = True,True ; break elif g.match(s,i,'>'): i += 1 complete,ok = False,True ; break else: i += 1 assert progress < i if trace: g.trace('ok',ok,repr(s[start:i])) return i,ok,complete
def tokenize(self, s): '''Tokenize comments, strings, identifiers, whitespace and operators.''' i, result = 0, [] while i < len(s): # Loop invariant: at end: j > i and s[i:j] is the new token. j = i ch = s[i] if ch in '@\n': # Make *sure* these are separate tokens. j += 1 elif ch == '#': # Preprocessor directive. j = g.skip_to_end_of_line(s, i) elif ch in ' \t': j = g.skip_ws(s, i) elif ch.isalpha() or ch == '_': j = g.skip_c_id(s, i) elif g.match(s, i, '//'): j = g.skip_line(s, i) elif g.match(s, i, '/*'): j = self.skip_block_comment(s, i) elif ch in "'\"": j = g.skip_string(s, i) else: j += 1 assert j > i result.append(''.join(s[i: j])) i = j # Advance. return result
def skipSigTail(self, s, i, kind): """Skip from the end of the arg list to the start of the block.""" trace = False and self.trace # Pascal interface has no tail. if kind == "class": return i, True start = i i = g.skip_ws(s, i) for z in self.sigFailTokens: if g.match(s, i, z): if trace: g.trace("failToken", z, "line", g.skip_line(s, i)) return i, False while i < len(s): if self.startsComment(s, i): i = self.skipComment(s, i) elif g.match(s, i, self.blockDelim1): if trace: g.trace(repr(s[start:i])) return i, True else: i += 1 if trace: g.trace("no block delim") return i, False
def skipCodeBlock (self,s,i,kind): trace = False ; verbose = True # if trace: g.trace('***',g.callers()) startIndent = self.startSigIndent if trace: g.trace('startIndent',startIndent) assert startIndent is not None i = start = g.skip_ws_and_nl(s,i) parenCount = 0 underIndentedStart = None # The start of trailing underindented blank or comment lines. while i < len(s): progress = i ch = s[i] if g.is_nl(s,i): if trace and verbose: g.trace(g.get_line(s,i)) backslashNewline = (i > 0 and g.match(s,i-1,'\\\n')) if backslashNewline: # An underindented line, including docstring, # does not end the code block. i += 1 # 2010/11/01 else: i = g.skip_nl(s,i) j = g.skip_ws(s,i) if g.is_nl(s,j): pass # We have already made progress. else: i,underIndentedStart,breakFlag = self.pythonNewlineHelper( s,i,parenCount,startIndent,underIndentedStart) if breakFlag: break elif ch == '#': i = g.skip_to_end_of_line(s,i) elif ch == '"' or ch == '\'': i = g.skip_python_string(s,i) elif ch in '[{(': i += 1 ; parenCount += 1 # g.trace('ch',ch,parenCount) elif ch in ']})': i += 1 ; parenCount -= 1 # g.trace('ch',ch,parenCount) else: i += 1 assert(progress < i) # The actual end of the block. if underIndentedStart is not None: i = underIndentedStart if trace: g.trace('***backtracking to underindent range') if trace: g.trace(g.get_line(s,i)) if 0 < i < len(s) and not g.match(s,i-1,'\n'): g.trace('Can not happen: Python block does not end in a newline.') g.trace(g.get_line(s,i)) return i,False # 2010/02/19: Include all following material # until the next 'def' or 'class' i = self.skipToTheNextClassOrFunction(s,i,startIndent) if (trace or self.trace) and s[start:i].strip(): g.trace('%s returns\n' % (kind) + s[start:i]) return i,True
def deleteComments(self, event=None): #@+<< deleteComments docstring >> #@+node:ekr.20171123135625.37: *3* << deleteComments docstring >> #@@pagewidth 50 ''' Removes one level of comment delimiters from all selected lines. The applicable @language directive determines the comment delimiters to be removed. Removes single-line comments if possible; removes block comments for languages like html that lack single-line comments. *See also*: add-comments. ''' #@-<< deleteComments docstring >> c = self p = c.p head, lines, tail, oldSel, oldYview = self.getBodyLines() result = [] if not lines: g.warning('no text selected') return # The default language in effect at p. language = c.frame.body.colorizer.scanLanguageDirectives(p) if c.hasAmbiguousLanguage(p): language = c.getLanguageAtCursor(p, language) d1, d2, d3 = g.set_delims_from_language(language) if d1: # Remove the single-line comment delim in front of each line d1b = d1 + ' ' n1, n1b = len(d1), len(d1b) for s in lines: i = g.skip_ws(s, 0) if g.match(s, i, d1b): result.append(s[: i] + s[i + n1b:]) elif g.match(s, i, d1): result.append(s[: i] + s[i + n1:]) else: result.append(s) else: # Remove the block comment delimiters from each line. n2, n3 = len(d2), len(d3) for s in lines: i = g.skip_ws(s, 0) j = s.find(d3, i + n2) if g.match(s, i, d2) and j > -1: first = i + n2 if g.match(s, first, ' '): first += 1 last = j if g.match(s, last - 1, ' '): last -= 1 result.append(s[: i] + s[first: last] + s[j + n3:]) else: result.append(s) result = ''.join(result) c.updateBodyPane(head, result, tail, undoType='Delete Comments', oldSel=None, oldYview=oldYview)
def skipInterface(self, s, i): """Skip from the opening delim to *past* the matching closing delim. If no matching is found i is set to len(s)""" trace = False start = i delim2 = "end." level = 0 start = i startIndent = self.startSigIndent if trace: g.trace("***", "startIndent", startIndent, g.callers()) while i < len(s): progress = i if g.is_nl(s, i): backslashNewline = i > 0 and g.match(s, i - 1, "\\\n") i = g.skip_nl(s, i) if not backslashNewline and not g.is_nl(s, i): j, indent = g.skip_leading_ws_with_indent(s, i, self.tab_width) line = g.get_line(s, j) if trace: g.trace("indent", indent, line) if indent < startIndent and line.strip(): # An non-empty underindented line. # Issue an error unless it contains just the closing bracket. if level == 1 and g.match(s, j, delim2): pass else: if j not in self.errorLines: # No error yet given. self.errorLines.append(j) self.underindentedLine(line) elif s[i] in (" ", "\t"): i += 1 # speed up the scan. elif self.startsComment(s, i): i = self.skipComment(s, i) elif self.startsString(s, i): i = self.skipString(s, i) elif g.match(s, i, delim2): i += len(delim2) if trace: g.trace("returns\n", repr(s[start:i])) return i else: i += 1 assert progress < i self.error("no interface") if 1: g.pr("** no interface **") i, j = g.getLine(s, start) g.trace(i, s[i:j]) else: if trace: g.trace("** no interface") return start
def extendSignature(self, s, i): '''Extend the text to be added to the class node following the signature. The text *must* end with a newline.''' # Add a docstring to the class node, # And everything on the line following it j = g.skip_ws_and_nl(s, i) if g.match(s, j, '"""') or g.match(s, j, "'''"): j = g.skip_python_string(s, j) if j < len(s): # No scanning error. # Return the docstring only if nothing but whitespace follows. j = g.skip_ws(s, j) if g.is_nl(s, j): return j + 1 return i
def skipSigTail(self, s, i, kind): '''Skip from the end of the arg list to the start of the block.''' if 1: # New code while i < len(s): ch = s[i] if ch == ':': return i, True elif ch == '\n': return i, False elif self.startsComment(s, i): i = self.skipComment(s, i) else: i += 1 return i, False else: # old code while i < len(s): ch = s[i] if ch == '\n': break elif ch in (' ', '\t',): i += 1 elif self.startsComment(s, i): i = self.skipComment(s, i) else: break return i, g.match(s, i, ':')
def skipCodeBlock (self,s,i,kind): '''Skip the code block in a function or class definition.''' trace = False start = i if kind == 'class': i = self.skipInterface(s,i) else: i = self.skipBlock(s,i,delim1=None,delim2=None) if self.sigFailTokens: i = g.skip_ws(s,i) for z in self.sigFailTokens: if g.match(s,i,z): if trace: g.trace('failtoken',z) return start,False if i > start: i = self.skipNewline(s,i,kind) if trace: g.trace(g.callers()) g.trace('returns...\n',g.listToString(g.splitLines(s[start:i]))) return i,True
def parseHeadline(s): """ Parse a headline of the form @kind:name=val Return (kind,name,val). Leo 4.11.1: Ignore everything after @data name. """ kind = name = val = None if g.match(s, 0, g.u('@')): i = g.skip_id(s, 1, chars=g.u('-')) i = g.skip_ws(s, i) kind = s[1: i].strip() if kind: # name is everything up to '=' if kind == g.u('data'): # i = g.skip_ws(s,i) j = s.find(g.u(' '), i) if j == -1: name = s[i:].strip() else: name = s[i: j].strip() else: j = s.find(g.u('='), i) if j == -1: name = s[i:].strip() else: name = s[i: j].strip() # val is everything after the '=' val = s[j + 1:].strip() # g.trace("%50s %10s %s" %(name,kind,val)) return kind, name, val
def handleAtPluginNode (self,p): '''Handle @plugin nodes.''' c = self.c tag = "@plugin" h = p.h assert(g.match(h,0,tag)) # Get the name of the module. theFile = h[len(tag):].strip() # The following two lines break g.loadOnePlugin #if theFile[-3:] == ".py": # theFile = theFile[:-3] # in fact, I believe the opposite behavior is intended: add .py if it doesn't exist if theFile[-3:] != ".py": theFile = theFile + ".py" theFile = g.toUnicode(theFile) if not self.atPluginNodes: g.warning("disabled @plugin: %s" % (theFile)) # elif theFile in g.app.loadedPlugins: elif g.pluginIsLoaded(theFile): g.warning("plugin already loaded: %s" % (theFile)) else: theModule = g.loadOnePlugin(theFile)
def skip_block_comment(self, s, i): assert(g.match(s, i, "/*")) j = s.find("*/", i) if j == -1: return len(s) else: return j + 2
def adjustDefStart(self, s, i): '''A hook to allow the Python importer to adjust the start of a class or function to include decorators. ''' # Invariant: i does not change. # Invariant: start is the present return value. try: assert s[i] != '\n' start = j = g.find_line_start(s, i) if i > 0 else 0 # g.trace('entry',j,i,repr(s[j:i+10])) assert j == 0 or s[j - 1] == '\n' while j > 0: progress = j j1 = j = g.find_line_start(s, j - 2) # g.trace('line',repr(s[j:progress])) j = g.skip_ws(s, j) if not g.match(s, j, '@'): break k = g.skip_id(s, j + 1) word = s[j: k] # Leo directives halt the scan. if word and word in g.globalDirectiveList: break # A decorator. start = j = j1 assert j < progress # g.trace('**returns %s, %s' % (repr(s[start:i]),repr(s[i:i+20]))) return start except AssertionError: g.es_exception() return i
def startsHelper(self, s, i, kind, tags, tag=None): '''return True if s[i:] starts section. Sets sigStart, sigEnd, sigId and codeEnd ivars.''' trace = False self.codeEnd = self.sigEnd = self.sigId = None self.sigStart = i sigStart = i ok, sigId, i = self.isSectionLine(s, i) if not sigId or not ok: # if trace: g.trace('fail',repr(g.getLine(s,i))) return False i = sigEnd = g.skip_line(s, i) # Skip everything until the next section. while i < len(s): progress = i ok, junk, junk = self.isSectionLine(s, i) if ok: break # don't change i. i = g.skip_line(s, i) assert progress < i # Success: set the ivars. self.sigStart = sigStart self.codeEnd = i self.sigEnd = sigEnd self.sigId = sigId self.classId = None # Note: backing up here is safe because # we won't back up past scan's 'start' point. # Thus, characters will never be output twice. k = self.sigStart if not g.match(s, k, '\n'): self.sigStart = g.find_line_start(s, k) if trace: g.trace(sigId, 'returns\n' + s[self.sigStart: i] + '\nEND') return True
def pythonNewlineHelper (self,s,i,parenCount,startIndent,underIndentedStart): trace = False breakFlag = False j, indent = g.skip_leading_ws_with_indent(s,i,self.tab_width) if trace: g.trace( 'startIndent',startIndent,'indent',indent,'parenCount',parenCount, 'line',repr(g.get_line(s,j))) if indent <= startIndent and parenCount == 0: # An underindented line: it ends the block *unless* # it is a blank or comment line or (2008/9/1) the end of a triple-quoted string. if g.match(s,j,'#'): if trace: g.trace('underindent: comment') if underIndentedStart is None: underIndentedStart = i i = j elif g.match(s,j,'\n'): if trace: g.trace('underindent: blank line') # Blank lines never start the range of underindented lines. i = j else: if trace: g.trace('underindent: end of block') breakFlag = True # The actual end of the block. else: if underIndentedStart and g.match(s,j,'\n'): # Add the blank line to the underindented range. if trace: g.trace('properly indented blank line extends underindent range') elif underIndentedStart and g.match(s,j,'#'): # Add the (properly indented!) comment line to the underindented range. if trace: g.trace('properly indented comment line extends underindent range') elif underIndentedStart is None: pass else: # A properly indented non-comment line. # Give a message for all underindented comments in underindented range. if trace: g.trace('properly indented line generates underindent errors') s2 = s[underIndentedStart:i] lines = g.splitlines(s2) for line in lines: if line.strip(): junk, indent = g.skip_leading_ws_with_indent(line,0,self.tab_width) if indent <= startIndent: if j not in self.errorLines: # No error yet given. self.errorLines.append(j) self.underindentedComment(line) underIndentedStart = None if trace: g.trace('breakFlag',breakFlag,'returns',i,'underIndentedStart',underIndentedStart) return i,underIndentedStart,breakFlag
def skipToMatchingTag (self,s,i,tag,tags,start): '''Skip the entire class definition. Return i,ok. ''' trace = False found,level,target_tag = False,1,tag.lower() while i < len(s): progress = i if s[i] == '"': i = self.skipString(s,i) elif g.match(s,i,'<!--'): i = self.skipComment(s,i) elif g.match(s,i,'</'): j = i+2 i = self.skipId(s,j) tag2 = s[j:i].lower() i,ok,complete = self.skipToEndOfTag(s,i,start=j) # Sets complete if /> terminates the tag. if ok and tag2 == target_tag: level -= 1 if level == 0: found = True ; break elif g.match(s,i,'<'): # An open tag. j = g.skip_ws_and_nl(s,i+1) i = self.skipId(s,j) word = s[j:i].lower() i,ok,complete = self.skipToEndOfTag(s,i,start=j) # **Important**: only bump level for nested *target* tags. # This avoids problems when interior tags are not properly nested. if ok and word == target_tag and not complete: level += 1 elif g.match(s,i,'/>'): # This is a syntax error. # This should have been eaten by skipToEndOfTag. i += 2 g.trace('syntax error: unmatched "/>"') else: i += 1 assert progress < i if trace: g.trace('%sfound:%s\n%s\n\n*****end %s\n' % ( '' if found else 'not ',target_tag,s[start:i],target_tag)) return i,found
def startsString(self,s,i): '''Single quotes do *not* start strings in xml or html.''' # Fix bug 1208659: leo parsed the wrong line number of html file. # Note: the compare failure was caused by using BaseScanner.startsString. # The line number problem is a separate issue. return g.match(s,i,'"')
def killLine(self, event): '''Kill the line containing the cursor.''' w = self.editWidget(event) if not w: return s = w.getAllText() ins = w.getInsertPoint() i, j = g.getLine(s, ins) if ins >= len(s) and g.match(s, j - 1, '\n'): # Kill the trailing newline of the body text. i = max(0, len(s) - 1) j = len(s) elif j > i + 1 and g.match(s, j - 1, '\n'): # Kill the line, but not the newline. j -= 1 else: pass # Kill the newline in the present line. self.kill(event, i, j, undoType='kill-line')
def scanFunction(self, parent, s, i): '''scan function(args) { body } and then rescan body.''' trace = True and not g.unitTesting # k1, k2 = g.getLine(s,i) # g.trace(s[k1:k2]) i1 = i # Scan backward for '(' i2 = i-1 while 0 <= i2 and s[i2].isspace(): i2 -= 1 in_expr = s[i2] == '(' assert g.match(s, i, 'function') i += len('function') i = g.skip_ws_and_nl(s, i) i = self.skipId(s, i) i = g.skip_ws_and_nl(s, i) # Skip the argument list. if not g.match(s, i, '('): if trace: g.trace('syntax error: no argument list',i) return i i = self.skipBlock(s, i,'(', ')') # skipBlock skips the ')' if i == len(s): g.trace('no args', g.get_line(s, i)) return i # Skip the body. i = g.skip_ws_and_nl(s, i) if not g.match(s, i, '{'): if trace: g.trace('no function body', i) return i block_i1 = i block_i2 = i = self.skipBlock(s, i, '{', '}') j = g.skip_ws_and_nl(s,i) if g.match(s, j, '('): i = g.skip_parens(s, i) if in_expr: j = g.skip_ws_and_nl(s,i) if g.match(s, j, ')'): i = j + 1 assert i > i1 block_s = s[block_i1+1:block_i2-1].strip() if trace: g.trace('*** rescanning ***\n\n%s\n\n' % block_s) self.scanHelper(parent, block_s) return i
def doEnter(self, event): # set cursor to end of line to avoid line splitting trace = False and not g.unitTesting textCursor = self.textCursor() position = len(self.document().toPlainText()) textCursor.setPosition(position) self.setTextCursor(textCursor) lines = [] block = self.document().lastBlock() # #792: python_console plugin doesn't handle copy/paste properly. while block: line = g.toUnicode(block.text()) block = block.previous() done = g.match(line, 0, '>>>') if done: line = line [4:] # remove marker lines.insert(0, line.rstrip()) if done: break if trace: g.trace() g.printObj(lines) self.historyIndex = -1 if len(lines) > 1: # #792: python_console plugin doesn't handle copy/paste properly. self.append('') self.command = '\n'.join(lines).rstrip() + '\n' self.interpreter.runIt(self.command) self.command = '' self.marker() return if self.customCommands(line): return None self.haveLine = bool(line) if self.haveLine: self.history.insert(0, line) if line[-1] == ':': self.multiLine = True g.trace(self.haveLine, self.multiLine, repr(line)) if self.haveLine: if self.multiLine: self.command += line + '\n' # + command and line self.append('') else: self.command = line self.append('') self.interpreter.runIt(self.command) self.command = '' else: if self.multiLine: self.append('') self.interpreter.runIt(self.command) self.command = '' self.multiLine = False # back to single line else: # Do nothing. self.append('') self.marker() return None
def startsString(self, s, i): '''Return True if s[i:] starts a JavaScript string.''' if g.match(s, i, '"') or g.match(s, i, "'"): # Count the number of preceding backslashes: n = 0; j = i - 1 while j >= 0 and s[j] == '\\': n += 1 j -= 1 return (n % 2) == 0 elif g.match(s, i, '//'): # Neither of these are valid in regexp literals. return False elif g.match(s, i, '/'): # could be a division operator or regexp literal. while i >= 0 and s[i - 1] in ' \t\n': i -= 1 if i == 0: return True return s[i - 1] in (',([{=') else: return False
def killToEndOfLine(self, event): '''Kill from the cursor to end of the line.''' w = self.editWidget(event) if not w: return s = w.getAllText() ins = w.getInsertPoint() i, j = g.getLine(s, ins) if ins >= len(s) and g.match(s, j - 1, '\n'): # Kill the trailing newline of the body text. i = max(0, len(s) - 1) j = len(s) elif ins + 1 < j and s[ins: j - 1].strip() and g.match(s, j - 1, '\n'): # Kill the line, but not the newline. i, j = ins, j - 1 elif g.match(s, j - 1, '\n'): i = ins # Kill the newline in the present line. else: i = j if i < j: self.kill(event, i, j, undoType='kill-line')
def startsParagraph(s): '''Return True if line s starts a paragraph.''' if not s.strip(): val = False elif s.strip() in ('"""', "'''"): val = True elif s[0].isdigit(): i = 0 while i < len(s) and s[i].isdigit(): i += 1 val = g.match(s, i, ')') or g.match(s, i, '.') elif s[0].isalpha(): # Careful: single characters only. # This could cause problems in some situations. val = ( (g.match(s, 1, ')') or g.match(s, 1, '.')) and (len(s) < 2 or s[2] in (' \t\n'))) else: val = s.startswith('@') or s.startswith('-') return val
def isSectionLine(self, s, i): i = g.skip_ws(s, i) if not g.match(s, i, '['): return False, None, i k = s.find('\n', i + 1) if k == -1: k = len(s) j = s.find(']', i + 1) if -1 < j < k: return True, s[i: j + 1], i else: return False, None, i
def getColor(self, h): '''Returns the background color from the given headline string''' color = None tag = '@color' i = h.find(tag) if i > -1: j = g.skip_ws(h, i + len(tag)) if g.match(h, j, '='): j += 1 k = h.find('@', j + 1) if k == -1: k = len(h) color = h[j: k].strip() return color
def skipSigTail(self, s, i, kind=None): '''Skip from the end of the arg list to the start of the block.''' trace = False and self.trace i1 = i i = self.skipWs(s, i) for z in self.sigFailTokens: if g.match(s, i, z): if trace: g.trace('failToken', z, 'line', g.skip_line(s, i)) return i, False while i < len(s): progress = i if self.startsComment(s, i): i = self.skipComment(s, i) elif g.match(s, i, self.blockDelim1): if trace: g.trace(repr(s[i1: i])) return i, True else: i += 1 assert progress < i if trace: g.trace('no block delim') return i, False
def skipSigTail(self, s, i, kind=None): '''Skip from the end of the arg list to the start of the block.''' trace = False and self.trace i1 = i i = self.skipWs(s, i) for z in self.sigFailTokens: if g.match(s, i, z): if trace: g.trace('failToken', z, 'line', g.skip_line(s, i)) return i, False while i < len(s): progress = i if self.startsComment(s, i): i = self.skipComment(s, i) elif g.match(s, i, self.blockDelim1): if trace: g.trace(repr(s[i1:i])) return i, True else: i += 1 assert progress < i if trace: g.trace('no block delim') return i, False
def startsFunction(self, s, i): '''Return True if s[i:] starts a function. Sets sigStart, sigEnd, sigId and codeEnd ivars.''' self.startSigIndent = self.getLeadingIndent(s, i) self.sigStart = i self.codeEnd = self.sigEnd = self.sigId = None if not g.match(s, i, '('): return False end = self.skipBlock(s, i) # g.trace('%3s %15s block: %s' % (i,repr(s[i:i+10]),repr(s[i:end]))) if not g.match(s, end - 1, ')'): return False i = g.skip_ws(s, i + 1) if not g.match_word(s, i, 'defun'): return False i += len('defun') sigEnd = i = g.skip_ws_and_nl(s, i) j = self.skipId(s, i) # Bug fix: 2009/09/30 word = s[i: j] if not word: return False self.codeEnd = end + 1 self.sigEnd = sigEnd self.sigId = word return True
def skipSigTail(self, s, i, kind): '''Skip from the end of the arg list to the start of the block.''' while i < len(s): ch = s[i] if ch == '\n': break elif ch in (' ', '\t',): i += 1 elif self.startsComment(s, i): i = self.skipComment(s, i) else: break return i, g.match(s, i, ':')
def skipSigTail(self, s, i, kind): '''Skip from the end of the arg list to the start of the block.''' trace = False and self.trace # Pascal interface has no tail. if kind == 'class': return i, True start = i i = g.skip_ws(s, i) for z in self.sigFailTokens: if g.match(s, i, z): if trace: g.trace('failToken', z, 'line', g.skip_line(s, i)) return i, False while i < len(s): if self.startsComment(s, i): i = self.skipComment(s, i) elif g.match(s, i, self.blockDelim1): if trace: g.trace(repr(s[start:i])) return i, True else: i += 1 if trace: g.trace('no block delim') return i, False
def startsParagraph(s): '''Return True if line s starts a paragraph.''' trace = False and not g.unitTesting if not s.strip(): val = False elif s.strip() in ('"""', "'''"): val = True elif s[0].isdigit(): i = 0 while i < len(s) and s[i].isdigit(): i += 1 val = g.match(s, i, ')') or g.match(s, i, '.') elif s[0].isalpha(): # Careful: single characters only. # This could cause problems in some situations. val = ( (g.match(s, 1, ')') or g.match(s, 1, '.')) and (len(s) < 2 or s[2] in (' \t\n'))) else: val = s.startswith('@') or s.startswith('-') if trace: g.trace(val, repr(s)) return val
def rp_wrap_all_lines(c, indents, leading_ws, lines, pageWidth): '''Compute the result of wrapping all lines.''' trailingNL = lines and lines[-1].endswith('\n') lines = [z[: -1] if z.endswith('\n') else z for z in lines] if lines: # Bug fix: 2013/12/22. s = lines[0] if startsParagraph(s): # Adjust indents[1] # Similar to code in startsParagraph(s) i = 0 if s[0].isdigit(): while i < len(s) and s[i].isdigit(): i += 1 if g.match(s, i, ')') or g.match(s, i, '.'): i += 1 elif s[0].isalpha(): if g.match(s, 1, ')') or g.match(s, 1, '.'): i = 2 elif s[0] == '-': i = 1 # Never decrease indentation. i = g.skip_ws(s, i + 1) if i > indents[1]: indents[1] = i leading_ws[1] = ' ' * i # Wrap the lines, decreasing the page width by indent. result = g.wrap_lines(lines, pageWidth - indents[1], pageWidth - indents[0]) # prefix with the leading whitespace, if any paddedResult = [] paddedResult.append(leading_ws[0] + result[0]) for line in result[1:]: paddedResult.append(leading_ws[1] + line) # Convert the result to a string. result = '\n'.join(paddedResult) if trailingNL: result = result + '\n' return result
def skipArgs(self, s, i, kind): '''Skip the argument or class list. Return i, ok kind is in ('class','function')''' start = i i = g.skip_ws_and_nl(s, i) if not g.match(s, i, '('): return start, kind == 'class' i = self.skipParens(s, i) # skipParens skips the ')' if i >= len(s): return start, False else: return i, True
def getShortcut(self, h): '''Return the keyboard shortcut from the given headline string''' shortcut = None i = h.find('@key') if i > -1: j = g.skip_ws(h, i + len('@key')) if g.match(h, j, '='): j += 1 if 0: shortcut = h[j:].strip() else: # new logic 1/3/2014 Jake Peck k = h.find('@', j + 1) if k == -1: k = len(h) shortcut = h[j: k].strip() return shortcut
def truncateButtonText(self, s): # 2011/10/16: Remove @button here only. i = 0 while g.match(s, i, '@'): i += 1 if g.match_word(s, i, 'button'): i += 6 s = s[i:] if self.maxButtonSize > 10: s = s[:self.maxButtonSize] if s.endswith('-'): s = s[:-1] s = s.strip('-') return s.strip()
def getShortcut(self, h): '''Return the keyboard shortcut from the given headline string''' shortcut = None i = h.find('@key') if i > -1: j = g.skip_ws(h, i + len('@key')) if g.match(h, j, '='): j += 1 if 0: shortcut = h[j:].strip() else: # new logic 1/3/2014 Jake Peck k = h.find('@', j + 1) if k == -1: k = len(h) shortcut = h[j:k].strip() return shortcut
def skipNewline(self, s, i, kind): ''' Skip whitespace and comments up to a newline, then skip the newline. Unlike the base class: - we always skip to a newline, if any. - we do *not* issue an error if no newline is found. ''' while i < len(s): i = self.skipWs(s, i) if self.startsComment(s, i): i = self.skipComment(s, i) else: break if i >= len(s): return len(s) elif g.match(s, i, '\n'): return i + 1 else: # A hack, but probably good enough in most cases. while i < len(s) and s[i] in ' \t()};': i += 1 if g.match(s, i, '\n'): i += 1 return i
def skip_heredoc_string(self, s, i): #@+<< skip_heredoc docstrig >> #@+node:ekr.20161130044051.2: *4* << skip_heredoc docstrig >> #@@nocolor-node ''' 08-SEP-2002 DTHEIN: added function skip_heredoc_string A heredoc string in PHP looks like: <<<EOS This is my string. It is mine. I own it. No one else has it. EOS It begins with <<< plus a token (naming same as PHP variable names). It ends with the token on a line by itself (must start in first position. ''' #@-<< skip_heredoc docstrig >> j = i assert (g.match(s, i, "<<<")) # pylint: disable=anomalous-backslash-in-string m = re.match("\<\<\<([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)", s[i:]) if m is None: i += 3 return i # 14-SEP-2002 DTHEIN: needed to add \n to find word, not just string delim = m.group(1) + '\n' i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: look after \n, not before n = len(s) while i < n and not g.match(s, i, delim): i = g.skip_line(s, i) # 14-SEP-2002 DTHEIN: move past \n if i >= n: g.scanError("Run on string: " + s[j:i]) elif g.match(s, i, delim): i += len(delim) return i
def getArgs(self, h): args = [] tag = '@args' i = h.find(tag) if i > -1: j = g.skip_ws(h, i + len(tag)) # 2011/10/16: Make '=' sign optional. if g.match(h, j, '='): j += 1 s = h[j + 1:].strip() args = s.split(',') args = [z.strip() for z in args] # g.trace('args',repr(args)) return args
def handleAtScriptNode(self, p): '''Handle @script nodes.''' c = self.c tag = "@script" assert (g.match(p.h, 0, tag)) name = p.h[len(tag):].strip() args = self.getArgs(p) if self.atScriptNodes: g.blue("executing script %s" % (name)) c.executeScript(args=args, p=p, useSelectedText=False, silent=True) else: g.warning("disabled @script: %s" % (name)) if 0: # Do not assume the script will want to remain in this commander. c.bodyWantsFocus()
def pasteOutline(self, event=None, redrawFlag=True, s=None, undoFlag=True ): """ Paste an outline into the present outline from the clipboard. Nodes do *not* retain their original identify. """ c = self if s is None: s = g.app.gui.getTextFromClipboard() c.endEditing() if not s or not c.canPasteOutline(s): return None # This should never happen. isLeo = g.match(s, 0, g.app.prolog_prefix_string) if not isLeo: return None # Get *position* to be pasted. pasted = c.fileCommands.getLeoOutlineFromClipboard(s) if not pasted: # Leo no longer supports MORE outlines. Use import-MORE-files instead. return None # Validate. c.validateOutline() c.checkOutline() # Handle the "before" data for undo. if undoFlag: undoData = c.undoer.beforeInsertNode(c.p, pasteAsClone=False, copiedBunchList=[], ) # Paste the node into the outline. c.selectPosition(pasted) pasted.setDirty() c.setChanged(redrawFlag=redrawFlag) # Prevent flash when fixing #387. back = pasted.back() if back and back.hasChildren() and back.isExpanded(): pasted.moveToNthChildOf(back, 0) # Finish the command. if undoFlag: c.undoer.afterInsertNode(pasted, 'Paste Node', undoData) if redrawFlag: c.redraw(pasted) c.recolor() return pasted
def scanObject(self, parent, s, i): ''' Scan an object, creating child nodes of prarent for objects that have inner functions. ''' assert s[i] == '{' g.trace(s[i:]) i1 = i i = self.skipBlock(s, i, '{', '}') if g.match(s, i - 1, '}'): g.trace('returns:\n\n%s\n\n' % s[i1:i + 1]) object_s = s[i1 + 1:i - 1].strip() self.scanHelper(parent, object_s) else: g.trace('==== no object') return i
def parseOptionLine(self, s): '''Parse a line containing name=val and return (name,value) or None. If no value is found, default to True.''' s = s.strip() if s.endswith(','): s = s[:-1] # Get name. Names may contain '-' and '_'. i = g.skip_id(s, 0, chars='-_') name = s[:i] if not name: return None j = g.skip_ws(s, i) if g.match(s, j, '='): val = s[j + 1:].strip() # g.trace(val) return name, val else: # g.trace('*True') return name, 'True'
def parseFontLine(line, d): s = line.strip() if not s: return if g.match(s, 0, '#'): s = s[1:].strip() comments = d.get('comments') comments.append(s) d['comments'] = comments else: # name is everything up to '=' i = s.find('=') if i == -1: name = s; val = None else: name = s[: i].strip() val = s[i + 1:].strip() val = val.lstrip('"').rstrip('"') val = val.lstrip("'").rstrip("'") if name.endswith(('_family', '_size', '_slant', '_weight')): d[name.rsplit('_', 1)[1]] = name, val
def getArgs(self, h): args = [] tag = '@args' i = h.find(tag) if i > -1: j = g.skip_ws(h, i + len(tag)) # 2011/10/16: Make '=' sign optional. if g.match(h, j, '='): j += 1 if 0: s = h[j + 1:].strip() else: # new logic 1/3/2014 Jake Peck k = h.find('@', j + 1) if k == -1: k = len(h) s = h[j:k].strip() args = s.split(',') args = [z.strip() for z in args] # g.trace('args',repr(args)) return args
def skipCodeBlock(self, s, i, kind): '''Skip the code block in a function or class definition.''' trace = False start = i if kind == 'class': i = self.skipInterface(s, i) else: i = self.skipBlock(s, i, delim1=None, delim2=None) if self.sigFailTokens: i = g.skip_ws(s, i) for z in self.sigFailTokens: if g.match(s, i, z): if trace: g.trace('failtoken', z) return start, False if i > start: i = self.skipNewline(s, i, kind) if trace: g.trace(g.callers()) g.trace('returns...\n', g.listToString(g.splitLines(s[start:i]))) return i, True
def handleAtPluginNode(self, p): '''Handle @plugin nodes.''' c = self.c tag = "@plugin" h = p.h assert (g.match(h, 0, tag)) # Get the name of the module. theFile = h[len(tag):].strip() if theFile[-3:] == ".py": theFile = theFile[:-3] theFile = g.toUnicode(theFile) if not self.atPluginNodes: g.warning("disabled @plugin: %s" % (theFile)) # elif theFile in g.app.loadedPlugins: elif g.pluginIsLoaded(theFile): g.warning("plugin already loaded: %s" % (theFile)) else: theModule = g.loadOnePlugin(theFile)
def handleAtPluginNode(self, p): '''Handle @plugin nodes.''' tag = "@plugin" h = p.h assert (g.match(h, 0, tag)) # Get the name of the module. theFile = h[len(tag):].strip() # The following two lines break g.loadOnePlugin #if theFile[-3:] == ".py": # theFile = theFile[:-3] # in fact, I believe the opposite behavior is intended: add .py if it doesn't exist if theFile[-3:] != ".py": theFile = theFile + ".py" theFile = g.toUnicode(theFile) if not self.atPluginNodes: g.warning("disabled @plugin: %s" % (theFile)) # elif theFile in g.app.loadedPlugins: elif g.pluginIsLoaded(theFile): g.warning("plugin already loaded: %s" % (theFile)) else: g.loadOnePlugin(theFile)
def getArgs(self, p): '''Return the list of @args field of p.h.''' args = [] if not p: return args h, tag = p.h, '@args' i = h.find(tag) if i > -1: j = g.skip_ws(h, i + len(tag)) # 2011/10/16: Make '=' sign optional. if g.match(h, j, '='): j += 1 if 0: s = h[j + 1:].strip() else: # new logic 1/3/2014 Jake Peck k = h.find('@', j + 1) if k == -1: k = len(h) s = h[j:k].strip() args = s.split(',') args = [z.strip() for z in args] # if args: g.trace(args) return args
def deleteComments(self, event=None): #@+<< deleteComments docstring >> #@+node:ekr.20171123135625.37: *3* << deleteComments docstring >> #@@pagewidth 50 ''' Removes one level of comment delimiters from all selected lines. The applicable @language directive determines the comment delimiters to be removed. Removes single-line comments if possible; removes block comments for languages like html that lack single-line comments. *See also*: add-comments. ''' #@-<< deleteComments docstring >> c = self p = c.p head, lines, tail, oldSel, oldYview = self.getBodyLines() result = [] if not lines: g.warning('no text selected') return # The default language in effect at p. language = c.frame.body.colorizer.scanLanguageDirectives(p) if c.hasAmbiguousLanguage(p): language = c.getLanguageAtCursor(p, language) d1, d2, d3 = g.set_delims_from_language(language) if d1: # Remove the single-line comment delim in front of each line d1b = d1 + ' ' n1, n1b = len(d1), len(d1b) for s in lines: i = g.skip_ws(s, 0) if g.match(s, i, d1b): result.append(s[:i] + s[i + n1b:]) elif g.match(s, i, d1): result.append(s[:i] + s[i + n1:]) else: result.append(s) else: # Remove the block comment delimiters from each line. n2, n3 = len(d2), len(d3) for s in lines: i = g.skip_ws(s, 0) j = s.find(d3, i + n2) if g.match(s, i, d2) and j > -1: first = i + n2 if g.match(s, first, ' '): first += 1 last = j if g.match(s, last - 1, ' '): last -= 1 result.append(s[:i] + s[first:last] + s[j + n3:]) else: result.append(s) result = ''.join(result) c.updateBodyPane(head, result, tail, undoType='Delete Comments', oldSel=None, oldYview=oldYview)
def scan_line(self, s, prev_state): ''' Update the scan state at the *end* of the line by scanning all of s. Distinguishing the the start of a regex from a div operator is tricky: http://stackoverflow.com/questions/4726295/ http://stackoverflow.com/questions/5519596/ (, [, {, ;, and binops can only be followed by a regexp. ), ], }, ids, strings and numbers can only be followed by a div operator. ''' context = prev_state.context curlies, parens = prev_state.curlies, prev_state.parens expect = None # (None, 'regex', 'div') i = 0 # Special case for the start of a *file* # if not context: # i = g.skip_ws(s, i) # m = self.start_pattern.match(s, i) # if m: # i += len(m.group(0)) # if g.match(s, i, '/'): # i = self.skip_regex(s, i) while i < len(s): assert expect is None, expect progress = i ch, s2 = s[i], s[i:i + 2] if context == '/*': if s2 == '*/': i += 2 context = '' expect = 'div' else: i += 1 # Eat the next comment char. elif context: assert context in ('"', "'", '`'), repr(context) # #651: support back tick if ch == '\\': i += 2 elif context == ch: i += 1 context = '' # End the string. expect = 'regex' else: i += 1 # Eat the string character. elif s2 == '//': break # The single-line comment ends the line. elif s2 == '/*': # Start a comment. i += 2 context = '/*' elif ch in ( '"', "'", '`', ): # #651: support back tick # Start a string. i += 1 context = ch elif ch in '_$' or ch.isalpha(): # An identifier. Only *approximately* correct. # http://stackoverflow.com/questions/1661197/ i += 1 while i < len(s) and (s[i] in '_$' or s[i].isalnum()): i += 1 expect = 'div' elif ch.isdigit(): i += 1 # Only *approximately* correct. while i < len(s) and (s[i] in '.+-e' or s[i].isdigit()): i += 1 # This should work even if the scan ends with '+' or '-' expect = 'div' elif ch in '?:': i += 1 expect = 'regex' elif ch in ';,': i += 1 expect = 'regex' elif ch == '\\': i += 2 elif ch == '{': i += 1 curlies += 1 expect = 'regex' elif ch == '}': i += 1 curlies -= 1 expect = 'div' elif ch == '(': i += 1 parens += 1 expect = 'regex' elif ch == ')': i += 1 parens -= 1 expect = 'div' elif ch == '[': i += 1 expect = 'regex' elif ch == ']': i += 1 expect = 'div' else: m = self.op_pattern.match(s, i) if m: i += len(m.group(0)) expect = 'regex' elif ch == '/': g.trace('no lookahead for "/"', repr(s)) assert False, i else: i += 1 expect = None # Look for a '/' in the expected context. if expect: assert not context, repr(context) i = g.skip_ws(s, i) # Careful // is the comment operator. if g.match(s, i, '//'): break elif g.match(s, i, '/'): if expect == 'div': i += 1 else: assert expect == 'regex', repr(expect) i = self.skip_regex(s, i) expect = None assert progress < i d = {'context': context, 'curlies': curlies, 'parens': parens} state = JS_ScanState(d) return state
def skipString(self, s, i): if g.match(s, i, '"') or g.match(s, i, "'"): return g.skip_string(s, i) else: return g.skip_heredoc_string(s, i)
def deleteComments(self, event=None): #@+<< deleteComments docstring >> #@+node:ekr.20171123135625.37: *3* << deleteComments docstring >> #@@pagewidth 50 """ Removes one level of comment delimiters from all selected lines. The applicable @language directive determines the comment delimiters to be removed. Removes single-line comments if possible; removes block comments for languages like html that lack single-line comments. *See also*: add-comments. """ #@-<< deleteComments docstring >> c, p, u, w = self, self.p, self.undoer, self.frame.body.wrapper # # "Before" snapshot. bunch = u.beforeChangeBody(p) # # Initial data. head, lines, tail, oldSel, oldYview = self.getBodyLines() if not lines: g.warning('no text selected') return # The default language in effect at p. language = c.frame.body.colorizer.scanLanguageDirectives(p) if c.hasAmbiguousLanguage(p): language = c.getLanguageAtCursor(p, language) d1, d2, d3 = g.set_delims_from_language(language) # # Calculate the result. changed, result = False, [] if d1: # Remove the single-line comment delim in front of each line d1b = d1 + ' ' n1, n1b = len(d1), len(d1b) for s in lines: i = g.skip_ws(s, 0) if g.match(s, i, d1b): result.append(s[:i] + s[i + n1b:]) changed = True elif g.match(s, i, d1): result.append(s[:i] + s[i + n1:]) changed = True else: result.append(s) else: # Remove the block comment delimiters from each line. n2, n3 = len(d2), len(d3) for s in lines: i = g.skip_ws(s, 0) j = s.find(d3, i + n2) if g.match(s, i, d2) and j > -1: first = i + n2 if g.match(s, first, ' '): first += 1 last = j if g.match(s, last - 1, ' '): last -= 1 result.append(s[:i] + s[first:last] + s[j + n3:]) changed = True else: result.append(s) if not changed: return # # Set p.b and w's text first. middle = ''.join(result) p.b = head + middle + tail # Sets dirty and changed bits. w.setAllText(head + middle + tail) # # Set the selection range and scroll position. i = len(head) j = ins = max(i, len(head) + len(middle) - 1) w.setSelectionRange(i, j, insert=ins) w.setYScrollPosition(oldYview) # # "after" snapshot. u.afterChangeBody(p, 'Indent Region', bunch)
def skip_block_comment(self, s, i): assert g.match(s, i, "/*") j = s.find("*/", i) if j == -1: return len(s) return j + 2
def OpenProcess(p): global RunNode,WorkDir global In,OutThread,ErrThread,ExitCode command = p.h[4:].strip() # Remove @run if not command: return #@+<< set the working directory or return >> #@+node:ekr.20040910094754: *3* << set the working directory or return >> args = command.split(' ') path,fname = os.path.split(args[0]) if g.match(fname,0,'#'): return if path: if os.access(path,os.F_OK) == 1: WorkDir=os.getcwd() os.chdir(path) else: g.error("@run: invalid path: %s" % (path)) return #@-<< set the working directory or return >> #@+<< set the command, removing all args following '#' >> #@+node:ekr.20040910100935: *3* << set the command, removing all args following '#' >> command = fname for arg in args[1:]: if g.match(arg,0,'#'): break else: command += ' ' + arg.strip() #@-<< set the command, removing all args following '#' >> if not command.strip(): return RunNode=p args = [] #@+<< append arguments from child nodes to command >> #@+node:ekr.20040910095147: *3* << append arguments from child nodes to command >> for child in p.children(): h = child.h if g.match_word(h,0,"@arg"): arg = h[4:].strip() args.append(arg) else: if ( not g.match_word(h,0,"@run") and not g.match_word(h,0,"@in") and not g.match_word(h,0,"@input") ): args.append(child.b.strip()) #@-<< append arguments from child nodes to command >> g.blue("@run %s>%s" % (os.getcwd(),command)) for arg in args: g.blue("@arg %s" % arg) command += ' ' + ' '.join(args) # Start the threads and open the pipe. OutThread = readingThread() ErrThread = readingThread() # In,OutThread.File,ErrThread.File = os.popen3(command,"t") # OutThread.File,In,ErrThread.File = os.popen3(command,"t") # PIPE = subprocess.PIPE proc = subprocess.Popen(command, shell=True) # bufsize=bufsize, # stdin=PIPE, # stdout=PIPE, # stderr=PIPE) ,close_fds=True) In = proc.stdin OutThread.File = proc.stdout ErrThread.File = proc.stderr OutThread.start() ErrThread.start() # Mark and select the node. RunNode.setMarked() c = RunNode.v.context c.selectPosition(RunNode) if os.name in ("nt","dos"): c.redraw()
def startsString(self, s, i): return g.match(s, i, '"') or g.match(s, i, "'") or g.match(s, i, '<<<')
def isSentinel(self, s, sentinelComment): i = g.skip_ws(s, 0) return g.match(s, i, sentinelComment)