From 787c70b5e767894d97ed8c33280236542af90fa5 Mon Sep 17 00:00:00 2001 From: Felix Hao Date: Thu, 3 Aug 2017 15:47:39 +0800 Subject: [PATCH] convert inline links to references close #426 --- Context.sublime-menu | 4 ++ Default.sublime-commands | 8 +++ decide_title.py | 3 +- messages/2.2.4.md | 9 +++- references.py | 111 ++++++++++++++++++++++++++++++++++++--- 5 files changed, 126 insertions(+), 9 deletions(-) diff --git a/Context.sublime-menu b/Context.sublime-menu index c9417f12..b2c9c0c5 100644 --- a/Context.sublime-menu +++ b/Context.sublime-menu @@ -10,5 +10,9 @@ { "caption": "MDE: Jump Reference", "command": "reference_jump_context" +}, +{ + "caption": "MDE: Convert inline link to reference", + "command": "convert_inline_link_to_reference" } ] \ No newline at end of file diff --git a/Default.sublime-commands b/Default.sublime-commands index f9f2e5c6..115c9679 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -144,5 +144,13 @@ { "caption": "MarkdownEditing: Change color scheme...", "command": "mde_color_activate" + }, + { + "caption": "MarkdownEditing: Convert all inline links to references", + "command": "convert_inline_links_to_references" + }, + { + "caption": "MarkdownEditing: Convert inline link to reference", + "command": "convert_inline_link_to_reference" } ] diff --git a/decide_title.py b/decide_title.py index 8fa52df1..664691f5 100644 --- a/decide_title.py +++ b/decide_title.py @@ -11,6 +11,7 @@ def on_modified_async(self, view): text = view.substr(sublime.Region(0, view.size())) it = re.finditer(r'^(#{1,6}(?!#))|^(-{3,}|={3,})', text, re.M) title = '' + title_begin = None for m in it: if '.front-matter' in view.scope_name(m.start()): continue @@ -23,7 +24,7 @@ def on_modified_async(self, view): title_begin = m.start() + 1 if 'markup.raw.block.markdown' not in view.scope_name(title_begin).split(' '): break - if len(title) == 0: + if len(title) == 0 and title_begin is not None: title = text[title_begin: title_end] title = title.strip() diff --git a/messages/2.2.4.md b/messages/2.2.4.md index 208125dc..2b6f043c 100644 --- a/messages/2.2.4.md +++ b/messages/2.2.4.md @@ -12,8 +12,13 @@ feedback you can use [GitHub issues][issues]. * Latex math expressions are highlighted now: - $\frac{d}{dx}\left( \int_{0}^{x} f(u)\,du\right)=f(x).$ -* Front matter (yaml, coffee, json) highlight -* Go Programming Language highlight in code blocks +* Front matter (yaml, coffee, json) highlight. +* Go Programming Language highlight in code blocks. +* Convert between inline links and references. + - Command: MarkdownEditing: Convert all inline links to references + - Command: MarkdownEditing: Convert inline link to references + + Only appear if cursor is on an inline link + + Also appear in context menu ## Changes diff --git a/references.py b/references.py index 7bc156e8..6765a5ee 100644 --- a/references.py +++ b/references.py @@ -2,6 +2,7 @@ import sublime_plugin import re import operator +from functools import partial try: from MarkdownEditing.mdeutils import * except ImportError: @@ -107,13 +108,15 @@ def getCurrentScopeRegion(view, pt): return sublime.Region(l, r) -def findScopeFrom(view, pt, scope, backwards=False): +def findScopeFrom(view, pt, scope, backwards=False, char=None): """Find the nearest position of a scope from given position.""" if backwards: - while pt >= 0 and not hasScope(view.scope_name(pt), scope): + while pt >= 0 and (not hasScope(view.scope_name(pt), scope) or + (char is not None and view.substr(pt) != char)): pt -= 1 else: - while pt < view.size() and not hasScope(view.scope_name(pt), scope): + while pt < view.size() and (not hasScope(view.scope_name(pt), scope) or + (char is not None and view.substr(pt) != char)): pt += 1 return pt @@ -245,7 +248,7 @@ def suggest_default_link_name(name, image): ret += word.capitalize() if len(ret) > 30: break - return ('image' if image else 'link') + ret + return ('image' if image else '') + ret else: return name @@ -274,7 +277,7 @@ def run(self, edit, image=False): link = mangle_url(contents) if is_url(contents) else "" suggested_name = "" if len(link) > 0: - # If link already exists, reuse existing reference + # If link already exists, reuse existing reference suggested_link_name = suggested_name = check_for_link(view, link) for sel in view.sel(): text = view.substr(sel) @@ -555,9 +558,105 @@ def run(self, edit): whitespace_at_end = view.find(r'\s*\z', 0) view.replace(edit, whitespace_at_end, "\n") - # If there is not already a reference list at the and, insert a new line at the end + # If there is not already a reference list at the end, insert a new line at the end if not view.find(r'\n\s*\[[^\]]*\]:.*\s*\z', 0): view.insert(edit, view.size(), "\n") for link in missings: view.insert(edit, view.size(), '[%s]: \n' % link) + + +def convert2ref(view, edit, link_span, name, omit_name=False): + """Convert single link to reference.""" + view.sel().clear() + link = view.substr(sublime.Region(link_span.a + 1, link_span.b - 1)) + if omit_name: + view.replace(edit, link_span, '[]') + link_span = sublime.Region(link_span.a + 1, link_span.a + 1) + offset = len(link) + else: + view.replace(edit, link_span, '[%s]' % name) + link_span = sublime.Region(link_span.a + 1, link_span.a + 1 + len(name)) + offset = len(link) - len(name) + view.sel().add(link_span) + view.show_at_center(link_span) + + _viewsize = view.size() + view.insert(edit, _viewsize, '[%s]: %s\n' % (name, link)) + reference_span = sublime.Region(_viewsize + 1, _viewsize + 1 + len(name)) + view.sel().add(reference_span) + return offset + + +class ConvertInlineLinkToReferenceCommand(MDETextCommand): + """Convert an inline link to reference.""" + + def is_visible(self): + """Return True if cursor is on a marker or reference.""" + for sel in self.view.sel(): + scope_name = self.view.scope_name(sel.b) + if hasScope(scope_name, 'meta.link.inline.markdown'): + return True + return False + + def run(self, edit, name=None): + """Run command callback.""" + view = self.view + pattern = r"\[([^\]]+)\]\((?!#)([^\)]+)\)" + + # Remove all whitespace at the end of the file + whitespace_at_end = view.find(r'\s*\z', 0) + view.replace(edit, whitespace_at_end, "\n") + + # If there is not already a reference list at the end, insert a new line at the end + if not view.find(r'\n\s*\[[^\]]*\]:.*\s*\z', 0): + view.insert(edit, view.size(), "\n") + + link_spans = [] + + for sel in view.sel(): + scope_name = view.scope_name(sel.b) + if not hasScope(scope_name, 'meta.link.inline.markdown'): + continue + start = findScopeFrom(view, sel.b, marker_begin_scope_name, backwards=True) + end = findScopeFrom(view, sel.b, 'punctuation.definition.metadata.markdown', char=')') + 1 + text = view.substr(sublime.Region(start, end)) + m = re.match(pattern, text) + if m is None: + continue + text = m.group(1) + link = m.group(2) + link_span = sublime.Region(start + m.span(2)[0] - 1, start + m.span(2)[1] + 1) + if is_url(link): + link = mangle_url(link) + if len(link) > 0: + if name is None: + # If link already exists, reuse existing reference + suggested_name = check_for_link(view, link) + if suggested_name is None: + is_image = view.substr(start - 1) == '!' if start > 0 else False + suggested_name = suggest_default_link_name(text, is_image) + + _name = name if name is not None else suggested_name + link_spans.append((link_span, _name, _name == text)) + + offset = 0 + for link_span in link_spans: + _link_span = sublime.Region(link_span[0].a + offset, link_span[0].b + offset) + offset -= convert2ref(view, edit, _link_span, link_span[1], link_span[2]) + + +class ConvertInlineLinksToReferencesCommand(MDETextCommand): + """Convert inline links to references.""" + + def run(self, edit): + """Run command callback.""" + view = self.view + pattern = r"(?<=\]\()(?!#)([^\)]+)(?=\))" + + _sel = [] + for sel in view.sel(): + _sel.append(sel) + view.sel().clear() + view.sel().add_all(view.find_all(pattern)) + view.run_command('convert_inline_link_to_reference')