This Sublime Text plugin randomly selects a position in a text file and places the cursor there as if the user had clicked on that position using the mouse.
I also want it to copy the line of that random position into the clipboard. Is it possible to do that in the same program?
import sublime, sublime_plugin
import random
class JumpToRandomPositionCommand(sublime_plugin.TextCommand):
"""
When invoked, randomly select a character in the current
file and jump the cursor to that position. Does nothing
if the current file is empty or if the current view does
not represent a file.
"""
def run(self, edit):
view = self.view
if view.size() > 0 and view.settings().get("is_widget", False) == False:
view.sel().clear()
pos = random.randrange(0, view.size())
view.sel().add(sublime.Region(pos, pos))
view.show(pos)
There are a couple of ways that do what you want to do here. So, here are some snippets of code you can add to the plugin you provided in your question. In each case, these bits of code would go into the plugin inside of your final if statement as the last line (i.e. after the call to view.show(). The position is important; is has to only happen if the position was changed, and (in the case of the first snippet) it has to happen after the cursor has moved to the new location.
First, the easiest one. Sublime contains the following setting in the default preferences:
// If true, the copy and cut commands will operate on the current line
// when the selection is empty, rather than doing nothing.
"copy_with_empty_selection": true,
When this is turned on (which as seen here it is by default), if you try to trigger the copy or cut commands when there's no selection, each command will assume you meant to work on the whole line.
If you're working in this way, the following line added to your plugin is enough to do the job:
view.run_command('copy')
That is, ask the view to run the copy command, which is what would happen if you manually pressed the key binding. The plugin sets a single cursor into the line in a random position with an empty selection, so this would operate on that empty selection and copy the line.
If on the other hand you don't use this setting, or you want the command to work regardless of what it is set to, then you need to do a bit more work. That would look something like this:
line = view.full_line(pos)
text = view.substr(line)
sublime.set_clipboard(text)
Here we ask Sublime to tell us what span of text represents the line that the random position is sitting inside of. We then extract that text from the buffer and add it to the clipboard.
Related
The . key can be used to repeat the last insert command. However, we might do some navigation that is not part of the insert, but we want it repeated.
Imagine commenting out lines like so:
// line of text
// line of text
line of text
line of text
The insert command is to put the two forward slashes and a space. That can be repeated using the . key. The navigation would be to navigate down one line and then left some number of characters. That part is not captured by the . key command.
How can we achieve this functionality? I read that it was not available in Vi some years ago, but I'm wondering if it exists now in the latest version of Vim.
Press qX, where X is any of the writable registers (typically: pick any lowercase letter).
Do whatever actions you want to record.
Press q again to stop recording.
Press #X (where X is the same register) to play it back (count times, if used with a count).
Press ## to replay the most recently used macro (count times).
I read that it was not available in Vi some years ago, but I'm wondering if it exists now in the latest version of Vim.
If the Vim docs are to be believed, Vi did not support recording (steps 1-3), but did support #. Then you would have to manually yank the characters into the target register with "Xy<motion> or some other register-writing command. That also works under Vim, but I can't recommend it because it is much more error prone.
Another approach would be "block select then edit" approach:
ctrl + v - block select
then go down j or down-arrow
shift + i will put you in insert mode. Make the change here where you want it to be reflected on all the other lines you've selected.
esc twice will show/repeat that change you made on the line one.
If you have a big range of similar lines and want to put // at the beginning of it, you can do something like:
:15,25norm! I//<space>
You can also use visual area (vip selects an entire paragraph)
:'<,'>norm! I//<space>
using a pattern
:g/TODO/norm! I//<space>
I'm currently writing a vim script that runs my code through the gofmt linter. Once I have the output from the command, I run the following.
let view = winsaveview()
1,$d
undojoin | call setline(1, gofmt_output)
call winrestview(view)
This works great, however, when the user presses u to undo, the file scrolls back to the top. Is there a way around this? Below is a gif of the behavior.
Here's a hack I just found that seems to be working somewhat.
All I do is to make a bogus, noop change on the current line
before I replace the buffer contents.
In my case I simply read the new content from a file so it looks like this:
let winview = winsaveview()
:s/$//
:%!cat /tmp/.vimfmtnew
call winrestview(winview)
The s/$// bit is the noop change on the current line, and :%!cat /tmp/.vimfmtnew replaces the whole buffer with my tmpfile.
My theory about this is that vim collapses all changes from my function into a single undo action and puts the cursor position to the first action's first changed line. This now happens to be an action where my cursor was when I called the function so this puts the cursor onto the correct line.
It's not perfect though:
if you have the same buffer open in a split view,
the unfocused split views will still reset on undo/redo.
I think the correct solution is this:
diff the old and new content
and only replace the changed lines.
I think that would retain the positions in the split views too
but it's a bit more effort to implement this nicely.
(Doing the replace from the first changed line doesn't always work for me
because I use goimports as my formatter and that can change lines near the beginning too so that would almost be equivalent to a full replace.)
In the following vim macro from this article, the author gives an explanation. I understand some but not everything.
:qccwcommand<Esc>:w<Ctl-W>jj<Enter>q
This macro (which includes the record start / stop, if you’re wondering) will change the current ‘path_to_command’ word to ‘command’, write the file out to disk, change window splits to the grep search results, move down one line in the results and navigate to that result’s location.
Q1. What is ccw doing here? Is it something to do with cw (change word) but I am not sure.
Q2. :w must be to write, right?.
Q3. What is <Ctrl-W>jj doing here? The following is what :h CTRL-W_J is about but I am not sure if I am looking into the correct help nor the meaning of the help in this context.
*CTRL-W_J*
CTRL-W J Move the current window to be at the very bottom, using the
full width of the screen. This works like closing the current
window and then creating another one with ":botright split",
except that the current window contents is used for the new
window.
you're confused because ccw taken literally in that sequence of commands dosen't really make sense!
the initial qc means "start macro recording in register c"
then the next cw means change word (e.g. delete the next word and leave editor in insert mode)
also notice that the final command is a q: this means to finish the macro recording. Macros are super useful when doing things that require a lot of repetition!!1 --> http://vim.wikia.com/wiki/Macros
then, his explanation in the blog post contains the answer to Q2 and Q3
This macro (which includes the record start / stop, if you’re wondering)
will change the current ‘path_to_command’ word to ‘command’, write the file
out to disk, change window splits to the grep search results, move down one
line in the results and navigate to that result’s location. I ran the macro
by hitting #c and then verifying the line selected was one that I wanted to
change. For a few instances where I did not want to change the line, I
manually navigated to the next search result and then re-ran the #c macro.
Q2 - yup, to save the file: "write the file out to disk"
Q3 - the <Ctl-W>jj<Enter> is a sequence of Vim key commands to move to the next entry in the quickfix window from the vimgrep, as he indicates: "write the file
out to disk, change window splits to the grep search results, move down one
line in the results and navigate to that result’s location."
I want to copy paste some lines in vi.
I have a text like
python class1 def:
code code code
...
code code code
last line class1
python class2 def:
code code code
...
code code code
I want to copy the whole class1. I was trying to do it with yNy, so I needed to get N, that is, to count the number of lines the class has.
Then I thought it would be good to get the line number of python class1 def: (let's say X) and the last line class1 (Y), calculate N=Y-X, go to the first line of the class and do the yNy. However, I could not figure out how I can get the line numbers.
So, is there any way to know which line I am in? And in general, is there any other way to copy paste a whole block like the one I indicated?
This is my vi version:
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Oct 26 2012 16:44:45)
Included patches: 1-547
The current line number can be obtained by :.=. Ctrl-g gives more details including filename, column information, ...
In order to copy a block, go to the start of the line to be copied 0. Hitting v would start the visual mode. Navigate to the last line to be copied. Yank y. (Visual selection is now in buffer.)
Using only normal mode commands:
You can do y} to yank everything from the current line to and including the next empty line, delimiting what Vim considers to be a "paragraph". This may or may not work depending on your coding style.
Still using the notion of "paragraph", you can do yip or yap from anywhere in a "paragraph".
You can set the number option which allows you to see absolute line numbers and therefore be able to do y10G, "yank everything from here to line 10".
You can set the relativenumber option which allows you to see relative line numbers and therefore be able to do y5j, "yank everything from here to 5 lines below".
You can do V/foo<CR>y to yank everything from here to foo linewise.
More generally, you can simply use visual mode to select what you want and yank it.
You can also set a mark on the first line of the class with ma, move the cursor to its last line and do y'a (which sounds like the name of a Lovecraftian deity).
Using Ex commands:
Because the aforementioned number option shows absolute line numbers, you can see that the class ends at line 10 and do :.,10y.
Because the aforementioned relativenumber option shows relative line numbers, you can see that the class ends 5 line below and do :,+5y (dropping the implied .).
Using your statusline (or not):
You can :set ruler to have the current line number displayed on the right side of your statusbar if you have one or on the right side of your command line if you don't have a statusline.
Using Vimscript:
You can use line('.') to retrieve the number of the current line.
Using custom text-objects:
There are a number of custom text-objects available on vim.org for indented blocks, function arguments and many other things. Maybe there is one for Python classes.
More generally, I'd advise you to set either ruler, number or relativenumber permanently in your ~/.vimrc and get used to it.
ruler is the least invasive of the bunch but it's also the most limited: you know where you are but it doesn't help at all when you want to define a target.
number is the most classical and can be used to easily target a specific line.
relativenumber is a bit weird at first and, like number, can be used easily to target a specific line.
Choosing number or relativenumber is, as far as I'm concerned, a matter of taste. I find relativenumber very intuitive, YMMV.
Try the following in command mode
:.= returns line number of current line at bottom of screen
yNy or Nyy copies the next N lines, including the current line
p pastes the copied text after the current line
Additionally,
:set nu! in command mode will turn on/off the line number at the beginning of each line.
let the vim registers do this task. why bother calculating lines
for example if you want to copy line X to line y
1) move your cursor to 1st character of line X.
2) type "ma" . this will save current cursor position in register "a".
3) move cursor to last char of line Y.
4) type "y`a". copy is done
5) p pastes the copied text
This method can work not only lines but block ,words even on characters.
This question already has answers here:
How do I reformat HTML code using Sublime Text 2?
(16 answers)
Closed 9 years ago.
I have a html source file containing only one line like the following:
<html><head>test</head><body>wow</body></html>
, and I want to format it as following:
<html>
<head>
test
</head>
<body>
wow
</body>
</html>
, I have used the command: Edit-> Line-> Reindent but it doesn't work.
In Sublime Text try this:
Highlight the last character in the first tag: ">"
Use Find > Quick Add Next (Command+D on Mac or Ctrl+D on PC)
You'll now have all occurrences highlighted in multiple selections, move the caret to the end of the tag and hit return to insert all the new lines you require.
Good luck
I was under the impression that Sublime provided this ability as well. When I found out that it wasn't, I had the idea of using regular expressions. Even though regex are usually considered inappropriate for parsing XML/HTML, I found this approach to be acceptable in this case. Sublime is also said to be highly customizable by plugins, so I think this would be a way.
Sublime Plugins
To be honest, I could have thought of tidy or at least suspect that there must be plugins out there dealing with your issue. Instead I ended up writing my first sublime plugin. I have only tested it with your input and expected output, which it satisfied, but it is most certainly far from working reliably. However, I post it here to share what I've learned and it's still an answer to the problem.
Opening a new buffer (Ctrl+n) and choosing the 'New Plugin...' entry in the Menu 'Tools' generously generates a little 'Hello World!' example plugin (as a Python module), which gives a great template for implementing a sublime_plugin.TextCommand subclass. A TextCommand provides access to an active buffer/currently open file. Like its relatives WindowCommand and ApplicationCommand, it is required to overwrite a run-method.
The official API Reference suggests learning by reading the example sources distributed with the Sublime builds and located in Packages/Default relative to the Sublime config path. Further examples can be found on the website. There's more on the internet.
Processing selected text
To get to a solution for your issue, we primarily need access to a View object which represents an active text buffer. Fortunately, the TextCommand subclass we are about to implement has one, and we can conveniently ask it for the currently selected regions and their selection contents, process selected text conforming our needs and replace the selected text with our preference afterwards.
To sum up the string operations: There are four regular expressions, each of which matches one of the element classes <start-tag>, <empty-tag/>, </close-tag> and text-node. Assuming that all of our markup text is covered by these, we did each line in selection into matching substrings. These are then realigned one-per-line. Having done this, we apply simple indentation by remembering to indent every line whose predecessor contains a start tag. Lines containing end tags are unindented immediately.
Using the group addressing features of Python regex, we can determine the indentation of every line and align the next one accordingly. This, with no further ado, will result in internally consistent indented markup, but with no consideration of the lines outside the selection. By extending the selection to an enclosing element, or at least complying with the indentation levels of the adjacent lines, one could easily improve the results. Its always possible to make use of the default commands.
Another thing to take care of is binding keys to the plugin command and contributing menu entries. It is probably possible somehow, and the default .sublime-menuand .sublime-commands files in Packages/Default at least give an idea. Anyway, here's some code. It has to be saved under Packages/User/whatever.py and can be called from the Sublime Python Console (Ctrl+`) like this: view.run_command('guess_indentation').
Code
import sublime
import sublime_plugin
import re
class GuessIndentationCommand(sublime_plugin.TextCommand):
def run(self, edit):
view = self.view
#view.begin_edit()
# patterns
start_tag = '<\w+(?:\s+[^>\/]+)*\s*>' # tag_start
node_patterns = [start_tag,
start_tag[:-1]+'\/\s*>', # tag_empty
'<\/\s?\w+\s?>', # tag_close
'[^>\s][^<>]*[^<\s]'] # text_node
patterns = '(?:{0})'.format('|'.join(node_patterns))
indentors = re.compile('[ \t]*({0})'.format('|'.join(node_patterns[:1])))
unindentors=re.compile('[ \t]*({0})'.format(node_patterns[2]))
# process selected text
for region in view.sel():
# if selection contains text:
if not region.empty():
selection = view.substr(region)
expanded = []
# divide selected lines into XML elements, if it contains more than one
for line in selection.split('\n'):
elements = re.findall(patterns, line)
if len(elements)>0:
expanded += elements
else:
expanded.append(line)
# indent output
indent=0
indented = []
for line in expanded:
match = unindentors.match(line)
if match:
indent = max(0, indent-1)
# append line to output, unindented if closing tag
indented.append('\t'*indent+line)
if match:
continue
# test for possible indentation candidate
# indentation applies to the NEXT line
match = indentors.match(line)
if match:
indent+=1
# replace selection with aligned output
view.replace(edit, region, '\n'.join(indented))
if its for something simple, i was able to record a macro (tools -> record macro) indenting the tags and then save it and reuse this macro. not sure if that helps any though.