Ejemplo n.º 1
0
 def electricBackspace(self, stc):
     """Delete all whitespace before the cursor unless in a string or comment
     """
     start, end = stc.GetSelection()
     if start != end:
         stc.ReplaceSelection("")
         return
     pos = stc.GetCurrentPos()
     if pos <= 0:
         return
     s = stc.GetStyleAt(pos - 1)
     if stc.isStyleComment(s) or stc.isStyleString(s):
         stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK)
     else:
         self.dprint("backspace from pos %d" % pos)
         start = pos
         while start > 0:
             c = stc.GetCharAt(start - 1)
             if c == ord(' ') or c == ord('\t') or c == 10 or c == 13:
                 start -= 1
             else:
                 break
         if start < pos:
             stc.SetTargetStart(start)
             stc.SetTargetEnd(pos)
             stc.ReplaceTarget('')
         else:
             stc.CmdKeyExecute(wx.stc.STC_CMD_DELETEBACK)
Ejemplo n.º 2
0
 def getLastNonWhitespaceChar(self, stc, pos):
     """Working backward, find the closest non-whitespace character
     
     @param stc: stc of interest
     @param pos: boundary position from which to start looking backwards
     @return: tuple of the matching char and the position
     """
     found = ''
     skip = self.getNonCodeStyles(stc)
     while pos > 0:
         check = pos - 1
         c = unichr(stc.GetCharAt(check))
         s = stc.GetStyleAt(check)
         #dprint("check=%d char='%s'" % (check, c))
         
         # Comment or string terminates the search and will return with the
         # character after the last comment/string char.
         if s in skip:
             break
         
         found = c
         pos = check
         if not c.isspace():
             break
     return (found, pos)
Ejemplo n.º 3
0
 def electricDelete(self, stc):
     """Delete all whitespace after the cursor unless in a string or comment
     """
     start, end = stc.GetSelection()
     if start != end:
         stc.ReplaceSelection("")
         return
     pos = stc.GetCurrentPos()
     s = stc.GetStyleAt(pos)
     if stc.isStyleComment(s) or stc.isStyleString(s):
         stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR)
     else:
         self.dprint("deleting from pos %d" % pos)
         end = pos
         while end < stc.GetLength():
             c = stc.GetCharAt(end)
             if c == ord(' ') or c == ord('\t') or c == 10 or c == 13:
                 end += 1
             else:
                 break
         if end > pos:
             stc.SetTargetStart(pos)
             stc.SetTargetEnd(end)
             stc.ReplaceTarget('')
         else:
             stc.CmdKeyExecute(wx.stc.STC_CMD_CLEAR)
Ejemplo n.º 4
0
def is_char_in_string(stc, pos):
    """Return True if the position is within a string"""
    style = stc.GetStyleAt(pos)
    #dprint("style %d at pos %d" % (style, pos))
    if style == 3 or style == 7 or style == 6 or style == 4:
        return True
    return False
Ejemplo n.º 5
0
 def electricChar(self, stc, uchar):
     """Reindent the line and insert a newline when special chars are typed.
     
     Like emacs, a semicolon or curly brace causes the line to be reindented
     and the next line to be indented to the correct column.
     
     @param stc: stc instance
     
     @param uchar: unicode character that was just typed by the user (note
     that it hasn't been inserted into the document yet.)
     
     @return: True if this method handled the character and the text
     was modified; False if the calling event handler should handle the
     character.
     """
     implicit_return = True
     
     if uchar == u';' or uchar == u':' or uchar == '{' or uchar == '}':
         pos = stc.GetCurrentPos()
         s = stc.GetStyleAt(pos)
         if not stc.isStyleComment(s) and not stc.isStyleString(s):
             if uchar == u':':
                 # FIXME: currently only process the : if the current
                 # line is a case statement.  Emacs also indents labels
                 # and namespace operators with a :: by checking if the
                 # last character on the previous line is a : and if so
                 # collapses the current line with the previous line and
                 # reindents the new line
                 linenum = stc.GetCurrentLine()
                 line = self.getCodeChars(stc, linenum, pos) + ":"
                 if not self.reCase.match(line) and not self.reClassAttrScope.match(line):
                     c, prev = self.getLastNonWhitespaceChar(stc, pos)
                     if c == u':':
                         # Found previous ':', so make it a double colon
                         stc.SetSelection(prev + 1, pos)
                         #dprint("selection: %d - %d" % (prev + 1, pos))
                     implicit_return = False
             elif uchar == u';':
                 # Don't process the semicolon if we're in the middle of an
                 # open statement
                 if self.isInsideStatement(stc, pos):
                     return False
             stc.BeginUndoAction()
             start, end = stc.GetSelection()
             if start == end:
                 stc.AddText(uchar)
             else:
                 stc.ReplaceSelection(uchar)
             
             # Always reindent the line, but only process a return if needed
             self.processTab(stc)
             if implicit_return:
                 self.processReturn(stc)
             
             stc.EndUndoAction()
             return True
     return False
