Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adapt to changes in fgallinas python el #41

Merged
merged 13 commits into from

3 participants

@fgeller
  • Fixes to adapt to latest changes of fgallina's python.el.

  • Some improvements for marking Python blocks (using fgallina's python.el).

Please let me know what you think :)

fgeller added some commits
@fgeller fgeller python-nav-sentence-* was renamed to python-nav-statement-*. f432267
@fgeller fgeller Fixes for er/mark-*-python-string.
 - `forward-sexp' is now jumps block-wise

 - Use a (brittle) search for the delimiters at the beginning of the
   string.

 - Factor out common functionality
3fdcb44
@fgeller fgeller Improved marking of Python blocks.
 - Fix failing access to `python-rx' by inlining the regular
   expression.

 - The `er/mark-python-block' function now accepts the next
   indentation level as an optional argument. This can be used to mark
   the surrounding block more reliably.
357cd00
@magnars
Owner

Hi fgeller! Thanks for contributing again. :-)

Would you mind adding a few basic tests for your changes? I'm trying to get a few key tests in place, which is especially important for modes that I don't use.

@fgeller

Not at all -- will take a look tomorrow.

I vaguely remember running into problems with Python strings and how to "delimit" them. There's no example for this (Python specific that is) at the moment, right?

@magnars
Owner

Thanks, that's great. Unfortunately there's no python examples to look at. You can however look at the espuds-project to find step definition implementations - and then add your own python-friendly step definitions.

fgeller added some commits
@fgeller fgeller Fix a problem where an incorrect region would be selected when the
block to be selected had no indentation.

Given

|def x():
    print()

print()

where | is point, er/mark-python-block should mark the region
delimited by brackets:

[def x():
    print()]
bbf9740
@fgeller fgeller Adding tests for expansions in fgallina's python.el.
 - Tests for marking the inner and outer part of a Python string.

 - Tests for marking inside & outside a multi-line Python string.

 - Tests for marking Python blocks.
52ec97a
@fgeller

I ran into one problem while adding tests, perhaps you know a fix: I needed to set the load-path manually in ecukes to include fgallina's python.el. For some reason ecukes seems to ignore the settings in my init.el. Do you know of a cleaner way, rather than editing the ecukes script?

@magnars
Owner

Ah yes, it does ignore your init, because it's running a clean emacs. We actually need to bundle the dependency to get reproducible tests.

Add a vendor directory to /features/support/ and add fgallina's python.el to it. Then edit env.el to add that directory to the load-path, and require it there.

Make sense?

fgeller added some commits
@fgeller fgeller Adding fgallina's python.el as a submodule and to the test environmen…
…t load-path.
cc9f537
@fgeller fgeller Use key press, rather than the silent call to `er/expand-region' 917e318
@fgeller fgeller Add some text that should not be selected to make this test more inte…
…resting.
5608028
@fgeller fgeller More robust marking of Python strings.
 - Use the ppss to find the end of a string, works for strings that
   contain escaped delimiters.

 - Test for marking a string that contains an escaped delimiter.
c44b1f8
@fgeller fgeller Add docstrings and update documentary intro comment. b16e686
@fgeller fgeller Update to latest version of fgallina's python.el
 - Adapt to changes in names, and augment test.
