Skip to content

Instantly share code, notes, and snippets.

@UltraInstinct05
Last active June 1, 2021 02:58
Show Gist options
  • Save UltraInstinct05/53acd9f84bb06e5a0d0ff37dead10c58 to your computer and use it in GitHub Desktop.
Save UltraInstinct05/53acd9f84bb06e5a0d0ff37dead10c58 to your computer and use it in GitHub Desktop.
@RoyiAvital
Copy link

RoyiAvital commented May 31, 2021

OK, I tried to learn how to write a plug in and this is what I came up with:

import sublime
import sublime_plugin
import re
import math

# Definition Text: [RefID]: <Url Address> <Url Tooltip>
# See https://regex101.com/r/P9Pm9Z/latest
REG_EX_URL_ADD_DEF_TEXT = r"]:\s([^\s]*)\s?"

# Link Text: [some text][RefID]
# See https://regex101.com/r/iU4GlD/latest
REG_EX_REF_ID_DEF_TEXT = r"\[.*?\]\[([\d]*)\]"


def ExtractUrlAddressDefText(defString):
    # Definition Text: [RefID]: <Url Address> <Url Tooltip>
    # See https://regex101.com/r/P9Pm9Z/latest
    httpRegExPattern = r"]:\s([^\s]*)\s?"
    httpRegEx = re.compile(httpRegExPattern)
    srchResult = httpRegEx.search(defString)
    if srchResult:
        return srchResult.group(1)
    else:
        return ""

def ExtractRefIdLinkText(linkString):
    # Link Text: [some text][RefID]
    # See https://regex101.com/r/iU4GlD/latest
    idRegRxPattern = r"\[.*?\]\[([\d]*)\]"
    idRegRx = re.compile(idRegRxPattern)
    return idRegRx.search(linkString).group(1)

def ExtractRegExMatch(inputString, regExPattern):
    regEx = re.compile(regExPattern)
    regExSrch = regEx.search(inputString)
    if regExSrch:
        return regExSrch.group(1)
    else:
        return ""

def DefinitionTemplate(linkIdx, linkUrl, numDigits):
        # See Parametrized Formats in https://pyformat.info/
        return "[" + RefIdTemplate(linkIdx, numDigits) + "]" + ": {}".format(linkUrl)

    # @staticmethod
def LinkTemplate(linkText, linkIdx, numDigits):
    # See Parametrized Formats in https://pyformat.info/
    if linkText == "":
        return "[Enter Link Text]" + "[" + RefIdTemplate(linkIdx, numDigits) + "]"
    return "[{}]".format(linkText) + "[" + RefIdTemplate(linkIdx, numDigits) + "]"

def RefIdTemplate(idIdx, numDigits):
    # See Parametrized Formats in https://pyformat.info/
    return "{:0{}}".format(idIdx, numDigits)

class ReferenceLinkInputHandler(sublime_plugin.TextInputHandler):

    def placeholder(self):
        return "Type a URL"


class ReferenceLinkCommand(sublime_plugin.TextCommand):
    # See https://www.youtube.com/watch?v=Ohb_pYwO5wE

    def run(self, edit, reference_link):
        # reference_link - The input text of the user

        defNumLeadingSpaces = 2
        defNumDigits        = 3

        # ref_links = self.view.find_by_selector("meta.link.reference.def.markdown") # Doesn't catch all cases

        # Catch the Definition ([Identifier]: <Link>) (Will catch inside ```block```, needs to be taken of)
        # lRefDef = self.view.find_all("^[\s]{0,2}(\[[\d]{0,}\]):") # See https://regex101.com/r/iopdag/latest
        lRefDef = self.view.find_all(r"^[\s]{0,2}\[([\d]*)\]:") # See https://regex101.com/r/iopdag/latest
        # Catch the link ([Link Text][Identifier]) (Will catch inside ```block``` and `text`, needs to be taken of)
        lRefLinks = self.view.find_all(r"\[.*?\](\[[\d]{0,}\])") # See https://regex101.com/r/L8GgbZ/latest

        lRefLinksLine = []

        for ii in lRefDef:
            print(self.view.substr(ii))

        numLinks = len(lRefDef)

        maxNumDigits = 1
        if(numLinks > 0):
            maxNumDigits = math.ceil(math.log(numLinks, numLinks))

        self.numLeadingSpaces   = defNumLeadingSpaces
        self.numDigits          = max(maxNumDigits, defNumDigits)

        addRefDef = True
        linkIdx = numLinks + 1
        if numLinks > 0:
            for iRegion in lRefDef:
                # urlAddress = ExtractUrlAddressDefText(self.view.substr(self.view.line(iRegion)))
                urlAddress = ExtractRegExMatch(self.view.substr(self.view.line(iRegion)), REG_EX_URL_ADD_DEF_TEXT)
                # print(urlAddress)
                if urlAddress == reference_link:
                    # print("Found a Match")
                    linkIdx = int(self.view.substr(iRegion).strip(" \n\t[]:"))
                    addRefDef = False
    
        lRefDef = [['001', reference_link, linkIdx]] # List of lists of the form [ID, urlText, Index]
        if addRefDef:
            self.WriteLinksDefinitions(edit, lRefDef, self.view.size())
        selTextPoint = self.view.sel()[0]
        selTextString = self.view.substr(selTextPoint)
        self.view.replace(edit, selTextPoint, LinkTemplate(selTextString, linkIdx, self.numDigits))


    def is_enabled(self):
        # Should be disabled for non Mark Down (Or no text selection?)
        return self.view.match_selector(0, "text.html.markdown") #<! Anoter option by self.view.syntax().name == "Markdown"

    # def is_visible(self, text, pos, event=None):
    #     # Should be disabled for non Mark Down (Or no text selection?)
    #     return True

    # def description(self, text, pos, event=None):
    #     # Only for dedicated menu
    #     return ""

    # def want_event(self):
    #     return False

    def input(self, args):
        if args is not None:
            return ReferenceLinkInputHandler()

    # def input_description(self):
    #     return "https://"

    def WriteLinksDefinitions(self, edit, lRefDef, writeIndex):
        # 
        leadingSpaces = " " * self.numLeadingSpaces
        for ii in range(len(lRefDef)):
            insertString = "\n" + leadingSpaces + DefinitionTemplate(lRefDef[ii][2], lRefDef[ii][1], self.numDigits)
            self.view.insert(edit, writeIndex, insertString)
            writeIndex = writeIndex + len(insertString)