Ejemplo n.º 6
0
 def reindentLine(self, stc, linenum=None, dedent_only=False):
     if linenum is None:
         linenum = stc.GetCurrentLine()
     
     pos = stc.GetCurrentPos()
     s = stc.GetStyleAt(pos)
     if stc.isStyleComment(s):
         return self.reindentComment(stc, linenum, dedent_only)
     else:
         return self.reindentSourceLine(stc, linenum, dedent_only)
Ejemplo n.º 7
0
 def electricChar(self, stc, uchar):
     """Reindent the line and insert a newline when special chars are typed.
     
     For python mode, a colon should reindent the line (for example, after
     an else statement, it should dedent it one level)
     """
     if uchar == u':':
         pos = stc.GetCurrentPos()
         s = stc.GetStyleAt(pos)
         if stc.isStyleComment(s) or stc.isStyleString(s):
             # These will report true if the cursor is before the first
             # character of a comment or string, meaning that:
             #
             #    else#blah
             #
             # where the cursor is between the 'e' and the '#' will fail.
             # So, we have to check for this as a special case and allow it
             if pos > 0:
                 s = stc.GetStyleAt(pos - 1)
                 allow = not stc.isStyleComment(
                     s) and not stc.isStyleString(s)
             else:
                 allow = False
         else:
             allow = True
         if allow:
             stc.BeginUndoAction()
             start, end = stc.GetSelection()
             if start == end:
                 stc.AddText(uchar)
             else:
                 stc.ReplaceSelection(uchar)
             self.reindentLine(stc, dedent_only=True)
             stc.EndUndoAction()
             return True
     return False
Ejemplo n.º 8
0
    def findIndent(self, stc, linenum):
        """Reindent the specified line to the correct level.

        Given a line, use some regex matching to determine the correct indent
        level.
        """
        # The text begins at indpos; check some special cases to see if there
        # should be a dedent
        before = stc.GetLineIndentPosition(linenum)
        end = stc.GetLineEndPosition(linenum)
        cmd = stc.GetTextRange(before, end)
        dprint(cmd)

        # skip blank lines
        if len(cmd) > 0:
            # don't reindent comments
            if cmd[0] == "#": return stc.GetLineIndentation(linenum)

            match = self.commandre.match(cmd)
            if match:
                # it's a command, so it shouldn't be indented.
                self.dprint("It's a command!  indent=0")
                return 0

        # OK, not a command, so it depends on context.
        while linenum > 0:
            linenum -= 1
            start = stc.PositionFromLine(linenum)
            style = stc.GetStyleAt(start)
            self.dprint("line=%d start=%d style=%d" % (linenum, start, style))
            if style == 5 or style == 3:
                # OK, it's a possible command.
                end = stc.GetLineEndPosition(linenum)
                cmd = stc.GetTextRange(start, end)
                match = self.commandre.match(cmd)
                if match:
                    # Yep, a command.  This line should be tabbed
                    self.dprint("tabbed!")
                    return 8
                return 0
            elif style == 3:
                return 0

        # If all else fails, just use the same amount of indentation as before
        return stc.GetLineIndentation(linenum)
