The python.el mode has several ways to fill docstrings, but I would like to fill single quoted lines. Is there a function that would change this:

variable = {
    ' this is a very long long long long long long long long long long long long long long long string'}

into this:

variable = {
    ' this is a very long long long long long long long long long long '
    'long long long long long string'}

I do not want to use triple quoted strings, because they add newlines I don't want.

  • Just for those who may suggest the same thing (this was my first impulse to try it): M-q inside long strings does it wrong (it inserts linebreak and doesn't even indent the string on the next line). – wvxvw Nov 28 '17 at 8:39
  • Looking further into this issue, it seems like python-fill-string automatically assumes that it is dealing with docstrings, and it isn't the right tool to accomplish this. – wvxvw Nov 28 '17 at 9:02
  • thanks for looking, in the mean time, I'm trying to write a python script to do this, will share if I manage. – Rosanne Nov 28 '17 at 9:16

Here's not a complete solution, but will work for many cases:

(defun wvxvw/python-wrap-string ()
    (goto-char (1- (previous-property-change (point))))
    (let ((chop (- fill-column 2 (current-column)))
          (start (point))
          (qmark (buffer-substring-no-properties (point) (1+ (point))))
          (end (1+ (next-property-change (1+ (point)))))
          (padding (make-string (current-column) ?\ )))
      (when (< chop 1) (setf chop 1))
      (let* ((str (buffer-substring-no-properties (1+ start) (1- end)))
              (cl-loop for i from 0 upto (length str) by chop
                       collect (substring str i (min (+ i chop) (length str))))))
        (delete-region start end)
        (insert qmark (car chunks) qmark "\n")
        (cl-loop for s on (cdr chunks) do
                 (insert padding qmark (car s) qmark (if (cdr s) "\n" "")))))))

(define-key python-mode-map (kbd "C-c C-q") 'wvxvw/python-wrap-string)

Some problems with this solution include:

  1. This uses SPC unconditionally, while Python mode may have been configured to use TAB for indentation.
  2. Genuine fill-paragraph function in Emacs is more intelligent wrt where it splits the string (eg. it will try to split it on word boundaries).
  3. The rules for inserting backslash in Python are very difficult to follow. It is possible that in some cases the backslashes will be required, but I'll add this once someone gives me an example where they are required.
  • Thanks for looking into this. Because I'm more comfortable with python I solved it with a python script, and because then I wouldn't have to worry about which character to escape. I did upvote your answer, but because I have no rep yet, that doesn't show. – Rosanne Nov 28 '17 at 10:27
  • 1
    @Rosanne not a problem :) Few things about your Python script: '{}'.format(indent) is kind of an eye-sore (it's the same thing as just indent). I'm not sure how you are extracting the text from the source file, but from the example, I assume that you are OK with having to manually select the string + some initial input. This sounds like a lot of work, plus in simple cases like foo = 'long string ...' won't work. Perhaps you want to look at python-indent-calculate-indentation to get a better estimate for indent. – wvxvw Nov 28 '17 at 11:29
  • thanks for the comments. The `.format’ was a copy paste oversight :p, fixed now. You're right about it not working if the string doesn't start on a new line, I'll think about how to fix it. I'd like to use emacs functionality while still keeping the script itself useful for other editors. I'll see how much time I have / how annoyed I get. – Rosanne Nov 28 '17 at 11:56

What I did eventually was write a python script:

#!/usr/bin/env python3
script for autoindenting single quoted strings.
usuage: provide as stdin e.g.:

        " this is ' a very long line that is too long for the pep8 rules and it also contains a single quote, and the initial indent is included in stdin"

this will output:

        " this is ' a very long line that is too long for the pep8 rules and "
        'it also contains a single quote, and the initial indent is included '
        'in stdin'

with the correct linelength and indentation.
import sys
import re
import pprint

def main():
    """ get input from stdin and print the reformatted string """
    original = sys.stdin.read()
    # parse stdin as code so you get a string that python can format
    exec('global executed_string; executed_string = ({})'.format(original))
    # determine initial indent
    indent = re.match('^ +', original)
    if indent:
        indent = indent.group(0)
        indent = ''
    pp = pprint.PrettyPrinter(width=79-len(indent))
    text = pp.pformat(executed_string)
    # remove () that is used to paste the single quoted strings together
    text = re.sub(r'(^\()|(\)$)', '', text)
    # add the proper indentation for the first line
    text = re.sub(r'^', indent), text)
    # add the proper indentation for the subsequent lines (pformat add a space
    # at the beginning of each line)
    text = re.sub(r'\n ', '\n{}'.format(indent), text)
    # remove trailing \n
    text = re.sub(r'\n$', '', text)

if __name__ == '__main__':

And assigned this to an emacs function (http://ergoemacs.org/emacs/elisp_perl_wrapper.html). It may look a bit hacky, but it does what I want it to do. You need to be careful to also select your initial indent in emacs, before you run the script, so it can determine the initial indent. Maybe there's a better way to do that, but for now, this works for me.

Note: because I'm using exec, this might actually give a uge mess if you provide anything other than a string. I might look into doing something about that, but for now, use with care.


Black formatter does this. You can use the blacken.el package. It's available in MELPA.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.