Skip to content

Commit

Permalink
Merge pull request #65 from Kronuz/level-adjustment
Browse files Browse the repository at this point in the history
Level adjustment
  • Loading branch information
mgaitan committed Feb 26, 2014
2 parents 2cb9462 + 78df047 commit 1d8407a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 26 deletions.
8 changes: 4 additions & 4 deletions Default (OSX).sublime-keymap
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,22 @@
[
{ "key": "selector", "operator": "equal", "operand": "text.restructuredtext" }
]
}, { "keys": ["ctrl+="], "command": "header_change_level", "context":
}, { "keys": ["alt+="], "command": "header_change_level", "context":
[
{ "key": "selector", "operator": "equal", "operand": "text.restructuredtext" }
]
},
{ "keys": ["ctrl+keypad_plus"], "command": "header_change_level", "context":
{ "keys": ["alt+keypad_plus"], "command": "header_change_level", "context":
[
{ "key": "selector", "operator": "equal", "operand": "text.restructuredtext" }
]
},
{ "keys": ["ctrl+-"], "command": "header_change_level", "args": {"offset": 1}, "context":
{ "keys": ["alt+-"], "command": "header_change_level", "args": {"offset": 1}, "context":
[
{ "key": "selector", "operator": "equal", "operand": "text.restructuredtext" }
]
},
{ "keys": ["ctrl+keypad_minus"], "command": "header_change_level", "args": {"offset": 1}, "context":
{ "keys": ["altl+keypad_minus"], "command": "header_change_level", "args": {"offset": 1}, "context":
[
{ "key": "selector", "operator": "equal", "operand": "text.restructuredtext" }
]
Expand Down
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,9 @@ Adjust header level
+++++++++++++++++++

With the cursor in a header, press ``ctrl + +`` (plus key) and ``ctrl + -``
(minus key) will increase and decrease the header level respectively.
The adornment decoration (underline / overline) are autodetected from the document
and uses Sphinx's conventions as default.
(minus key) (``alt + +`` and ``alt + -``, in Mac) will increase and decrease the
header level respectively. The adornment decoration (underline / overline) are
autodetected from the document and uses Sphinx's conventions as default.

For example, you have the cursor in::

Expand Down
77 changes: 58 additions & 19 deletions headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
# reference:
# http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections
ADORNMENTS = r"""[!\"#$%&'\\()*+,\-./:;<=>?@\[\]\^_`{|}~]"""
PATTERN = r"^(%s*)\n(?P<tit>.+)\n(?P<under>%s+)" % (ADORNMENTS,
ADORNMENTS)
PATTERN_RE = re.compile(r"^(%s*)\n(.+)\n(%s+)" % (ADORNMENTS, ADORNMENTS), re.MULTILINE)

Header = namedtuple('Header', "level start end adornment title raw")
Header = namedtuple('Header', "level start end adornment title raw idx")


class RstHeaderTree(object):
# based on sphinx's header conventions
DEFAULT_HEADERS = {0: '**', 1: '=', 2: '-', 3: '^', 4: '"', 5: '+',
6: '~', 7: '#', 8: "'", 9: ':'}
DEFAULT_HEADERS = '** = - ^ " + ~ # \' :'.split()

def __init__(self, text):
# add a ficticius break as first line
Expand Down Expand Up @@ -49,12 +47,12 @@ def _parse(self, text):
"""

candidates = re.findall(PATTERN, text, re.MULTILINE)
candidates = PATTERN_RE.findall(text)
headers = []
levels = []
idx = 0

for candidate in candidates:
(over, title, under) = candidate
for over, title, under in candidates:
# validate.
if ((over == '' or over == under) and len(under) >= len(title)
and len(set(under)) == 1):
Expand All @@ -63,10 +61,11 @@ def _parse(self, text):
if adornment not in levels:
levels.append(adornment)
level = levels.index(adornment)
raw = "\n".join(candidate)
start = text.find(raw) - 1 # see comment on __init__
raw = (over + '\n' if over else '') + title + '\n' + under
start = text.find(raw) - 1 # see comment on __init__
end = start + len(raw)
h = Header(level, start, end, adornment, title, raw)
h = Header(level, start, end, adornment, title, raw, idx)
idx += 1
headers.append(h)
return headers

Expand Down Expand Up @@ -144,9 +143,19 @@ def prev(self, header, same_or_high=False, offset=-1):

def levels(self):
""" returns the heading adornment map"""
levels = RstHeaderTree.DEFAULT_HEADERS.copy()
_levels = RstHeaderTree.DEFAULT_HEADERS.copy()
for h in self.headers:
levels[h.level] = h.adornment
_levels[h.level] = h.adornment
levels = []
for adornment in _levels:
if adornment not in levels:
levels.append(adornment)
for adornment in RstHeaderTree.DEFAULT_HEADERS:
if adornment not in levels:
if len(adornment) == 2:
levels.insert(0, adornment)
else:
levels.append(adornment)
return levels

@classmethod
Expand All @@ -168,24 +177,54 @@ class HeaderChangeLevelCommand(sublime_plugin.TextCommand):
The level markup is autodetected from the document,
and use sphinx's convention by default.
"""
views = {}

def run(self, edit, offset=-1):
vid = self.view.id()
HeaderChangeLevelEvent.listen.pop(vid, None)

cursor_pos = self.view.sel()[0].begin()
region = sublime.Region(0, self.view.size())
tree = RstHeaderTree(self.view.substr(region))
levels = tree.levels()

parent = tree.belong_to(cursor_pos)
hregion = sublime.Region(parent.start + 1, parent.end + 1)
if not (parent.start < cursor_pos <= parent.end):

is_in_header = parent.start <= cursor_pos <= parent.end
if not is_in_header:
return

if offset == -1 and parent.level == 0:
idx, levels = HeaderChangeLevelCommand.views.get(vid, (None, None))
if idx != parent.idx:
levels = tree.levels()
HeaderChangeLevelCommand.views[vid] = (parent.idx, levels)

try:
level = levels.index(parent.adornment)
if level + offset < 0:
return
adornment = levels[level + offset]
except IndexError:
return

adornment = levels[parent.level + offset]
new_header = RstHeaderTree.make_header(parent.title, adornment)
self.view.replace(edit, hregion, new_header)
hregion = sublime.Region(parent.start, parent.end + 1)

try:
self.view.replace(edit, hregion, new_header)
finally:
def callback():
HeaderChangeLevelEvent.listen[vid] = True
sublime.set_timeout(callback, 0)


class HeaderChangeLevelEvent(sublime_plugin.EventListener):
listen = {}

def on_modified(self, view):
vid = view.id()
if HeaderChangeLevelEvent.listen.get(vid):
del HeaderChangeLevelCommand.views[vid]
del HeaderChangeLevelEvent.listen[vid]


class HeadlineMoveCommand(sublime_plugin.TextCommand):
Expand Down

0 comments on commit 1d8407a

Please sign in to comment.