Ejemplo n.º 9
0
 def electricChar(self, stc, uchar):
     i = ord(uchar)
     if i >= ord('0') and i <= ord('9'):
         pos = stc.GetCurrentPos()
         s = stc.GetStyleAt(pos)
         col = stc.GetColumn(pos)
         if not stc.isStyleComment(s) and col < 5:
             ln = stc.LineFromPosition(pos)
             fc = stc.PositionFromLine(ln)
             lc = stc.GetLineEndPosition(ln)
             text = stc.GetTextRange(fc, lc)
             #dprint("pos=%d col=%d ln=%d fc=%d lc=%d len=%d" % (pos, col, ln, fc, lc, len(text)))
             #dprint("1234567")
             #dprint(text)
             if len(text) > 5:
                 numbers = text[0:5]
                 continuation = text[5]
                 remainder = text[6:]
             else:
                 numbers = text + " "*(5 - len(text))
                 continuation = " "
                 remainder = ""
             numbers = numbers[0:col] + uchar + numbers[col:]
             before = len(numbers)
             numbers = numbers.lstrip()
             if before > len(numbers):
                 col -= before - len(numbers)
             numbers = numbers.strip()
             #dprint("pos=%d col=%d ln=%d fc=%d lc=%d len=%d" % (pos, col, ln, fc, lc, len(text)))
             line = "%-5s%s%s" % (numbers, continuation, remainder)
             #dprint("1234567")
             #dprint(line)
             stc.SetTargetStart(fc)
             stc.SetTargetEnd(lc)
             stc.ReplaceTarget(line)
             stc.GotoPos(fc + col + 1)
             return True
     return False
Ejemplo n.º 10
0
    def findIndent(self, stc, linenum):
        ln = linenum
        pos = 0

        fc = stc.PositionFromLine(ln)
        s = stc.GetStyleAt(fc)
        if stc.isStyleComment(s):
            # comment lines aren't indented at all in F77
            return pos

        # find the first line with content that is not starting with comment
        # text, and take the base indent position from that
        above = ''
        while ln > 0:
            ln -= 1
            fc = stc.PositionFromLine(ln)
            s = stc.GetStyleAt(fc)
            if stc.isStyleComment(s):
                continue
            lc = stc.GetLineEndPosition(ln)
            text = stc.GetTextRange(fc, lc)
            if len(text) < 6:
                # skip lines that have only labels
                continue
            if text[5] != ' ':
                # skip continuation lines
                continue
            above = text[6:]
            pos = 6
            while pos < len(text) and text[pos] == ' ':
                pos += 1
            break

        # try 'couples' for an opening on the above line first.  since we only
        # adjust by 1 unit, we only need 1 match.
        adjustment = 0
        if '(' in self.couples and self.coupleBalance(stc, ln, '(', ')') > 0:
            adjustment += 1

        # check if we should indent, unless the line starts with comment text,
        # or the match is in comment text Here we look at the line above the
        # current line
        if self.reIndentAfter:
            match = self.reIndentAfter.search(above)
            self.dprint(above)
            if match:
                self.dprint("reIndentAfter: found %s at %d" % (match.group(0), match.start(0)))
                adjustment += 1

        # Now that we've found the base indent based on the lines above the
        # current line, check if the current line needs to be modified
        fc = stc.PositionFromLine(linenum)
        lc = stc.GetLineEndPosition(linenum)
        text = stc.GetTextRange(fc, lc)
        if len(text) > 6:
            # Note that no lines that I know of automatically indent
            # themselves, so we only check for lines that should unindent
            # themselves like ELSE, ENDIF, etc.
            text = text[6:]
            if self.reUnindent:
                match = self.reUnindent.search(text)
                if match:
                    self.dprint("reUnndent: found %s at %d" % (match.group(0), match.start(0)))
                    adjustment -= 1
        
        # Find the actual number of spaces to indent
        if adjustment > 0:
            pos += stc.GetIndent()
        elif adjustment < 0:
            pos -= stc.GetIndent()
        if pos < 0:
            pos = 0
        
        return pos
