[Obsidian] で Kiil and Yank をクリップボードを使って実装した
なぜかObsidianでは、^K
と^Y
による"Kill"&"Yank"に対応していません。正確に言うと^K
で"Kill"は動くのですが、消すだけです。ペーストできないんですね。
そこでいのうえたくや氏のobsidian-kill-and-yankを基にして、kill & yank機能を拡充して、IMEの状態チェックとクリップボードへの対応しました。
いのうえたくや氏に感謝。
[Obsidian] で kill line と yank したいでお話した通りですが、リファクタリングもすんで、いったん良い状況になりましたので、こちらで紹介と説明してpull requestしたい流れです。
現時点での完成したソースコードはこちらです。更新分について説明します。
import { Editor, EditorPosition, MarkdownView, Plugin } from 'obsidian'
import { EditorView } from '@codemirror/view'
export default class KillAndYankPlugin extends Plugin {
private editor: Editor
private killRing: string
private mark: EditorPosition | null = null
private isComposing(view: MarkdownView): boolean {
// @ts-expect-error
const editorView = view.editor.cm as EditorView
// console.log(`composing = ${editorView.composing}`);
return editorView.composing
}
private isMark(editor: Editor): boolean {
if (this.mark) {
editor.setSelection(this.mark, editor.getCursor())
return true
}
return false
}
async onload() {
this.addCommand({
id: 'kill-line',
name: 'Kill line (Cut from the cursor position to the end of the line)',
hotkeys: [{ modifiers: ['Ctrl'], key: 'k' }],
editorCallback: (editor: Editor, view: MarkdownView) => {
if (this.isComposing(view)) return
const position: EditorPosition = editor.getCursor()
const line: string = editor.getLine(position.line)
const textToBeRetained = line.slice(0, position.ch)
const textToBeCut = line.slice(position.ch)
// this.killRing = textToBeCut
navigator.clipboard.writeText(textToBeCut)
editor.setLine(position.line, textToBeRetained)
editor.setCursor(position, position.ch)
},
})
this.addCommand({
id: 'kill-region',
name: 'Kill region (Cut the selection)',
hotkeys: [{ modifiers: ['Ctrl'], key: 'w' }],
editorCallback: (editor: Editor, view: MarkdownView) => {
if (this.isComposing(view)) return
this.mark = this.isMark(editor) ? null : null
// this.killRing = editor.getSelection()
navigator.clipboard.writeText(editor.getSelection())
editor.replaceSelection('')
},
})
this.addCommand({
id: 'yank',
name: 'Yank (Paste)',
hotkeys: [{ modifiers: ['Ctrl'], key: 'y' }],
editorCallback: (editor: Editor, view: MarkdownView) => {
if (this.isComposing(view)) return
navigator.clipboard.readText().then((text) => {
editor.replaceSelection(text)
})
// editor.replaceSelection(this.killRing)
},
})
this.addCommand({
id: 'set-mark',
name: 'Set mark (Toggle the start position of the selection)',
hotkeys: [{ modifiers: ['Ctrl'], key: ' ' }],
editorCallback: (editor: Editor, view: MarkdownView) => {
this.mark = this.isMark(editor) ? null : editor.getCursor()
},
})
}
onunload() {}
}
IME 対応
たまたま、^K
や^Y
に日本語入力時のキーカスタマイズがかぶっていたため、Kill-and-yankにてIMEの状態がONの時には実行せず、という機能を追加しました。そのチェック部分がこちらのisComposing
です。IMEがON(True)/OFF(False)の状態をチェックして、その結果をBOOLEAN
で返却するだけです。
import { EditorView } from '@codemirror/view'
:
:
private isComposing(view: MarkdownView): boolean {
// @ts-expect-error
const editorView = view.editor.cm as EditorView
// console.log(`composing = ${editorView.composing}`);
return editorView.composing
}
Obsidian標準のライブラリには入っていなかったので、基のエディタ側であるCodeMirrorのライブラリをimportして利用するようになっています。
※ 注意事項と使い方 : Communicating with editor extensions
後は、editorCallback
の直後にif (this.isComposing(view)) return
を入れておけば、IMEがOFFの時にしか作動しなくなるというだけの仕組みです。
editorCallback: (editor: Editor, view: MarkdownView) => {
if (this.isComposing(view)) return
クリップボード対応
基のソースコードですと、別のワークエリアを準備して^K
と^Y
をクリップボードとは別の独立したエリアで実現していました。ただ、個人的に^Y
でクリップボードの内容をペーストしたい気持ちがありました。ので、クリップボードへ対応しました。
ここは標準のクリップボード機能を利用します。^K
と^W
で、いわゆるコピーをする場合にはnavigator.clipboard.writeText()
を利用します。ここで、コピーしたい内容が含まれているテキストを、関数の引数にしてあげるだけです。カンタン。
navigator.clipboard.writeText(editor.getSelection())
^Y
でペーストするときは、このクリップボードの情報をnavigator.clipboard.readText()
で読み出して、画面へ出力してあげることになります。このnavigator.clipboard.readText()
は、Promise
で値が返却されるので、await
させるか、今回のようにPromise
っぽく処理してあげるとよくなります。
navigator.clipboard.readText().then((text) => {
editor.replaceSelection(text)
})
今後の予定
ふと気がついたけど、^W
だけじゃあれなのでM-w
もあったら良さそうな気がしますね。なるほど。
Discussion