Generate Link @ Cursor Position

Related to I wonder if SilverBullet could take advantage of the “URL text fragment” standard 🤔 and Getting the start of line where cursor is?

I assume there are a lot of people who have secretly implemented this? hhhh… Are there many people like me who love to goto anywhere in their documents? XD

command.define {
  name = "Cursor: Copy Reference",
  key = "Shift-Alt-c",
  run = function()
    local pageName = editor.getCurrentPage()
    local pos = editor.getCursor()
    local ref = string.format("[[%s@%d]]", pageName, pos)
    editor.copyToClipboard(ref)
    editor.flashNotification("Copied reference: " .. ref, "info")
  end
}

Compared to the alternative of planting one immutable (but invisible) anchor and jumping to it, the current technique streamlines operations but raises maintenance concerns if the document is edited (pos-word map might change).

Hence, a smarter method — like the one referenced in I wonder if SilverBullet could take advantage of the “URL text fragment” standard 🤔
or an equivalent implementation — may be warranted (from SB directly?) :

SilverBullet’s backlinks effectively re-evaluate cursor positions so they update when the source moves (However, it seems that when [[A Page]] appears at the end of B Page, the backlink from A Page to B Page does likely not return to the correct position: v2.1.9 tiny bug - the cursor does, but the view does not: view always at the top. emmmmmmmmm, Now I cannot replicate this bug -_-||).

In essence, there are three precise forms of reference, with stability typically increasing in that order:

  1. addressing a specific character position or line;
  2. referencing a given piece of text (or a textual pattern);
  3. referencing a visible or invisible label/anchor.

I previously implemented an automated backlinks-enabled, yet LaTeX-based version of strategy 3. Is anyone interested in achieving a similar effect (i.e. Cursor-level fine-grained forwardLink with Automatic BackLinks) in SilverBullet?

1 Like

An analogy to help @zef and those intrigued by this intellectual exercise understand:

At present, the Linked Mentions and Linked Tasks widgets of a given [[Page A]] are essentially Cursor-level precise backlinks — each pointing from the [[Page A]] to a specific cursor position within other[[Page ?]]s.

Now, imagine that these 2 backlink widgets are no longer fixed at the top or bottom of a [[Page A]], but can instead float freely.
In that case, they no longer belong to a particular Page Object [[Page A]], but rather to a designated Anchor Object [[Page A@pos]].

This instantly yields automatic backlinks for “floating” [[Page A@pos]] (rather than “fixed” [[Page A]]), since it reuses the precise backlinks mechanism already implemented in Library/Std/Widgets/Widgets.

Furthermore, the former forward/outward links (from [[Page ?]]s) to [[Page A]] would now all become links to [[Page A@pos]]. Note that this simply requires extending the index to also record and update the pos and corresponding [[Page A@pos]] name — which is quite feasible, given that Library/Std/Widgets/Widgets already employs a similar mechanism in Auto-Backlinks.

Beyond this, nothing more is required — it is essentially a fine-grained Cursor-level extension of the existing Page-level system. And although it may appear somewhat recursive, there is no actual loop involved.

Believe me, this will be the next killer feature of SB v2, second only to Linked Tasks: fully automated, cursor-level precise for/out-ward and back/in-ward linking — something almost no other note-taking software/website/app has achieved.

I have already implemented this exact functionality in the supplementary materials of my short paper as well as in my PhD dissertation. Since it can be realized in the LaTeX ecosystem, it is certainly achievable in SilverBullet.

1/2 of the journey remains:

manual Forwardlink to a [[Certain Page]]
auto Backlink s to [[Other Pages@pos]] s with auto-updated pos s from a [[Certain Page]]
auto Backlink s to [[Other Pages@pos_O]] s with auto-updated pos_O s from a [[Certain Page@pos_C]]
manual Forwardlink to a [[Certain Page@pos_C]] with auto-updated pos_C (without s !)

Note that once the 4 points above are implemented, the referencing positions of [[Other Pages@pos_O]] s may themselves reside within the [[Certain Page]] which contains the anchor [[Certain Page@pos_C]] that is cited. This signifies a complete transition away from the Page-based paradigm.

Keep going, @zef — this “will definitely make SB great again”!

In essence, what we are doing is reinventing Ted_Nelson
’s concept of bidirectional HTML.

It feels somewhat akin to a “block web” (similar to “block chain”).

I have a faint yet precise intuition that: SilverBullet serves as an excellent demonstrative platform for this philosophy.

1 Like

Forester? :slight_smile:

2 Likes

Almost there!
Very impressive and Close enough!

But… we need more precise links!
How precise should it be?

Pretty much like the feeling when you Ctrl + Enter or Click a [[Page]],

you can precisely Alt + ← jumping back (~ AutoBackLink) to where you quoted/cited/refered at Cursor level,

and then Alt + → jumping forward (~ ManualForwardLink) once again at Cursor level.

Update: When the cursor is located inside a header, produce the format [[pageName#headerName]] rather than [[pageName@pos]].

command.define {
  name = "Cursor: Copy Reference",
  key = "Shift-Alt-c",
  run = function()
    local currentLine = editor.getCurrentLine().textWithCursor:gsub("|%^|", "")
    local pageName = editor.getCurrentPage()
    local pos = editor.getCursor()
    local headerMarks, headerName = string.match(currentLine, "^(#+) +(.+)$")
    -- editor.flashNotification(headerMarks .. headerName)
    local ref
    if headerMarks and headerName and headerName:match("%S") then
      headerName = headerName:match("^%s*(.+)")
      ref = string.format("[[%s#%s]]", pageName, headerName)
      editor.flashNotification("Copied header reference: " .. ref, "info")
    else
      ref = string.format("[[%s@%d]]", pageName, pos)
      editor.flashNotification("Copied cursor reference: " .. ref, "info")
    end

    editor.copyToClipboard(ref)
  end
}

Borrowed some tech editor.getCurrentLine().textWithCursor:gsub("|%^|", "") (see editor) from Copy the nearest Format around Cursor - #2 by ChenZhu-Xie

Copy (as) External Link version

-- [[Page#Header]] -> http(s)://host/Page#Header
-- [[Page@pos]]    -> http(s)://host/Page@pos

local function replace_space_with_percent20(s)
  local parts = {}
  for i = 1, #s do
    local c = s:sub(i, i)
    if c == " " then
      parts[#parts+1] = "%20"
    else
      parts[#parts+1] = c
    end
  end
  return table.concat(parts)
end

-- your address:   "https://your-domain"
local BASE_URL = "http://127.0.0.1:3000"

local function build_page_url(pageName)
  local path = replace_space_with_percent20(pageName)
  return string.format("%s/%s", BASE_URL, path)
end

command.define {
  name = "Cursor: Copy Link",
  key = "Ctrl-Shift-c",
  run = function()
    local currentLine = editor.getCurrentLine().text
    local pageName = editor.getCurrentPage()
    local pos = editor.getCursor()
    local headerMarks, headerName = string.match(currentLine, "^(#+) +(.+)$")
    
    local pageUrl = build_page_url(pageName)
    local out
    if headerMarks and headerName and headerName:match("%S") then
      headerName = headerName:match("^%s*(.+)")
      headerName = replace_space_with_percent20(headerName)
      out = string.format("%s#%s", pageUrl, headerName)
      editor.flashNotification("Copied header external link: " .. out, "info")
    else
      out = string.format("%s@%d", pageUrl, pos)
      editor.flashNotification("Copied cursor external link: " .. out, "info")
    end
  
    editor.copyToClipboard(out)
  end
}

Cursor Position Link Generator

This script provides functionality to generate and copy links to specific cursor positions and headers within markdown documents. It’s particularly useful for creating precise references within a document or across multiple documents.

Features

  • External Links: Generate full URLs pointing to specific cursor positions or headers
  • Internal Links: Create markdown-style internal links for use within the same document

Usage

External Links (Ctrl+Shift+C)

Copies a full URL to the current cursor position or header to the clipboard.

  • If the cursor is on a header line: Creates a URL with the header as an anchor (e.g., https://your-host/Page#Header Name)
  • If not on a header: Creates a URL with the cursor position (e.g., https://your-host/Page@123)

Internal Links (Ctrl+Shift+L)

Copies a markdown-style internal link to the current cursor position or header.

  • If on a header: Creates a link like [Header Name@123]
  • If not on a header: Creates a link like [Page Name@123]

Source

-- [[Page#Header]] -> http(s)://host/Page#Header
-- [[Page@pos]]    -> http(s)://host/Page@pos

local function replace_space_with_percent20(s)
  local parts = {}
  for i = 1, #s do
    local c = s:sub(i, i)
    if c == " " then
      parts[#parts+1] = "%20"
    else
      parts[#parts+1] = c
    end
  end
  return table.concat(parts)
end

local function build_page_url(pageName)
  -- get direct url from js
  local BASE_URL = js.window.location.origin
  local path = replace_space_with_percent20(pageName)
  if BASE_URL:sub(-1) == "/" then
    return BASE_URL .. path
  else
    return BASE_URL .. "/" .. path
  end
end

command.define {
  name = "Cursor: Copy external link",
  key = "Ctrl-Shift-c",
  run = function()
    local currentLine = editor.getCurrentLine().text
    local pageName = editor.getCurrentPage()
    local pos = editor.getCursor()
    local headerMarks, headerName = string.match(currentLine, "^(#+) +(.+)$")
    
    local pageUrl = build_page_url(pageName)
    local out
    if headerMarks and headerName and headerName:match("%S") then
      headerName = headerName:match("^%s*(.+)")
      headerName = replace_space_with_percent20(headerName)
      out = string.format("%s#%s", pageUrl, headerName)
      -- editor.flashNotification("Copied header external link: " .. out, "info")
      editor.flashNotification("Copied header link: " .. out, "info")
    else
      out = string.format("%s@%d", pageUrl, pos)
      -- editor.flashNotification("Copied cursor external link: " .. out, "info")
      editor.flashNotification("Copied cursor link: " .. out, "info")
    end
  
    editor.copyToClipboard(out)
  end
}


command.define {
  name = "Cursor: Copy internal link",
  key = "Ctrl-Shift-l",
  run = function()
    local currentLine = editor.getCurrentLine().text
    local pageName = editor.getCurrentPage()
    local pos = editor.getCursor()
    local headerMarks, headerName = string.match(currentLine, "^(#+) +(.+)$")
    
    local out
    if headerMarks and headerName and headerName:match("%S") then
      headerName = headerName:match("^%s*(.+)")
      out = string.format("[[%s@%s]]", replace_space_with_percent20(headerName), pos)
      editor.flashNotification("Copied header internal link: " .. out, "info")
    else
      out = string.format("[[%s@%d]]", replace_space_with_percent20(pageName), pos)
      editor.flashNotification("Copied cursor internal link: " .. out, "info")
    end
  
    editor.copyToClipboard(out)
  end
}

No parameters needed :slight_smile:

1 Like