### Plugin Initialization and Lifecycle in TypeScript Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt This snippet shows the `onload` method of the main Obsidian plugin class. It handles loading settings, verifying Neovim installation, creating a Neovim instance, registering event handlers for file opening and application quit, and adding a command to manually open Neovim. ```typescript import { Plugin, FileSystemAdapter } from "obsidian"; import Neovim from "./Neovim"; import { EditInNeovimSettings, DEFAULT_SETTINGS } from "./Settings"; export default class EditInNeovim extends Plugin { settings: EditInNeovimSettings; neovim: Neovim; async onload() { // Load saved settings or use defaults await this.loadSettings(); // Verify neovim is installed this.pluginChecks(); // Get vault adapter for file system access const adapter = this.app.vault.adapter as FileSystemAdapter; // Create Neovim instance with REST API key if available this.neovim = new Neovim( this.settings, adapter, this.restAPIEnabled() ); // Automatically spawn Neovim terminal if configured if (this.settings.openNeovimOnLoad) { this.neovim.newInstance(adapter); } // Register file-open event to sync with Neovim this.registerEvent( this.app.workspace.on("file-open", this.neovim.openFile) ); // Register quit event for cleanup this.registerEvent( this.app.workspace.on("quit", this.neovim?.close) ); // Add settings tab this.addSettingTab(new EditInNeovimSettingsTab(this.app, this)); // Add manual Neovim open command this.addCommand({ id: "edit-in-neovim-new-instance", name: "Open Neovim", callback: async () => { await this.neovim.newInstance(adapter); // Wait for Neovim to be ready before opening file setTimeout( () => this.neovim.openFile( this.app.workspace.getActiveFile() ), 1000 ); } }); } async loadSettings() { this.settings = Object.assign( {}, DEFAULT_SETTINGS, await this.loadData() ); } } // Default configuration const DEFAULT_SETTINGS = { terminal: process.env.TERMINAL || "alacritty", listenOn: "127.0.0.1:2006", openNeovimOnLoad: true, supportedFileTypes: ["txt", "md", "css", "js", "ts", "tsx", "jsx", "json"], pathToBinary: "", appname: "" }; ``` -------------------------------- ### Create New Neovim Instance with TypeScript Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt Spawns a new terminal process running Neovim, configured to listen on a specified address for remote commands. It handles process verification, environment variable setup, and platform-specific spawn arguments for both Unix-like systems and Windows Terminal. Includes error handling and RPC connection verification. ```typescript import { spawn } from "node:child_process"; import { attach, findNvim } from "neovim"; import { FileSystemAdapter, Notice } from "obsidian"; class Neovim { async newInstance(adapter: FileSystemAdapter) { // Prevent multiple instances if (this.process) { new Notice("edit-in-neovim:\nInstance already running", 5000); return; } // Verify terminal and neovim binaries exist if (!this.termBinary || !this.nvimBinary?.path) { new Notice("Terminal or Neovim binary not found", 5000); return; } // Set environment variables const extraEnvVars = {}; if (this.apiKey) { extraEnvVars["OBSIDIAN_REST_API_KEY"] = this.apiKey; } if (this.settings.appname !== "") { extraEnvVars["NVIM_APPNAME"] = this.settings.appname; } // Configure spawn options based on terminal type const terminalName = this.termBinary.split('\\').pop()?.toLowerCase() || ''; const spawnOptions = { spawnArgs: [], cwd: adapter.getBasePath(), env: { ...process.env, ...extraEnvVars }, shell: false, detached: false }; // For Unix-like systems (macOS/Linux) if (process.platform !== 'win32') { spawnOptions.spawnArgs = [ "-e", this.nvimBinary.path, "--listen", this.settings.listenOn ]; spawnOptions.shell = require('os').userInfo().shell || true; } // For Windows Terminal if (terminalName === 'wt.exe') { spawnOptions.spawnArgs = [ 'new-tab', '--title', 'Neovim', this.nvimBinary.path, '--listen', this.settings.listenOn ]; } // Spawn the process try { this.process = spawn( this.termBinary, spawnOptions.spawnArgs, spawnOptions ); console.debug(`Neovim process running, PID: ${this.process.pid}`); // Handle process events this.process.on("close", (code) => { console.info(`nvim closed with code: ${code}`); this.process = undefined; this.instance = undefined; }); this.process.on("error", (err) => { new Notice("edit-in-neovim:\nNeovim error, see logs"); console.error(`Neovim error: ${JSON.stringify(err, null, 2)}`); this.process = undefined; this.instance = undefined; }); // Attach RPC client to process this.instance = attach({ proc: this.process }); // Verify RPC connection setTimeout(async () => { if (!this.instance) return; try { await this.instance.eval('1'); console.debug("Neovim RPC connection successful."); new Notice("Neovim instance started and connected.", 3000); } catch (error) { console.error("RPC connection failed:", error); new Notice(`RPC connection failed: ${error.message}`, 7000); this.close(); } }, 1500); } catch (error) { console.error("Error spawning Neovim:", error); new Notice(`Error spawning Neovim: ${error.message}`, 10000); this.process = undefined; this.instance = undefined; } } } ``` -------------------------------- ### Open File in Neovim Instance (TypeScript) Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt The openFile method facilitates opening a specified file within a running Neovim instance. It checks for valid inputs, supported file types, and verifies that a Neovim server is active on the configured port before executing the `nvim --remote` command. Error handling is included for common issues like Neovim not being found or connection failures. ```typescript import { TFile } from "obsidian"; import { execFile } from "node:child_process"; import { isPortInUse } from "./utils"; class Neovim { openFile = async (file: TFile | null) => { // Early returns for invalid states if (!file) return; if (!this.nvimBinary?.path) return; // Check if file type is supported const isExcalidrawMd = file.extension === "md" && file.path.endsWith(".excalidraw.md"); let isSupported = this.settings.supportedFileTypes.includes( file.extension ); isSupported = isSupported || ( isExcalidrawMd && this.settings.supportedFileTypes.includes("excalidraw") ); if (!isSupported) return; // Extract port from listen address const port = this.settings.listenOn.split(':').at(-1); // Verify Neovim server is running try { if (!(port && await isPortInUse(port))) { console.debug("No Neovim instance listening on port"); return; } } catch (error) { console.error(`Error checking port ${port}: ${error.message}`); return; } // Get absolute path to file const absolutePath = this.adapter.getFullPath(file.path); const args = [ '--server', this.settings.listenOn, '--remote', absolutePath ]; console.debug(`Opening ${absolutePath} in neovim`); // Execute nvim --remote command execFile(this.nvimBinary.path, args, (error, stdout, stderr) => { if (error) { let noticeMessage = `Error opening file: ${error.message}`; // Handle specific error cases if (error.code === 'ENOENT') { noticeMessage = `Neovim not found at: ${this.nvimBinary.path}`; } else if ( stderr && ( stderr.includes('ECONNREFUSED') || stderr.includes('Connection refused')) ) { noticeMessage = `Could not connect to Neovim at ${this.settings.listenOn}`; } else if ( stderr && stderr.includes("No such file or directory") && stderr.includes(absolutePath) ) { noticeMessage = `File not found: ${file.basename}`; } else if (stderr) { noticeMessage = `Error: ${stderr.split(' ')[0]}`; } new Notice(noticeMessage, 10000); return; } if (stdout) console.log(`Neovim stdout: ${stdout}`); if (stderr) console.warn(`Neovim stderr: ${stderr}`); }); }; } ``` -------------------------------- ### Find Terminal and Neovim Binaries (TypeScript) Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt The searchForBinary utility function locates executables across different operating systems by checking common PATH directories and package manager locations. It verifies if the found path is executable and returns the absolute path or undefined if not found. Dependencies include Node.js path and fs modules. ```typescript import { join, delimiter, normalize, isAbsolute } from 'node:path'; import { accessSync, existsSync, constants } from "node:fs"; const windows = process.platform === 'win32'; export function searchForBinary(name: string): string | undefined { // If absolute path provided, verify it directly if (isAbsolute(name)) { return verifyPath(name); } // Collect all search paths const paths = new Set(); const { PATH, USERPROFILE, LOCALAPPDATA, PROGRAMFILES, HOME } = process.env; // Add PATH environment variable directories PATH?.split(delimiter).forEach(p => paths.add(normalizePath(p)) ); if (windows) { // Add .exe extension on Windows name = `${name}.exe`; // Scoop package manager locations if (USERPROFILE) { paths.add(normalizePath(`${USERPROFILE}/scoop/shims`)); } paths.add(normalizePath('C:/ProgramData/scoop/shims')); // Winget package manager locations if (LOCALAPPDATA) { paths.add(normalizePath(`${LOCALAPPDATA}/Microsoft/WindowsApps`)); paths.add(normalizePath(`${LOCALAPPDATA}/Microsoft/WinGet/Packages`)); } if (PROGRAMFILES) { paths.add(normalizePath(`${PROGRAMFILES}/WinGet/Packages`)); paths.add(normalizePath(`${PROGRAMFILES} (x86)/WinGet/Packages`)); } } else { // Unix-like common paths [ '/usr/local/bin', '/usr/bin', '/opt/homebrew/bin', '/home/linuxbrew/.linuxbrew/bin' ].forEach(p => paths.add(p)); if (HOME) { paths.add(normalizePath(`${HOME}/bin`)); paths.add(normalizePath(`${HOME}/.linuxbrew/bin`)); } } // Search all paths for the binary const allPaths = [...paths].map(p => join(p, name)); for (const path of allPaths) { const verifiedPath = verifyPath(path); if (verifiedPath) { return verifiedPath; } } return undefined; } function verifyPath(name: string): string | undefined { if (!existsSync(name)) { return undefined; } try { // Check if file is executable accessSync(name, constants.X_OK); return name; } catch (e) { console.log(`Invalid binary at ${name}: ${e}`); return undefined; } } function normalizePath(path: string): string { return normalize(windows ? path.toLowerCase() : path); } // Usage example const foundNeovim = searchForBinary("nvim"); if (foundNeovim) { console.log(`Found Neovim at: ${foundNeovim}`); // Result: "/usr/local/bin/nvim" or "C:\\Program Files\\nvim\\bin\\nvim.exe" } const foundTerminal = searchForBinary("alacritty"); if (foundTerminal) { console.log(`Found terminal at: ${foundTerminal}`); // Result: "/usr/bin/alacritty" or "C:\\Users\\Name\\scoop\\shims\\alacritty.exe" } ``` -------------------------------- ### Configure Neovim Spawn Arguments for Terminals (TypeScript) Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt This TypeScript function `configureProcessSpawnArgs` adapts Neovim's process spawning arguments based on the operating system and the specific terminal emulator being used. It handles differences between Unix-like systems (Linux, macOS) and various Windows terminals like Alacritty, WezTerm, Kitty, Windows Terminal, PowerShell, and Command Prompt. It returns a `SpawnProcessOptions` object tailored for the detected environment, including arguments, shell configuration, and other process-related settings. ```typescript '--listen', port ]; spawnOptions.shell = true; return spawnOptions; } // Fallback for unknown terminals console.warn(`Unknown terminal: ${terminalPath}. Using fallback config.`); spawnOptions.spawnArgs = ['-e', nvimPath, '--listen', port]; return spawnOptions; } // Example usage const spawnConfig = configureProcessSpawnArgs( { spawnArgs: [], cwd: "/home/user/vault", env: { ...process.env, NVIM_APPNAME: "lazyvim" }, shell: false, detached: false }, "alacritty.exe", "C:\\Users\\Name\\scoop\\shims\\alacritty.exe", "C:\\Program Files\\nvim\\bin\\nvim.exe", "127.0.0.1:2006" ); // Result for Alacritty on Windows: // { // spawnArgs: ['-e', 'C:\\Program Files\\nvim\\bin\\nvim.exe', '--listen', '127.0.0.1:2006'], // cwd: "/home/user/vault", // env: { PATH: "...", NVIM_APPNAME: "lazyvim", ... }, // shell: false, // detached: false // } ``` -------------------------------- ### Check if Neovim Server Port is Listening (TypeScript) Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt The isPortInUse function verifies if the Neovim RPC server is accepting connections on a given port. It uses the systeminformation library to retrieve network connections and checks if any connection matches the specified port. This function is asynchronous and returns a boolean indicating whether the port is in use. ```typescript import systeminformation from "systeminformation"; export async function isPortInUse(port: string): Promise { // Get all network connections const networkConnections = await systeminformation.networkConnections(); // Check if any connection uses the specified port const connectionFound = networkConnections.find( (connection: { localPort: string }): boolean => { return connection.localPort === String(port); } ); return connectionFound !== undefined; } // Usage in openFile method async function beforeOpeningFile() { const listenAddress = "127.0.0.1:2006"; const port = listenAddress.split(':').at(-1); // "2006" try { const isListening = await isPortInUse(port); if (!isListening) { console.debug("Neovim server not listening on port " + port); return false; } console.log("Neovim server is ready on port " + port); return true; } catch (error) { console.error(`Port check failed: ${error.message}`); return false; } } ``` -------------------------------- ### Configure Edit in Neovim Plugin Settings (TypeScript) Source: https://context7.com/theseusgrey/edit-in-neovim/llms.txt This TypeScript code defines the settings interface and default values for the Edit in Neovim Obsidian plugin. It allows customization of the terminal emulator, RPC server address, supported file types, and Neovim application name. The `SettingTab` class handles the UI for these settings within Obsidian. ```typescript import { App, PluginSettingTab, Setting } from "obsidian"; import EditInNeovim from "./main"; export interface EditInNeovimSettings { terminal: string; listenOn: string; openNeovimOnLoad: boolean; supportedFileTypes: string[]; pathToBinary: string; appname: string; } export const DEFAULT_SETTINGS: EditInNeovimSettings = { terminal: process.env.TERMINAL || "alacritty", listenOn: "127.0.0.1:2006", openNeovimOnLoad: true, supportedFileTypes: ["txt", "md", "css", "js", "ts", "tsx", "jsx", "json"], pathToBinary: "", appname: "" }; export default class EditInNeovimSettingsTab extends PluginSettingTab { plugin: EditInNeovim; constructor(app: App, plugin: EditInNeovim) { super(app, plugin); this.plugin = plugin; } display(): void { const { containerEl } = this; containerEl.empty(); // Terminal selection new Setting(containerEl) .setName("Terminal") .setDesc("Which terminal emulator to use for Neovim") .addText((text) => text .setPlaceholder("E.g. alacritty, kitty, wezterm...") .setValue(this.plugin.settings.terminal) .onChange(async (value) => { this.plugin.settings.terminal = value; await this.plugin.saveSettings(); }) ); // Server listen address new Setting(containerEl) .setName("Neovim server location") .setDesc("IP:PORT for Neovim RPC server") .addText((text) => text .setPlaceholder("127.0.0.1:2006") .setValue(this.plugin.settings.listenOn) .onChange(async (value) => { this.plugin.settings.listenOn = value; await this.plugin.saveSettings(); }) ); // Custom Neovim config (NVIM_APPNAME) new Setting(containerEl) .setName("NVIM_APPNAME") .setDesc("Use specific Neovim config (e.g., lazyvim)") .addText((text) => text .setPlaceholder("lazyvim, my_writing_config, etc.") .setValue(this.plugin.settings.appname) .onChange(async (value) => { this.plugin.settings.appname = value; await this.plugin.saveSettings(); }) ); // Supported file types new Setting(containerEl) .setName("Supported file types") .setDesc("Space-separated file extensions without dots") .addText((text) => text .setPlaceholder("txt md css html js ts") .setValue(this.plugin.settings.supportedFileTypes.join(" ")) .onChange(async (value) => { this.plugin.settings.supportedFileTypes = value.split(" "); await this.plugin.saveSettings(); }) ); // Auto-open toggle new Setting(containerEl) .setName("Open on startup") .setDesc("Open Neovim when Obsidian starts") .addToggle((toggle) => toggle .setValue(this.plugin.settings.openNeovimOnLoad) .onChange(async (value) => { this.plugin.settings.openNeovimOnLoad = value; await this.plugin.saveSettings(); }) ); } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.