Skip to content

Commit

Permalink
Add linux named pipe support
Browse files Browse the repository at this point in the history
  • Loading branch information
rlivings39 committed Jun 14, 2020
1 parent d01146c commit fc2c2f9
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 77 deletions.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Program",
"program": "${workspaceFolder}/src/socketfun.ts",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Extension",
"type": "extensionHost",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.3.0] - 2020-06-14

### Changed

- Changed implementation to avoid using command line utilities: `xargs, cut, code`. `fzf` output is now sent through named pipes and processed in the VSCode extension code. This avoids issues with having those utilities installed, having the wrong `code` on the path, and allows for implemeting arbitrary functionality beyond what the `code` command supports.

## [0.2.12] - 2020-05-26

### Added
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "fzf-quick-open",
"displayName": "fzf fuzzy quick open",
"description": "Fuzzy search files/folders and ripgrep results with fzf inside VSCode",
"version": "0.2.12",
"version": "0.3.0",
"author": "Ryan Livingston",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -71,6 +71,7 @@
"scope": "window",
"type": "string",
"default": "code",
"deprecationMessage": "DEPRECATED: This option has no utility as of v0.3.0. We avoid use of the code command line API and don't need this to distinguish code from code-insiders. Please report unexpected issues based on the removal of this property.",
"description": "Command used to launch VSCode. Set to code-insiders if using insiders build"
},
"fzf-quick-open.fuzzyCmd": {
Expand Down Expand Up @@ -122,4 +123,4 @@
"typescript": "^3.8.3",
"vscode-test": "^1.0.0"
}
}
}
7 changes: 7 additions & 0 deletions scripts/topipe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#! /usr/bin/env sh
# USAGE: topipe.bat commandString pipename
# reads input to send to pipename from input pipe
sep='$$'
read arg
# commandString{open, add, rg} pwd pipedInput > pipeName
echo "$1 $sep `pwd` $sep $arg" > $2
175 changes: 101 additions & 74 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as net from 'net';
import * as os from 'os';
import * as cp from 'child_process';

let fzfTerminal: vscode.Terminal | undefined = undefined;
let fzfTerminalPwd: vscode.Terminal | undefined = undefined;

let codeCmd: string;
let codePath: string;
let findCmd: string;
let fzfCmd: string;
let initialCwd: string;
let rgCaseFlag: string;
let fzfPipe: string;
let fzfPipeBatch: string;
let fzfPipeScript: string;

export const TERMINAL_NAME = "fzf terminal";
export const TERMINAL_NAME_PWD = "fzf pwd terminal";
Expand Down Expand Up @@ -79,21 +81,6 @@ function isWindows() {
return process.platform === 'win32';
}

function setupCodeCmd() {
codePath = vscode.env.appName.toLowerCase().indexOf('insiders') !== -1 ? 'code-insiders' : 'code';
// Go find the code executable if we can. This adds stability for when the user's shell
// doesn't have the right code on the top of the path
let binRoot = vscode.env.appRoot;
let localPath = path.join(binRoot, '..', '..', 'bin');
let remotePath = path.join(binRoot, 'bin');
if (fs.existsSync(localPath)) {
codePath = path.join(localPath, codePath);
} else if (fs.existsSync(remotePath)) {
codePath = path.join(remotePath, codePath);
}
codeCmd = `"${codePath}"`;
}