Ejemplo n.º 11
0
    def findIndent(self, stc, linenum):
        """Determine the correct indentation for the line.
        
        This routine uses regular expressions to determine the indentation
        level of the line.
        
        @param linenum: current line number
        
        @param return: the number of columns to indent, or None to leave as-is
        """
        if linenum < 1:
            return None

        #// find the first line with content that is not starting with comment text,
        #// and take the position from that
        ln = linenum
        pos = 0
        above = ''
        while ln > 0:
            ln -= 1
            fc = stc.GetLineIndentPosition(ln)
            lc = stc.GetLineEndPosition(ln)
            self.dprint("ln=%d fc=%d lc=%d line=-->%s<--" % (ln, fc, lc, stc.GetLine(ln)))
            # skip blank lines
            if fc < lc:
                s = stc.GetStyleAt(fc)
                if stc.isStyleComment(s):
                    continue
                pos = stc.GetLineIndentation(ln)
                above = stc.GetTextRange(fc, lc)
                break

        #  // try 'couples' for an opening on the above line first. since we only adjust by 1 unit,
        #  // we only need 1 match.
        adjustment = 0
        if '(' in self.couples and self.coupleBalance(stc, ln, '(', ')') > 0:
            adjustment += 1
        elif '[' in self.couples and self.coupleBalance(stc, ln, '[', ']') > 0:
            adjustment += 1
        elif '{' in self.couples and self.coupleBalance(stc, ln, '{', '}') > 0:
            adjustment += 1
        
        #  // Try 'couples' for a closing on this line first. since we only adjust by 1 unit,
        #  // we only need 1 match. For unindenting, we look for a closing character
        #  // *at the beginning of the line*
        #  // NOTE Assume that a closing brace with the configured attribute on the start
        #  // of the line is closing.
        #  // When acting on processChar, the character isn't highlighted. So I could
        #  // either not check, assuming that the first char *is* meant to close, or do a
        #  // match test if the attrib is 0. How ever, doing that is
        #  // a potentially huge job, if the match is several hundred lines away.
        #  // Currently, the check is done.
        #  {
        #    KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() );
        #    int i = tl->firstChar();
        #    if ( i > -1 )
        #    {
        #      QChar ch = tl->getChar( i );
        #      uchar at = tl->attribute( i );
        #      kdDebug(13030)<<"attrib is "<<at<<endl;
        #      if ( d->couples & Parens && ch == ')'
        #           && ( at == d->coupleAttrib
        #                || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
        #              )
        #         )
        #        adjustment--;
        #      else if ( d->couples & Braces && ch == '}'
        #                && ( at == d->coupleAttrib
        #                     || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
        #                   )
        #              )
        #        adjustment--;
        #      else if ( d->couples & Brackets && ch == ']'
        #                && ( at == d->coupleAttrib
        #                     || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) ))
        #                   )
        #              )
        #        adjustment--;
        #    }
        #  }
        
        # Haven't figured out what that was for, so ignoring for now.
        
        
        #  // check if we should indent, unless the line starts with comment text,
        #  // or the match is in comment text
        # Here we look at the line above the current line
        if self.reIndentAfter:
            match = self.reIndentAfter.search(above)
            self.dprint(above)
            if match:
                self.dprint("reIndentAfter: found %s at %d" % (match.group(0), match.start(0)))
                adjustment += 1

        #  // else, check if this line should indent unless ...
        #  ktl = doc->plainKateTextLine( line.line() );
        #  if ( ! d->reIndent.isEmpty()
        #         && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1
        #         && ! ISCOMMENT )
        #    adjustment++;
        fc = stc.GetLineIndentPosition(linenum)
        lc = stc.GetLineEndPosition(linenum)
        s = stc.GetStyleAt(fc)
        if self.reIndent and lc > fc and not stc.isStyleComment(s):
            text = stc.GetTextRange(fc, lc)
            match = self.reIndent.search(text)
            self.dprint(text)
            if match:
                self.dprint("reIndent: found %s at %d" % (match.group(0), match.start(0)))
                adjustment += 1
        
        #  // else, check if the current line indicates if we should remove indentation unless ...
        if self.reUnindent and lc > fc and not stc.isStyleComment(s):
            text = stc.GetTextRange(fc, lc)
            match = self.reUnindent.search(text)
            if match:
                self.dprint("reUnndent: found %s at %d" % (match.group(0), match.start(0)))
                adjustment -= 1
        
        # Find the actual number of spaces to indent
        if adjustment > 0:
            pos += stc.GetIndent()
        elif adjustment < 0:
            pos -= stc.GetIndent()
        if pos < 0:
            pos = 0
        
        return pos
