-
Notifications
You must be signed in to change notification settings - Fork 126
[WIP] Implement abstraction for visual selection #269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This is needed because Instead of class Selection(object):
def __init__(self, buffer, startpos, endpos): then it can take any pair of (line, column) positions, instead of just We could still have the @property
def visual_selection(self):
"""Get the current visual selection"""
startmark = self.mark('<')
endmark = self.mark('>')
return Region(self, startmark, endmark) |
This implements a class abstraction to represent a selection between two positions in the buffer. Most notably this can be used to represent a selection in visual block mode. In addition to the representation, said class allows for manipulation of the represented selection.
5fdc50a
to
e73f6f5
Compare
Aye, exactly this. @justinmk Regarding the deprecation of Range/merge of Range into Selection/Region: Should this be done immediately in this PR or as a follow-up one? A reason against having those two objects merged would be that Range would implement writing through onto the buffer while not caring about partials and Selection/Region takes care of the partial-lines-implementation, separating those logics. But otherwise, merging both into one object would probably reduce the overall footprint. Furthermore, in case we go for direct merge, I think I might have found a Bug in Range that I'd like to investigate and possibly fix before we would merge the two classes, therefore I would stall this PR for the moment then to keep Bugfixes separate from Feature implementations in that case if that's ok. |
Actually, another reason to not deprecate Range would be that it would make perfect sense for a Range of three lines to replace that by e.g. five lines, the Range in the original buffer would just expand a little. This however is not well defined for a Selection/Region: inserting more lines than the Region would duplicate some part of the lines that are not part of the selection, that's why I implemented the explicit
Capturing this in one object would lead to rather nested code and the separation into a range-object adn a region-object that is based on a range-object would be a much cleaner solution and range would be able to keep its larger set of functionality. Therefore I'd argue merging Range and Region/Selection is not a good idea. |
"deprecate Range" doesn't need to be discussed right now, it was only a minor future suggestion.
Bugfix is welcome. |
Ok, Code ready for merge now. There are some complaints of flake8 left causing the CI to fail, but they do not affect those parts of the code that this PR touches, so that shouldn't be an obstacle for the merge of this PR. Re: Bugfix in Range: not a blocker for this PR, so this one can be merged, I'll track down the Bug I suspect to be in Range in the next couple of days and open a separate PR for that if I actually can substantiate my suspicion. |
@bfredl |
Later on we might want abstractions directly in the API for regions on the character-level, especially with extmarks that care about adjusting character position. At first API functions that gets or sets the text between two mark objects (an abstraction that either is a named mark or concrete position, extmarks PR neovim/neovim#5031 does steps towards this), also nvim_put neovim/neovim#6819 could take a mark object and not just put at cursor position. |
Maybe just add it to the ignore list in setup.cfg |
ok, then I'd say this one is ready to be merged. |
neovim/api/buffer.py
Outdated
class Region(object): | ||
def __init__(self, buffer, startmark, endmark): | ||
self._range = buffer.range(startmark[0], endmark[0] + 1) | ||
self.slice = slice(startmark[1], endmark[1] + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not a slice as startmark[1]
and endmark[1]
are indicies into different sequences (lines in this case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, this implementation is actually intended for visual block selection. This is indeed not sufficient for normal visual selection, when thinking about it.
How can a simple visual selection and a visual block selection be differentiated? Start- and end-marks are not sufficient for that, obviously, as it starts both of them.
Thinking about this, I think I'll introduce a If possible I try to let both inherit from a |
Let's not go crazy with OOP :) They aren't very different. The interface is the same:
|
Actually not: standard visual mode has full lines between the first and the last lines and partial lines in the first and last row. That makes handling of such a selection actually quite different for my taste as both differ in a number of nasty details, requiring very different flavors of Merging both into one Class and having a switch would be an option, though. I'm not yet sure if that's a nice solution, because This then make up more or less the entirety of that class (as (I dropped the "let's inherit from a general object"-idea, though, as that does indeed make very little sense in this context.) At least this is my reasoning to this point as far as I have thought the thing out so far. I'm happy to be convinced otherwise, @justinmk :) Wouldn't be the first time that I don't see a more beautiful solution right away and go for a very much inferior one.^^ (and don't mind the failing CI, btw., I just pushed the current state for brief review, I will fix and test ASAP. However, feel free to comment on the current code right now (at least on a conceptual level), if you want, that's why I already pushed even if it's not yet finished). |
It's still a pair of (row,col). The behavior is different, but the interface is the same. It isn't a different "class", it's just a region.
Could be functions like |
Good point indeed. I'll reconsider the implementation and see how to merge both objects. |
Based on the suggestions of @justinmk I redesigned the API for the Region object and it is indeed much more general now. I still like to rebase onto master, but feel free to already review the code and give your opinion and suggestions! |
# writing | ||
vim.current.buffer[:] = reference_buffer | ||
r = Region(vim.current.buffer, 2, 4, partials=[(1, 3), (1, 3), (0, 3)]) | ||
r[:] = ['bar']*3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would strongly prefer explicit functions rather than cute list operations. I don't know if it's pythonic. This is an RPC API, not pandas or whatever. Though I also find pandas far too magical.
@bfredl uses python more than I , would like to hear his opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest until extmarks (and api or lua functions to manipulate charwise ranges in an efficient and atomic way) are available this is a pretty weak abstraction, "cute" list operations (no matter what one thinks about them) are more or less what the class offers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@justinmk
Regarding the list-operatons I tried to keep as close as possible to the Buffer- and Range-classes, that's where the idea for this kind of implementation came from.
Regarding the API: which API is this wrapping? The code footprint would actually much decrease if there is an API I overlooked giving me the current selection that I can use.
@justinmk @bfredl
So we proceed with the list operators like Buffer and Range use it for the Region-abstraction (until there is something better)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough
|
||
|
||
class Region(object): | ||
def __init__(self, buffer, startrow, endrow, partials=(None, None)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the doc should mention that it's 1-indexed.
See neovim/neovim#13896 (comment) and neovim/neovim#21115 (comment) . The Lua function |
This implements a class abstraction to represent a selection between two positions in the buffer. Most notably this can be used to represent a selection in visual block mode. In addition to the representation, said class allows for manipulation of the represented selection.
Furthermore, a property visual_selection was added to Buffer to allow easy access to a selection made in visual (block) mode and manipulation thereof e.g. via
nvim.current.buffer.visual_selection
.