function getPath(arg: string, pwd: string): string | undefined {
if (!path.isAbsolute(arg)) {
arg = path.join(pwd, arg);
Expand All @@ -106,70 +93,111 @@ function getPath(arg: string, pwd: string): string | undefined {
}

function escapeWinPath(origPath: string) {
return origPath.replace(/\\/g,'\\\\');
return origPath.replace(/\\/g, '\\\\');
}

export function activate(context: vscode.ExtensionContext) {
applyConfig();
if (isWindows()) {
let server = net.createServer((socket) => {
socket.on('data', (data) => {
let [cmd, pwd, arg] = data.toString().trim().split('$$');
cmd = cmd.trim(); pwd = pwd.trim(); arg = arg.trim();
if (arg === "") { return }
if (cmd === 'open') {
let filename = getPath(arg, pwd);
if (!filename) { return }
vscode.window.showTextDocument(vscode.Uri.file(filename));
} else if (cmd === 'add') {
let folder = getPath(arg, pwd);
if (!folder) { return }
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, null, {
uri: vscode.Uri.file(folder)
});
vscode.commands.executeCommand('workbench.view.explorer');
} else if (cmd === 'rg') {
let [file, linestr, colstr] = arg.split(':');
let filename = getPath(file, pwd);
if (!filename) { return };
let line = parseInt(linestr)-1;
let col = parseInt(colstr)-1;
vscode.window.showTextDocument(vscode.Uri.file(filename)).then((ed) => {
ed.selection = new vscode.Selection(line, col, line, col);
})
}
})

function processCommandInput(data: Buffer) {
let [cmd, pwd, arg] = data.toString().trim().split('$$');
cmd = cmd.trim(); pwd = pwd.trim(); arg = arg.trim();
if (arg === "") { return }
if (cmd === 'open') {
let filename = getPath(arg, pwd);
if (!filename) { return }
vscode.window.showTextDocument(vscode.Uri.file(filename));
} else if (cmd === 'add') {
let folder = getPath(arg, pwd);
if (!folder) { return }
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, null, {
uri: vscode.Uri.file(folder)
});
let idx = 0;
while (!fzfPipe) {
try {
let pipe = `\\\\?\\pipe\\fzf-pipe-${process.pid}`;
if (idx > 0) { pipe += `-${idx}`; }
server.listen(pipe);
fzfPipe = escapeWinPath(pipe);
} catch(e) {
if (e.code === 'EADDRINUSE') {
// Try again for a new address
++idx;
} else {
// Bad news
throw e;
}
vscode.commands.executeCommand('workbench.view.explorer');
} else if (cmd === 'rg') {
let [file, linestr, colstr] = arg.split(':');
let filename = getPath(file, pwd);
if (!filename) { return };
let line = parseInt(linestr) - 1;
let col = parseInt(colstr) - 1;
vscode.window.showTextDocument(vscode.Uri.file(filename)).then((ed) => {
ed.selection = new vscode.Selection(line, col, line, col);
})
}
}

function listenToFifo(fifo: string) {
fs.open(fifo, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK , (err, fd) => {
const pipe = new net.Socket({fd: fd, allowHalfOpen: true });
pipe.on('data', (data) => {
processCommandInput(data);
})
pipe.on('end', () => {
console.log('end!')
listenToFifo(fifo);
})
})
}

function setupWindowsPipe() {
let server = net.createServer((socket) => {
socket.on('data', (data) => {
processCommandInput(data);
})
});
let idx = 0;
while (!fzfPipe) {
try {
let pipe = `\\\\?\\pipe\\fzf-pipe-${process.pid}`;
if (idx > 0) { pipe += `-${idx}`; }
server.listen(pipe);
fzfPipe = escapeWinPath(pipe);
} catch (e) {
if (e.code === 'EADDRINUSE') {
// Try again for a new address
++idx;
} else {
// Bad news
throw e;
}
}
fzfPipeBatch = vscode.extensions.getExtension('rlivings39.fzf-quick-open')?.extensionPath ?? "";
fzfPipeBatch = escapeWinPath(path.join(fzfPipeBatch, 'scripts', 'topipe.bat'));
}
setupCodeCmd();
}

function setupPOSIXPipe() {
let idx = 0;
while (!fzfPipe && idx < 10) {
try {
let pipe = path.join(os.tmpdir(), `fzf-pipe-${process.pid}`);
if (idx > 0) { pipe += `-${idx}`; }
cp.execSync(`mkfifo -m 600 ${pipe}`);
fzfPipe = pipe;
} catch (e) {
// Try again for a new address
++idx;
}
}
listenToFifo(fzfPipe);
}

function setupPipesAndListeners() {
if (isWindows()) {
setupWindowsPipe();
} else {
setupPOSIXPipe();
}
}

export function activate(context: vscode.ExtensionContext) {
applyConfig();
setupPipesAndListeners();
fzfPipeScript = vscode.extensions.getExtension('rlivings39.fzf-quick-open')?.extensionPath ?? "";
fzfPipeScript = escapeWinPath(path.join(fzfPipeScript, 'scripts', 'topipe.' + (isWindows() ? "bat" : "sh")));
vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('fzf-quick-open')) {
applyConfig();
}
})

let codeOpenFileCmd = `${fzfCmd} | ${fzfPipeBatch} open ${fzfPipe}`;
let codeOpenFolderCmd = `${fzfCmd} | ${fzfPipeBatch} add ${fzfPipe}`;
let codeOpenFileCmd = `${fzfCmd} | ${fzfPipeScript} open "${fzfPipe}"`;
let codeOpenFolderCmd = `${fzfCmd} | ${fzfPipeScript} add "${fzfPipe}"`;

context.subscriptions.push(vscode.commands.registerCommand('fzf-quick-open.runFzfFile', () => {
let term = showFzfTerminal(TERMINAL_NAME, fzfTerminal);
Expand Down Expand Up @@ -199,7 +227,7 @@ export function activate(context: vscode.ExtensionContext) {
return;
}
let term = showFzfTerminal(TERMINAL_NAME, fzfTerminal);
term.sendText(makeSearchCmd(pattern, codeCmd), true);
term.sendText(makeSearchCmd(pattern), true);
}));

context.subscriptions.push(vscode.commands.registerCommand('fzf-quick-open.runFzfSearchPwd', async () => {
Expand All @@ -209,7 +237,7 @@ export function activate(context: vscode.ExtensionContext) {
}
let term = showFzfTerminal(TERMINAL_NAME_PWD, fzfTerminalPwd);
moveToPwd(term);
term.sendText(makeSearchCmd(pattern, codeCmd), true);
term.sendText(makeSearchCmd(pattern), true);
}));

vscode.window.onDidCloseTerminal((terminal) => {
Expand Down Expand Up @@ -249,8 +277,7 @@ async function getSearchText(): Promise<string | undefined> {
/**
* Return the command used to invoke rg. Exported to allow unit testing.
* @param pattern Pattern to search for
* @param codeCmd Command used to launch code
*/
export function makeSearchCmd(pattern: string, codeCmd: string): string {
return `rg '${pattern}' ${rgCaseFlag} --vimgrep --color ansi | ${fzfCmd} --ansi | ${fzfPipeBatch} rg ${fzfPipe}`;
export function makeSearchCmd(pattern: string): string {
return `rg '${pattern}' ${rgCaseFlag} --vimgrep --color ansi | ${fzfCmd} --ansi | ${fzfPipeScript} rg "${fzfPipe}"`;
}
2 changes: 1 addition & 1 deletion src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ suite('fzf quick open uninit', async () => {
async function verifyRgConfig(opt: fzfQuickOpen.rgoptions, flag: string) {
let settings = vscode.workspace.getConfiguration('fzf-quick-open');
await settings.update('ripgrepSearchStyle', opt, vscode.ConfigurationTarget.Global);
let rgcmd = fzfQuickOpen.makeSearchCmd('pattern','code');
let rgcmd = fzfQuickOpen.makeSearchCmd('pattern');
let expectedFlag = fzfQuickOpen.rgflagmap.get(opt);
expect(rgcmd).contains(expectedFlag);
expect(rgcmd).contains(flag);
Expand Down

0 comments on commit fc2c2f9

Please sign in to comment.