UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

204 lines (176 loc) 6.27 kB
import { Connection } from 'vscode-languageserver'; import { exec } from 'child_process'; import { execAsyncFish } from './utils/exec'; import { promisify } from 'util'; import { appendFileSync } from 'fs'; export const execAsync = promisify(exec); export type ExecResultKind = 'error' | 'info'; export type ExecResultWrapper = { message: string; kind: ExecResultKind; }; export async function execLineInBuffer(line: string): Promise<ExecResultWrapper> { const { stderr, stdout } = await execAsync(`fish -c '${line}'; or true`); if (stderr) { return { message: buildOutput(line, 'stderr:', stderr), kind: 'error' }; } if (stdout) { return { message: buildOutput(line, 'stdout:', stdout), kind: 'info' }; } return { message: [ `${fishLspPromptIcon} ${line}`, '-'.repeat(50), 'EMPTY RESULT', ].join('\n'), kind: 'info', }; } export const fishLspPromptIcon = '><(((°>'; export function buildOutput(line: string, outputMessage: 'error:' | 'stderr:' | 'stdout:', output: string) { const tokens = line.trim().split(' '); let promptLine = `${fishLspPromptIcon} `; let currentLen = promptLine.length; for (const token of tokens) { if (1 + token.length + currentLen > 49) { const newToken = `\\\n ${token} `; promptLine += newToken; currentLen = newToken.slice(newToken.indexOf('\n')).length; } else { const newToken = token + ' '; promptLine += newToken; currentLen += newToken.length + 1; } } return [ promptLine, '-'.repeat(50), `${outputMessage} ${output}`, ].join('\n'); } export function buildExecuteNotificationResponse( input: string, output: { stdout: string; stderr: string; }, ) { const outputType = output.stdout ? output.stdout : output.stderr; const outputMessagePrefix = output.stdout ? 'stdout:' : 'stderr:'; const kind: ExecResultKind = output.stdout ? 'info' : 'error'; return { message: buildOutput(input, outputMessagePrefix, outputType), kind, }; } export async function execEntireBuffer(bufferName: string): Promise<ExecResultWrapper> { const { stdout, stderr } = await execAsync(`fish ${bufferName}`); const statusOutput = (await execAsync(`fish -c 'fish ${bufferName} 1> /dev/null; echo "\\$status: $status"'`)).stdout; const headerOutput = [ `${fishLspPromptIcon} executing file:`, `${' '.repeat(fishLspPromptIcon.length)} ${bufferName}`, ].join('\n'); const longestLineLen = findLongestLine(headerOutput, stdout, stderr, '-'.repeat(50)).length; let output = ''; if (stdout) output += `${stdout}`; if (stdout && stderr) output += `\nerror:\n${stderr}`; else if (!stdout && stderr) output += `error:\n${stderr}`; let messageType: ExecResultKind = 'info'; if (stderr) messageType = 'error'; if (statusOutput) output += `${'-'.repeat(longestLineLen)}\n${statusOutput}`; return { message: [ headerOutput, '-'.repeat(longestLineLen), output, ].join('\n'), kind: messageType, }; } export async function sourceFishBuffer(bufferName: string) { const { stdout, stderr } = await execAsync(`fish -c 'source ${bufferName}'`); const statusOutput = (await execAsync(`fish -c 'source ${bufferName} 1> /dev/null; echo "\\$status: $status"'`)).stdout; const message = [ `${fishLspPromptIcon} sourcing file:`, `${' '.repeat(fishLspPromptIcon.length)} ${bufferName}`, ].join('\n'); const longestLineLen = findLongestLine(message, stdout, stderr, statusOutput, '-'.repeat(50)).length; const outputArr: string[] = []; if (statusOutput) outputArr.push(statusOutput); if (stdout) outputArr.push(stdout); if (stderr) outputArr.push(stderr); const output = outputArr.join('-'.repeat(50) + '\n'); return [ message, '-'.repeat(longestLineLen), output, ].join('\n'); } export async function FishThemeDump() { return (await execAsyncFish('fish_config theme dump; or true')).stdout.split('\n'); } export async function showCurrentTheme(buffName: string) { const output = (await execAsyncFish('fish_config theme demo; or true')).stdout.split('\n'); // Append the longest line to the file for (const line of output) { appendFileSync(buffName, `${line}\n`, 'utf8'); } return { message: `${fishLspPromptIcon} appended theme variables to end of file`, kind: 'info', }; } export type ThemeOptions = { asVariables: boolean; }; const defaultThemeOptions: ThemeOptions = { asVariables: false, }; export async function executeThemeDump(buffName: string, options: ThemeOptions = defaultThemeOptions): Promise<ExecResultWrapper> { const output = (await execAsyncFish('fish_config theme dump; or true')).stdout.split('\n'); // Append the longest line to the file if (options.asVariables) { appendFileSync(buffName, '# created by fish-lsp'); } for (const line of output) { if (options.asVariables) { appendFileSync(buffName, `set -gx ${line}\n`, 'utf8'); } else { appendFileSync(buffName, `${line}\n`, 'utf8'); } } return { message: `${fishLspPromptIcon} appended theme variables to end of file`, kind: 'info', }; } /** * Function to find the longest line in a string. * @param input - The input string with lines separated by newline characters. * @returns The longest line in the input string. */ function findLongestLine(...inputs: string[]): string { const input = inputs.join('\n'); // Split the input string by newline characters into an array of lines const lines: string[] = input.split('\n'); // Initialize a variable to keep track of the longest line let longestLine: string = ''; // Iterate over each line for (const line of lines) { // If the current line is longer than the longestLine found so far, update longestLine if (line.length > longestLine.length) { longestLine = line; } } // Return the longest line found return longestLine; } export function useMessageKind(connection: Connection, result: ExecResultWrapper) { switch (result.kind) { case 'info': connection.window.showInformationMessage(result.message); return; case 'error': connection.window.showErrorMessage(result.message); return; default: return; } }