1c3d599
@fgeller fgeller Include check for end of buffer in `er/mark-python-block'.
 - Add a test for marking a buffer with a nested one.

 - Be more clear about position of point while looping.
b400719
@fgeller fgeller Only proceed to next line when not looking at the start of a block and
the current indent is deeper than the target block's indent. Fixes
issue with selecting a nested block with trailing statements.

Adds test for marking a nested block with trailing statements in the
outer block.
e643a26
@magnars
Owner

Ooh, lots of changes here. And tests! Excellent stuff, thank you. Is this ready for a merge?

@fgeller
@magnars magnars merged commit a182112 into magnars:master
@magnars
Owner

Very nice, thanks :-)

@magnars
Owner

I'm getting 5 failing tests. Any idea why that is?

  Scenario: Mark a basic Python block
    Given I turn on python-mode
    And there is no region selected
    When I insert:
    And I go to point "1"
er/mark-word
    And I press "C-@"
er/mark-python-statement
    And I press "C-@"
python-mark-block
    And I press "C-@"
    Then the region should be:
      Expected the region to be 'if True:
    print('To be, or not to be...')', but was 'if True:
    print('To be, or not to be...')
'.

  Scenario: Mark a Python block with a nested block
    Given I turn on python-mode
    And there is no region selected
    When I insert:
    And I go to point "1"
er/mark-word
    And I press "C-@"
    Then the region should be:
er/mark-python-statement
    And I press "C-@"
    Then the region should be:
python-mark-block
    And I press "C-@"
    Then the region should be:
      Expected the region to be 'if True:
    if True:
        print(23)
    print('To be, or not to be...')', but was 'if True:
    if True:
        print(23)
    print('To be, or not to be...')
'.

  Scenario: Mark another Python block with a nested block
    Given I turn on python-mode
    And there is no region selected
    When I insert:
    And I go to point "1"
er/mark-word
    And I press "C-@"
er/mark-python-statement
    And I press "C-@"
    And I press "C-@"
    Then the region should be:
      Expected the region to be 'def moo(data):
    for foo in data.items():
        print(foo)', but was 'def moo(data):
    for foo in data.items():
        print(foo)
'.

  Scenario: Mark an outer Python block
    Given I turn on python-mode
    And there is no region selected
    When I insert:
    And I go to point "42"
er/mark-word
    And I press "C-@"
    Then the region should be:
er/mark-python-statement
    And I press "C-@"
    Then the region should be:
python-mark-block
    And I press "C-@"
    Then the region should be:
      Expected the region to be 'if True:
        print('To be, or not to be...')', but was 'def the_truth():
    if True:
        print('To be, or not to be...')
    else:
        print('Booyah.')
'.
    And I press "C-@"
    Then the region should be:

  Scenario: Mark nested Python block with subsequent statements in outer block
    Given I turn on python-mode
    And there is no region selected
    When I insert:
    And I go to point "23"
er/mark-word
    And I press "C-@"
    Then the region should be:
er/mark-python-statement
    And I press "C-@"
    Then the region should be:
    And I press "C-@"
    Then the region should be:
      Expected the region to be 'def inner_foo():
        return 23', but was 'def outer_foo():

    def inner_foo():
        return 23
    return inner_foo()
@fgeller

It seems that the wrong expansions are loaded: The tests use python-mark-block for you, which is part of python-el-expansions.el and I'm using er/mark-python-block in for fgallina's python.el.

Don't really know why this happens at the moment, but I saw that you merged the pull request so I'll try to pull that and try on master :)

@magnars
Owner

Thanks for looking into it. This is just off the top of my head, but maybe need to add the python modes to vendor, and load the right ones?

@fgeller

Don't think so (at least not at the moment, as we only have tests for fgallina's mode, right?). As far as I can tell, fgallina's mode is added in env.el to the load-path so that it overrides the one shipped with Emacs, or?

@magnars
Owner

That explains it. My vendor/python-el directory was empty. Doing a git submodule update --init fixed the issue. Thanks!

@fgeller

Glad it worked out :)

@magnars
Owner

Hi mate,

I just added expand-region to travis-ci for continuous integration. Unfortunately I had to disable the tests for fgallina expansions, since the newest fgallina uses syntax-propertize-rules from Emacs 24, and expand-region still supports Emacs 23. I don't know how to run tests only in certain environments - and doing integration testing vs an older fgallina seemed weird too.

If you have any ideas how to mend this, please let me know. Otherwise they will have to be disabled until expand-region drops support for Emacs 23. :-/

@fgeller

Hey,

cool, that you are pushing the automation of tests :)

Would it be possible to copy the macro into the env and define it if it isn't bound, or does the macro come with too many dependencies? ... Only other idea I have currently. The maintainer fgallina doesn't seem to have enough time to maintain the emacs23 branch of the mode, that would have been another option.

@magnars
Owner

Hey, thanks for the feedback :-)

I tried including stuff from Emacs 24, and got the tests running after including syntax-propertize-rules and syntax-propertize--shift-groups from syntax.el, and also prog-mode from simple.el.

I am uncertain about this, but my feeling is that including so much of emacs 24 to get the tests to run on 23 is not a proper way of handling integration tests. I would think the best course of action was to find a way of only running the fgallina-tests when on Emacs 24. Somehow.

Another way would be to include a static version of fgallina that is known to work - that would remove the uncertainty of "why is the build broken now" - but also remove the "integration" part of CI.

This was harder than expected. :-)

@gvol

It seems like optional tests would be a good feature for ecukes to have. According to stackoverflow the best thing for cucumber is to use tags. I don't think ecukes has tags though. :-(

@magnars
Owner

After thinking a little, I think this would be the best solution:

  • Bundle the last version of fgallina-mode that worked on E23, and use that when running tests on E23.
  • Download the latest version of fgallina-mode, and use that when running tests on E24.

This way we ensure that it keeps working in both supported environments.

Of course, at some point the two will differ so much that this becomes prohibitive. Perhaps it already is. At that point we just drop the tests for fgallina-mode in E23.

I'll go digging in ecukes to see what can be done. It could at least be made to work by coding some stuff in env.el.

@magnars
Owner

I have finally come to my senses and stopped worrying about E23, at least on the CI-server. :-)

This means that I can now use Carton to grab any dev-dependencies. So we're now integration-testing versus the version of fgallina's python-mode that is in MELPA.

Finally uncommenting all those tests, I was happily surprised to see that only one of them has stopped working in the last 7 months.

The test in question is Scenario: Mark region outside a multi-line string. that I have now commented out waiting for a fix. Wanna give it a shot, @fgeller ? :-)

@fgeller
@magnars
Owner

That sounds like a good idea, once Cassou's PPA for emacs24 is up to 24.3. It's still at GNU Emacs 24.2.1, but I guess that'll change soon enough.

@magnars
Owner

Good news for fgallina btw :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 17, 2012
  1. @fgeller
  2. @fgeller

    Fixes for er/mark-*-python-string.

    fgeller authored
     - `forward-sexp' is now jumps block-wise
    
     - Use a (brittle) search for the delimiters at the beginning of the
       string.
    
     - Factor out common functionality
  3. @fgeller

    Improved marking of Python blocks.

    fgeller authored
     - Fix failing access to `python-rx' by inlining the regular
       expression.
    
     - The `er/mark-python-block' function now accepts the next
       indentation level as an optional argument. This can be used to mark
       the surrounding block more reliably.