Ejemplo n.º 12
0
    def findIndent(self, stc, linenum=None):
        """Reindent the specified line to the correct level.

        Given a line, use Scintilla's built-in folding to determine
        the indention level of the current line.
        """
        if linenum is None:
            linenum = stc.GetCurrentLine()
        linestart = stc.PositionFromLine(linenum)

        # actual indention of current line
        col = stc.GetLineIndentation(linenum) # columns
        pos = stc.GetLineIndentPosition(linenum) # absolute character position

        # folding says this should be the current indention
        fold = (stc.GetFoldLevel(linenum)&wx.stc.STC_FOLDLEVELNUMBERMASK) - wx.stc.STC_FOLDLEVELBASE
        c = stc.GetCharAt(pos)
        s = stc.GetStyleAt(pos)
        indent = stc.GetIndent()
        partial = 0
        self.dprint("col=%d (pos=%d), fold=%d char=%s" % (col, pos, fold, repr(chr(c))))
        if c == ord('}'):
            # Scintilla doesn't automatically dedent the closing brace, so we
            # force that here.
            fold -= 1
        elif c == ord('{'):
            # Opening brace on a line by itself always stays at the fold level
            pass
        elif c == ord('#') and s == 9:
            # Force preprocessor directives to start at column zero
            fold = 0
        else:
            start = self.getFoldSectionStart(stc, linenum)
            opener = self.getBraceOpener(stc, start-1)
            self.dprint(opener)
            
            # First, try to match on the current line to see if we know enough
            # about it to figure its indent level
            matched = False
            line = self.getCodeChars(stc, linenum)
            if opener == "switch":
                # case statements are partially dedented relative to the
                # scintilla level
                if self.reCase.match(line):
                    matched = True
                    partial = - (indent / 2)
            elif opener == "class":
                # public/private/protected statements are partially dedented
                # relative to the scintilla level
                if self.reClassAttrScope.match(line):
                    matched = True
                    partial = - (indent / 2)
            
            # labels are matched after case statements to prevent the
            # 'default:' label from getting confused with a regular label
            if not matched and self.reLabel.match(line):
                fold = 0
                matched = True
            
            # If we can't determine the indent level when only looking at
            # the current line, start backing up to find the first non blank
            # statement above the line.  We then look to see if we should
            # indent relative to that statement (e.g.  if the statement is a
            # continuation) or relative to the fold level supplied by scintilla
            if not matched:
                for ln in xrange(linenum - 1, start - 1, -1):
                    line = self.getCodeChars(stc, ln)
                    self.dprint(line)
                    if not line.strip() or self.reLabel.match(line):
                        continue
                    if opener == "switch":
                        if self.reCase.match(line):
                            # a case statement will be interpreted as a continuation
                            break
                    if self.reIndentAfter.match(line):
                        self.dprint("continuation")
                        fold += 1
                    else:
                        self.dprint("terminated statement")
                    break

        return (fold * indent) + partial
