Reputation: 46381
I have a text like this in Sublime Text (usually a very huge text file):
#tag3
Some notes here about this and that.
#tag1 #tag2
Hello world, here is some text
#tag4
Blah
Blah
#tag2
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
#foo
bar
How is it possible with Sublime Text to group or display only paragraphs relevant to #tag2
? Is it possible with "multiple cursors" or another technique?
This is the desired output: the #tag2
paragraphs moved first, and then the rest at the end:
#tag1 #tag2
Hello world, here is some text
#tag2
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
#tag3
Something else
#tag4
Blah
Blah
#foo
bar
What I've tried up to now: CTRL+F, and #tag2
and then browse the different matches to find everything relevant to this tag.
Note: I'm not looking for a command-line method, but rather a handy method in Sulbime Text to be able to browse quickly a gigantic text document with notes/paragraphs organized by tags.
Upvotes: 2
Views: 1175
Reputation: 22791
There's no easy built in way to do something like this. As you mentioned you could search for the tag in question and skip between the results. You could also try to craft a regular expression that would only match the tags (and their content) that you're interested in and then do a Find All
to select them and cut and paste them to the top. However that may or may not be feasible depending on the contents of the file.
One potential method would be to create some sort of custom syntax for your notes so that you could take advantage of built in symbol list functionality.
%YAML 1.2
---
# See http://www.sublimetext.com/docs/3/syntax.html
scope: text.plain.notes
file_extensions:
- notes
contexts:
main:
- match: '^\s*(#\w+)'
captures:
1: entity.name.type
push:
- match: '#\w+'
scope: entity.name.type
- match: $
pop: true
If this simple example syntax (ST3 only) was applied to your notes file, all of the tags would be syntax highlighted and would appear in the symbol list, allowing you to use Goto > Goto Symbol
.
In this case the tags would appear in the symbol list in the order they appear in the file, but you can enter some filtering text to filter the list and easily skip between them. You also gain the ability to open a file directly at a tag via Goto > Goto Anything
.
Additionally, with the cursor on a tag, Goto > Goto Defintion...
would show a quick panel giving the other locations for that tag (if any), both in the current file as well as other files, allowing you to jump to the appropriate location.
For something such as you mention in your question that would shuffle the contents of the file around, you would need a plugin.
An example of such a plugin is the following, which assumes that tags always appear on lines by themselves and that everything following them up until the next line of tags is the body (such as is laid out in your example text).
import sublime
import sublime_plugin
import re
# Regular expressions that match a single tag as well as a line that contains
# one or more tags. Note that the code below presumes that lines with tags
# contain ONLY tags and nothing else.
_tag_regex = r"#\w+"
_tag_line_regex = r"^[ \t]*(?:(?:#\w+)\s*){1,}"
# Command palette input handlers are only supported in Sublime builds 3154 and
# later, so guard against using them in a previous version.
st_ver = int(sublime.version())
HandlerBase = sublime_plugin.ListInputHandler if st_ver >= 3154 else object
class TagInputHandler(HandlerBase):
"""
Input handler for a command argument named "tag"; tries to provide a list
of tags that appear in the current file, if any. The selected value becomes
the value for the argument.
"""
def __init__(self, view):
self.view = view
def placeholder(self):
return "Tag to move"
def list_items(self):
tags = set()
tag_lines = self.view.find_all(_tag_line_regex)
for region in tag_lines:
line = self.view.substr(region)
tags.update(re.findall(_tag_regex, line))
if not tags:
sublime.status_message("File contains no tags")
return list(sorted(tags))
class TagToTopCommand(sublime_plugin.TextCommand):
def run(self, edit, tag=None):
if tag is None:
return self.prompt_tag()
# Find all lines that contain tags; they are considered the start of
# sections.
lines = self.view.find_all(_tag_line_regex)
matched = []
eof = sublime.Region(self.view.size())
# Keep any tag lines that contain the tag we were given. The found
# regions are modified so that they start at the start of the tag line
# and end at the start of the following tag section (or end of file)
# so that the region entirely encompasses the data for these tags.
for idx, line in enumerate(lines):
end = lines[idx + 1] if idx + 1 < len(lines) else eof
if tag in re.findall(_tag_regex, self.view.substr(line)):
matched.append(sublime.Region(line.a, end.a))
# Extract all of the sections that we matched above.
text = []
if matched:
# Go bottom up so we don't disturb our offsets.
for region in reversed(matched):
text.append(self.view.substr(region))
# Special handling if this region ends at the buffer end
if region.b == eof.b:
# Move the start up the region upwards to skip over all
# blank lines, so that when we trim this section out there
# aren't extra blank lines left at the bottom of the view.
prev_line = self.view.line(region.a - 1)
while prev_line.a == prev_line.b:
prev_line = self.view.line(prev_line.a - 1)
region.a = prev_line.b + 1
# The region doesn't capture the last line of the buffer,
# so ensure that this item is separated from the other
# items when it moves.
text[-1] += "\n"
self.view.replace(edit, region, '')
# Add all of the text back at the beginning, but reverse the order
# of the entries so they preserve the order they appeared in the
# file.
self.view.insert(edit, 0, ''.join(reversed(text)))
else:
sublime.status_message("'%s' not found in the current file" % tag)
def input(self, args):
# This is invoked by Sublime if the command is executed from the
# command palette; we make the palette prompt us for the tag if one is
# not given.
if args.get("tag", None) is None:
return TagInputHandler(self.view)
def prompt_tag(self):
# This is invoked in situations where the command is executed without a
# "tag" parameter and it's not executing in the command palette; fall
# back to prompting via a quick panel
items = TagInputHandler(self.view).list_items()
def pick(idx):
if idx != -1:
self.view.window().run_command("tag_to_top", {"tag": items[idx]})
self.view.window().show_quick_panel(
items,
lambda idx: pick(idx))
This implements a tag_to_top
command which, given a tag, will find all of the sections that mention that tag and pull them to the top of the file in their current file order.
The tag
argument is optional; if not given a list of all of the unique tags in the current file will be displayed in a list for you to select from. You can also pass it the tag directly, for example if you have some tags you often view or some such.
{
"keys": ["super+t"], "command": "tag_to_top",
//"args": { "tag": "#something" }
},
You can also add it to the command palette if desired by adding the following to a file named TagActions.sublime-commands
to your User
package:
[
{ "caption": "Bring Tag to Top", "command": "tag_to_top" }
]
As above you could provide a tag as an argument, potentially adding the command multiple times with different tags to easily swap the file around.
Additionally, if you're using a build of Sublime that supports it, when run from the command palette this will prompt you for the tag directly there instead of opening a quick panel instead.
Note that the above is meant for Sublime Text 3, but it should work in Sublime Text 2 as well (since you tagged it), although this is only mildly tested on my part.
The example demonstrates how to find the sections of the file that represent a tag so that they can be removed from the file and added back in at the top. For a non-destructive option this could be adapted to instead determine the regions that DON'T match the tag and instead fold them up, which would leave only the tags you're interested in immediately visible in the buffer.
Upvotes: 1