Commits on Jul 18, 2012
  1. @fgeller

    Fix a problem where an incorrect region would be selected when the

    fgeller authored
    block to be selected had no indentation.
    
    Given
    
    |def x():
        print()
    
    print()
    
    where | is point, er/mark-python-block should mark the region
    delimited by brackets:
    
    [def x():
        print()]
  2. @fgeller

    Adding tests for expansions in fgallina's python.el.

    fgeller authored
     - Tests for marking the inner and outer part of a Python string.
    
     - Tests for marking inside & outside a multi-line Python string.
    
     - Tests for marking Python blocks.
  3. @fgeller
  4. @fgeller
  5. @fgeller
  6. @fgeller

    More robust marking of Python strings.

    fgeller authored
     - Use the ppss to find the end of a string, works for strings that
       contain escaped delimiters.
    
     - Test for marking a string that contains an escaped delimiter.
  7. @fgeller
Commits on Jul 21, 2012
  1. @fgeller

    Update to latest version of fgallina's python.el

    fgeller authored
     - Adapt to changes in names, and augment test.
  2. @fgeller

    Include check for end of buffer in `er/mark-python-block'.

    fgeller authored
     - Add a test for marking a buffer with a nested one.
    
     - Be more clear about position of point while looping.
Commits on Jul 24, 2012
  1. @fgeller

    Only proceed to next line when not looking at the start of a block and

    fgeller authored
    the current indent is deeper than the target block's indent. Fixes
    issue with selecting a nested block with trailing statements.
    
    Adds test for marking a nested block with trailing statements in the
    outer block.
This page is out of date. Refresh to see the latest.
View
3  .gitmodules
@@ -4,3 +4,6 @@
[submodule "util/espuds"]
path = util/espuds
url = git://github.com/rejeep/espuds.git
+[submodule "features/support/vendor/python-el"]
+ path = features/support/vendor/python-el
+ url = git://github.com/fgallina/python.el.git
View
230 features/fgallina-python-el-expansions.feature
@@ -0,0 +1,230 @@
+Feature: fgallinas python.el expansions
+ In order to quickly and precisely mark Python code blocks
+ As an Emacs user
+ I want to expand to them
+
+ Scenario: Baseline feature test.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert "run(23)"
+ And I place the cursor between "n" and "("
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be "run(23)"
+
+ Scenario: Mark region inside a string.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert "'X-Men: Wolverine'"
+ And I place the cursor between "r" and "i"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be "X-Men: Wolverine"
+
+ Scenario: Mark region inside a string with escape delimiter.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert "'pre' + 'X-Men: Wol\'verine' + 'post'"
+ And I place the cursor between "r" and "i"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be "X-Men: Wol\'verine"
+
+ Scenario: Mark region outside a string.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert "run('X-Men: ' + 'Wolverine')"
+ And I place the cursor between "M" and "e"
+ And I press "C-@"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be "'X-Men: '"
+
+ Scenario: Mark region inside a multi-line string.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ print('lalelu')
+
+ '''This is a multi-line Python string
+ with lots of useless content.
+ '''
+
+ print('lalelu')
+ """
+ And I place the cursor between "-" and "l"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be:
+ """
+ This is a multi-line Python string
+ with lots of useless content.
+
+ """
+
+ Scenario: Mark region outside a multi-line string.
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ '''This is a multi-line Python string
+ with lots of useless content.
+ '''
+ """
+ And I place the cursor between "-" and "l"
+ And I press "C-@"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be:
+ """
+ '''This is a multi-line Python string
+ with lots of useless content.
+ '''
+ """
+
+ Scenario: Mark a basic Python block
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ if True:
+ print('To be, or not to be...')
+ else:
+ print('Booyah.')
+ """
+ And I go to point "1"
+ And I press "C-@"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be:
+ """
+ if True:
+ print('To be, or not to be...')
+ """
+
+ Scenario: Mark a Python block with a nested block
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ if True:
+ if True:
+ print(23)
+ print('To be, or not to be...')
+ else:
+ print('Booyah.')
+ """
+ And I go to point "1"
+ And I press "C-@"
+ Then the region should be:
+ """
+ if
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ if True:
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ if True:
+ if True:
+ print(23)
+ print('To be, or not to be...')
+ """
+
+ Scenario: Mark another Python block with a nested block
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ def moo(data):
+ for foo in data.items():
+ print(foo)
+
+ """
+ And I go to point "1"
+ And I press "C-@"
+ And I press "C-@"
+ And I press "C-@"
+ Then the region should be:
+ """
+ def moo(data):
+ for foo in data.items():
+ print(foo)
+ """
+
+ Scenario: Mark an outer Python block
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ print('More stuff')
+
+ def the_truth():
+ if True:
+ print('To be, or not to be...')
+ else:
+ print('Booyah.')
+
+ print('Even more stuff.')
+ """
+ And I go to point "42"
+ And I press "C-@"
+ Then the region should be:
+ """
+ if
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ if True:
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ if True:
+ print('To be, or not to be...')
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ def the_truth():
+ if True:
+ print('To be, or not to be...')
+ else:
+ print('Booyah.')
+ """
+
+ Scenario: Mark nested Python block with subsequent statements in outer block
+ Given I turn on python-mode
+ And there is no region selected
+ When I insert:
+ """
+ def outer_foo():
+
+ def inner_foo():
+ return 23
+
+ return inner_foo()
+
+ """
+ And I go to point "23"
+ And I press "C-@"
+ Then the region should be:
+ """
+ def
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ def inner_foo():
+ """
+ And I press "C-@"
+ Then the region should be:
+ """
+ def inner_foo():
+ return 23
+ """
View
1  features/support/env.el
@@ -6,6 +6,7 @@
(add-to-list 'load-path expand-region-root-path)
(add-to-list 'load-path (expand-file-name "espuds" expand-region-util-path))
+(add-to-list 'load-path (expand-file-name "features/support/vendor/python-el" expand-region-root-path))
(require 'expand-region)
(require 'espuds)
1  features/support/vendor/python-el
@@ -0,0 +1 @@
+Subproject commit fcd5edddcf72baf18a546d21796150473777903f
View
121 python-el-fgallina-expansions.el
@@ -23,67 +23,128 @@
;; - Additions implemented here:
;; - `er/mark-inside-python-string'
;; - `er/mark-outside-python-string'
-;; - `er/mark-python-sentence'
+;; - `er/mark-python-statement'
+;; - `er/mark-python-block'
+;; - `er/mark-outer-python-block'
;; - Supports multi-line strings
;;; Code:
(require 'expand-region-core)
-(defvar er--python-string-delimiter "'\"")
-
-(defun er/mark-inside-python-string ()
- (interactive)
+(defvar er--python-string-delimiter
+ "'\""
+ "Characters that delimit a Python string.")
+
+; copied from @fgallina's python.el as a quick fix. The variable
+; `python-rx-constituents' is not bound when we use the python-rx
+; macro from here, so we have to construct the regular expression
+; manually.
+(defvar er--python-block-start-regex
+ (rx symbol-start
+ (or "def" "class" "if" "elif" "else" "try"
+ "except" "finally" "for" "while" "with")
+ symbol-end)
+ "Regular expression string to match the beginning of a Python block.")
+
+(defun er/mark-python-string (mark-inside)
+ "Mark the Python string that surrounds point.
+
+If the optional MARK-INSIDE is not nil, only mark the region
+between the string delimiters, otherwise the region includes the
+delimiters as well."
(let ((beginning-of-string (python-info-ppss-context 'string (syntax-ppss))))
(when beginning-of-string
(goto-char beginning-of-string)
- (forward-sexp)
- (skip-chars-backward er--python-string-delimiter)
+ ;; Move inside the string, so we can use ppss to find the end of
+ ;; the string.
+ (skip-chars-forward er--python-string-delimiter)
+ (while (python-info-ppss-context 'string (syntax-ppss))
+ (forward-char 1))
+ (when mark-inside (skip-chars-backward er--python-string-delimiter))
(set-mark (point))
(goto-char beginning-of-string)
- (skip-chars-forward er--python-string-delimiter))))
+ (when mark-inside (skip-chars-forward er--python-string-delimiter)))))
+
+(defun er/mark-inside-python-string ()
+ "Mark the inside of the Python string that surrounds point.
+
+Command that wraps `er/mark-python-string'."
+ (interactive)
+ (er/mark-python-string t))
(defun er/mark-outside-python-string ()
+ "Mark the outside of the Python string that surrounds point.
+
+Command that wraps `er/mark-python-string'."
(interactive)
- (let ((beginning-of-string (python-info-ppss-context 'string (syntax-ppss))))
- (when beginning-of-string
- (goto-char beginning-of-string)
- (set-mark (point))
- (forward-sexp)
- (exchange-point-and-mark))))
+ (er/mark-python-string nil))
-(defun er/mark-python-sentence ()
+(defun er/mark-python-statement ()
+ "Mark the Python statement that surrounds point."
(interactive)
- (python-nav-sentence-end)
+ (python-nav-end-of-statement)
(set-mark (point))
- (python-nav-sentence-start))
+ (python-nav-beginning-of-statement))
+
+(defun er/mark-python-block (&optional next-indent-level)
+ "Mark the Python block that surrounds point.
-(defun er/mark-python-block ()
+If the optional NEXT-INDENT-LEVEL is given, select the
+surrounding block that is defined at an indentation that is less
+than NEXT-INDENT-LEVEL."
(interactive)
- (let ((rx-block-start (python-rx block-start)))
- (back-to-indentation)
- (unless (looking-at rx-block-start)
- (re-search-backward rx-block-start))
- (set-mark (point)) ; mark beginnig-of-block
+ (back-to-indentation)
+ (let ((next-indent-level
+ (or
+ ;; Use the given level
+ next-indent-level
+ ;; Check whether point is at the start of a Python block.
+ (if (looking-at er--python-block-start-regex)
+ ;; Block start means that the next level is deeper.
+ (+ (current-indentation) python-indent)
+ ;; Assuming we're inside the block that we want to mark
+ (current-indentation)))))
+ ;; Move point to next Python block start at the correct indent-level
+ (while (>= (current-indentation) next-indent-level)
+ (re-search-backward er--python-block-start-regex))
+ ;; Mark the beginning of the block
+ (set-mark (point))
+ ;; Save indentation and look for the end of this block
(let ((block-indentation (current-indentation)))
- (end-of-line)
- (while (and (re-search-forward rx-block-start (point-max) 'goto-end)
- (> (current-indentation) block-indentation)))
- (beginning-of-line)
+ (forward-line 1)
+ (while (and
+ ;; No need to go beyond the end of the buffer. Can't use
+ ;; eobp as the loop places the point at the beginning of
+ ;; line, but eob might be at the end of the line.
+ (not (= (point-max) (point-at-eol)))
+ ;; Proceed if: indentation is too deep
+ (or (> (current-indentation) block-indentation)
+ ;; Looking at an empty line
+ (looking-at (rx line-start (* whitespace) line-end))
+ ;; We're not looking at the start of a Python block
+ ;; and the indent is deeper than the block's indent
+ (and (not (looking-at er--python-block-start-regex))
+ (> (current-indentation) block-indentation))))
+ (forward-line 1)
+ (back-to-indentation))
+ ;; Find the end of the block by skipping comments backwards
(python-util-forward-comment -1)
(exchange-point-and-mark))))
(defun er/mark-outer-python-block ()
+ "Mark the Python block that surrounds the Python block around point.
+
+Command that wraps `er/mark-python-block'."
(interactive)
- (forward-line -1)
- (er/mark-python-block))
+ (er/mark-python-block (current-indentation)))
(defun er/add-python-mode-expansions ()
"Adds python-mode-specific expansions for buffers in python-mode"
(let ((try-expand-list-additions '(
er/mark-inside-python-string
er/mark-outside-python-string
- er/mark-python-sentence
+ er/mark-python-statement
er/mark-python-block
er/mark-outer-python-block
)))
Something went wrong with that request. Please try again.