Ejemplo n.º 13
0
    def findIndent(self, stc, linenum):
        """Find proper indentation of the current Python source line.
        
        This uses IDLE's python parsing routine to find the indent level of the
        specified line based on the python code that comes before it.
        
        @param linenum: line number
        @return: integer indicating number of columns to indent.
        """
        indentwidth = stc.GetIndent()
        tabwidth = stc.GetTabWidth()
        indent = stc.GetLineIndentation(linenum)
        y = PyParse.Parser(indentwidth, tabwidth)
        # FIXME: context line hack straight from IDLE
        for context in [50, 500, 5000000]:
            firstline = linenum - context
            if firstline < 0:
                firstline = 0
            start = stc.PositionFromLine(firstline)

            # end is the position before the first character of the line, so
            # we're looking at the code up to the start of the current line.
            end = stc.PositionFromLine(linenum)
            rawtext = stc.GetTextRange(start, end)

            # Handle two issues with this loop.  1: Remove comments that start
            # at column zero so they don't affect the indenting.  2: PyParse
            # is hardcoded for "\n" style newlines only, so by splitting the
            # lines here we can change whatever the newlines are into "\n"
            # characters.
            lines = []
            for line in rawtext.splitlines():
                if len(line) > 0:
                    if line[0] != '#':
                        lines.append(line)
            lines.append('')  # include a blank line at the end
            rawtext = "\n".join(lines)
            y.set_str(rawtext)

            bod = y.find_good_parse_start(build_char_in_string_func(
                stc, start))
            if bod is not None or firstline == 0:
                break
        #dprint(rawtext)
        self.dprint("bod = %s" % bod)
        y.set_lo(bod or 0)

        c = y.get_continuation_type()
        self.dprint("continuation type: %s" % c)
        extra_data = None
        if c != PyParse.C_NONE:
            # The current stmt hasn't ended yet.
            if c == PyParse.C_STRING_FIRST_LINE:
                s = stc.GetStyleAt(end)
                if s == 6 or s == 7:
                    # Inside a triple quoted string (TQS)
                    self.dprint("C_STRING_FIRST_LINE in TQS")
                    indentstr = y.get_base_indent_string()
                    indent = len(indentstr.expandtabs(tabwidth))
                else:
                    # after the first line of a string; do not indent at all
                    self.dprint("C_STRING_FIRST_LINE")
                pass
            elif c == PyParse.C_STRING_NEXT_LINES:
                # inside a string which started before this line;
                # just mimic the current indent
                #text.insert("insert", indent)
                s = stc.GetStyleAt(end)
                if s == 6 or s == 7:
                    # Inside a triple quoted string (TQS)
                    self.dprint("C_STRING_NEXT_LINES in TQS")
                    indentstr = y.get_base_indent_string()
                    indent = len(indentstr.expandtabs(tabwidth))
                else:
                    # FIXME: Does this ever happen without being in a TQS???
                    self.dprint("C_STRING_NEXT_LINES")
            elif c == PyParse.C_BRACKET:
                # line up with the first (if any) element of the
                # last open bracket structure; else indent one
                # level beyond the indent of the line with the
                # last open bracket
                self.dprint("C_BRACKET")
                #self.reindent_to(y.compute_bracket_indent())
                indent = y.compute_bracket_indent()
            elif c == PyParse.C_BACKSLASH:
                # if more than one line in this stmt already, just
                # mimic the current indent; else if initial line
                # has a start on an assignment stmt, indent to
                # beyond leftmost =; else to beyond first chunk of
                # non-whitespace on initial line
                if y.get_num_lines_in_stmt() > 1:
                    pass
                else:
                    indent = y.compute_backslash_indent()
            else:
                assert 0, "bogus continuation type %r" % (c, )

        else:
            # This line starts a brand new stmt; indent relative to
            # indentation of initial line of closest preceding
            # interesting stmt.
            indentstr = y.get_base_indent_string()
            indent = len(indentstr.expandtabs(tabwidth))

            if y.is_block_opener():
                self.dprint("block opener")
                indent += indentwidth
                extra_data = "block opener"
            elif indent and y.is_block_closer():
                self.dprint("block dedent")
                indent = ((indent - 1) // indentwidth) * indentwidth
                extra_data = "block dedent"
        self.dprint("indent = %d" % indent)

        # check some special cases to see if they line should be dedented by
        # a level
        before = stc.GetLineIndentPosition(linenum)
        style = stc.GetStyleAt(before)
        end = stc.GetLineEndPosition(linenum)
        cmd = stc.GetTextRange(before, end)
        #dprint("checking %s" % cmd)
        if linenum > 0 and style == wx.stc.STC_P_WORD and (
                cmd.startswith('else') or cmd.startswith('elif')
                or cmd.startswith('except') or cmd.startswith('finally')):
            self.dprint("Found a dedent: %s" % cmd)
            if extra_data != "block dedent":
                # If we aren't right after a return or something that already
                # caused a dedent, dedent it
                indent -= indentwidth
        return indent