class FormatLinksCommand(sublime_plugin.TextCommand):
    # See https://www.youtube.com/watch?v=Ohb_pYwO5wE

    def run(self, edit):
        # reference_link - The input text of the user
        # Use dictionary to set the Ref ID by order {ID: Order}

        numLeadingSpaces    = 2
        numDigits           = 3

        # Catch the link ([Link Text][Identifier]) (Will catch inside ```block``` and `text`, needs to be taken of)
        lRefLinksReg = self.view.find_all(r"\[.*?\](\[[\d]{0,}\])") # See https://regex101.com/r/L8GgbZ/latest

        lRefLinks = list()

        for iRegion in lRefLinksReg:
            # lRefLinks.append(ExtractRefIdLinkText(self.view.substr(iRegion)))
            lRefLinks.append(ExtractRegExMatch(self.view.substr(iRegion)), REG_EX_REF_ID_DEF_TEXT)

        # Reference ID by order 
        lRefLinksUnique = list(dict.fromkeys(lRefLinks)) #<! See https://stackoverflow.com/a/48028065
        # print(lRefLinksUnique)

        # Update Ref ID to the format
        for iRegion in lRefLinksReg:
            refIdRegion = self.view.find(r"\[[\d]*\]", iRegion.begin())
            refIdRegion.a += 1
            refIdRegion.b -= 1
            # refIdRegion = sublime.Region(refIdRegionTmp.begin() + 1, refIdRegionTmp.end() - 1)
            # print(self.view.substr(refIdRegion))
            refIdIndex = lRefLinksUnique.index(self.view.substr(refIdRegion)) + 1
            self.view.replace(edit, refIdRegion, RefIdTemplate(refIdIndex, numDigits))

        # Extract Reference Definition
        # lRefDef = self.view.find_all(r"^[\s]{0,2}\[([\d]*)\]:") # See https://regex101.com/r/iopdag/latest
        # Pay attnetion that `\s` is not only whitespace but the class of whitespace (Includes newline)
        lRefDef = self.view.find_all(r"^ {0,2}\[\d+\]:") # See https://regex101.com/r/yWdBFL/latest

        # For cases for ID without address
        lRefLinksUniqueUrl = [" \n"] * len(lRefLinksUnique) #<! For each one we'll have a Ref Def

        for iRegion in lRefDef:
            lineRegion = self.view.full_line(iRegion) #<! The whole line region
            refIdRegion = self.view.find(r"\[[\d]*\]", iRegion.begin())
            refIdRegion = sublime.Region(refIdRegion.begin() + 1, refIdRegion.end() - 1)
            urlRegion = sublime.Region(iRegion.end() + 1, lineRegion.end()) #<! Everything after ":"
            if self.view.substr(refIdRegion) in lRefLinksUnique:
                urlIndex = lRefLinksUnique.index(self.view.substr(refIdRegion))
                lRefLinksUniqueUrl[urlIndex] = self.view.substr(urlRegion)

        # Delete all lines of lRefLinksReg (Use self.view.full_line())
        # Must be done in reverse so regions are still valid
        for iRegion in reversed(lRefDef):
            self.view.erase(edit, self.view.full_line(iRegion))

        # Build the Def List and append at the end of the buffer
        leadingSpaces = " " * numLeadingSpaces
        self.view.insert(edit, self.view.size(), "\n")
        for ii, iRefUrl in enumerate(lRefLinksUniqueUrl):
            insertString = leadingSpaces + DefinitionTemplate(ii + 1, iRefUrl, numDigits) #<! The "\n" at the end is already in `iRefUrl`
            self.view.insert(edit, self.view.size(), insertString)

    def ExtractUrlById(self, lRefId):
        # Extract all 
        return

    def is_enabled(self):
        # Should be disabled for non Mark Down (Or no text selection?)
        return self.view.match_selector(0, "text.html.markdown") #<! Another option by self.view.syntax().name == "Markdown"

@RoyiAvital
Copy link

My code can format existing list to have a proper order and style. It can also add a new link.

@UltraInstinct05
Copy link
Author

That's great to see ! 🙂 My example plugin was mainly to give a starting point (since I don't work with reference links in Markdown all that much).

@RoyiAvital
Copy link

Yep, Your code was a great starting point for me. Thank You.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment