## 概要
カーソルよりも後方で最も近いリンクを開きたい。たとえば、`♦`がカーソル位置として
```
[[huga]]
♦ hoge https://hoge huga [[huga]]
https://fufu
```
のような状態でコマンドを実行したら `https://hoge`を開きたい。
```
hoge https://hoge huga [[huga]]
https://fufu ♦
- [[huga]]
```
なら `[[huga]]`を開きたい。
以下のような方法もあるが、それぞれ課題を感じる。
- リンクまで移動して、[[Follow link under cursor]]
- 移動するのが面倒
- [[Jump to link]]プラグインを使う
- 2アクション必要
- 表示されるシンボルを認識して、ボタンを押すのが面倒
- [[🦉Another Quick Switcher]]で`target`を`link`にする
- 2アクション以上必要
- カーソル位置の後方から近い順には候補が表示されない
- [[URL]]が候補に含まれない
## ソリューション
2つ方法があります。
### [[🦉Shukuchi]]を使う
[[🦉tadashi-aikawaが開発しているObsidianプラグイン]]である[[🦉Shukuchi]]を使います。
> [!info] [[🦉Shukuchi]]を使う方法がオススメ
> [[🦉Shukuchi]]の実装は[[#Templater を使う]]方法をベースとして、更なる機能を提供しています。
### [[Templater]]を使う
[[Templater]]でオリジナルコマンドを作る方法です。以下のような挙動になります。
![[2023-02-11-18-45-33.mp4]]
#### やり方
[[Templater]]を使って以下のスクリプトを作成します。
```js
<%*
tp.user.open_next_occurrence_link(false)
%>
```
`open_next_occurrence_link`は[[Script User Functions]]です。以下のような[[JavaScript]]ファイルを作成し、[[Script User Functions]]としてあらかじめ登録が必要です。
`openNextOccurrenceLink.js`
```js
function sorter(toOrdered, order = "asc") {
return (a, b) =>
order === "asc"
? toOrdered(a) > toOrdered(b)
? 1
: toOrdered(b) > toOrdered(a)
? -1
: 0
: toOrdered(a) < toOrdered(b)
? 1
: toOrdered(b) < toOrdered(a)
? -1
: 0;
}
// newLeaf: boolean
// trueなら新しいLeafで開く
function openNextOccurrenceLink(newLeaf) {
const editor = app.workspace.activeLeaf.view.editor;
const linksMatches = Array.from(
editor.getValue().matchAll(/(?<link>\[\[[^\]]+]])/g)
);
const internalLinkPositions = linksMatches.map((x) => ({
start: x.index,
end: x.index + x.groups.link.length,
}));
const urlsMatches = Array.from(
editor.getValue().matchAll(/(?<= |\n|^|\()(?<url>https?:\/\/[^ )\n]+)/g)
);
const externalLinkPositions = urlsMatches.map((x) => ({
start: x.index,
end: x.index + x.groups.url.length,
}));
const currentOffset = editor.posToOffset(editor.getCursor());
const target = [...internalLinkPositions, ...externalLinkPositions]
.sort(sorter((x) => x.start))
.find((x) => x.end >= currentOffset);
if (!target) {
return;
}
if (currentOffset <= target.start || currentOffset > target.end) {
editor.setCursor(editor.offsetToPos(target.start + 1));
}
app.commands.executeCommandById(
newLeaf ? "editor:open-link-in-new-leaf" : "editor:follow-link"
);
}
module.exports = openNextOccurrenceLink;
```
これを[[Follow link under cursor]]と同じ[[ホットキー (Obsidian)|ホットキー]]として代わりに登録すればOKです。
> [!hint]
> [[Open link under cursor in new tab]]も同様に、`tp.user.open_next_occurrence_link(true)` と記載されたスクリプトを使うことで実現できます。
> [!info] コマンドにホットキーを登録する方法
> [[Templaterで作成したコマンド(スクリプト)をホットキーで実行する方法]] を参考にしてください。
#### 別の仕様
以下のような優先順位仕様にもできます。好みで好きな方をどうぞ。
1. 同一行のカーソルと距離が近いリンク
2. 同一行ではないカーソルと距離が近いリンク
この場合は後方かどうかは関係しません。行に移動して傍にあるリンクを開きたい場合はこの方が便利かもしれません。
```js
function sorter(toOrdered, order = "asc") {
return (a, b) =>
order === "asc"
? toOrdered(a) > toOrdered(b)
? 1
: toOrdered(b) > toOrdered(a)
? -1
: 0
: toOrdered(a) < toOrdered(b)
? 1
: toOrdered(b) < toOrdered(a)
? -1
: 0;
}
function openNearOccurrenceLink(newLeaf) {
const editor = app.workspace.activeLeaf.view.editor;
const linksMatches = Array.from(
editor.getValue().matchAll(/(?<link>\[\[[^\]]+]])/g)
);
const internalLinkPositions = linksMatches.map((x) => ({
start: x.index,
end: x.index + x.groups.link.length,
line: editor.offsetToPos(x.index).line,
}));
const urlsMatches = Array.from(
editor.getValue().matchAll(/(?<= |\n|^|\()(?<url>https?:\/\/[^ )\n]+)/g)
);
const externalLinkPositions = urlsMatches.map((x) => ({
start: x.index,
end: x.index + x.groups.url.length,
line: editor.offsetToPos(x.index).line,
}));
const cursor = editor.getCursor();
const currentOffset = editor.posToOffset(cursor);
const target = [...internalLinkPositions, ...externalLinkPositions].sort(
sorter(
(x) =>
Math.min(
Math.abs(x.start - currentOffset),
Math.abs(x.end - currentOffset)
) + (x.line !== cursor.line ? 10000 : 0)
)
)?.[0];
if (!target) {
return;
}
if (currentOffset <= target.start || currentOffset > target.end) {
editor.setCursor(editor.offsetToPos(target.start + 1));
}
app.commands.executeCommandById(
newLeaf ? "editor:open-link-in-new-leaf" : "editor:follow-link"
);
}
module.exports = openNearOccurrenceLink;
```