Как предлагать файлы с завершением табуляции с помощью readline?

В оболочке Bash я могу использовать завершение табуляции, чтобы предлагать имена файлов и каталогов. Как добиться этого с помощью nodejs и readline?

Примеры:

  • /<Tab> должен предлагать /root/, /bin/ и т. д.
  • /et<Tab> должно завершиться до /etc/.
  • fo<Tab> должно завершиться foobar при условии, что такой файл существует в текущем каталоге.

Я думал об использовании подстановки (шаблон search_term.replace(/[?*]/g, "\\$&") + "*"), но, может быть, есть библиотека, которую я упустил?

Это мой текущий подход с использованием glob, он не работает при использовании //<Tab>, поскольку возвращает канонизированное имя и, возможно, имеет некоторые другие странности:

function command_completion(line) {
    var hits;
    // likely broken, one does not simply escape a glob char
    var pat = line.replace(/[?*]/g, "\\$&") + "*";
    // depends: glob >= 3.0
    var glob = require("glob").sync;
    hits = glob(pat, {
        silent: true,
        nobrace: true,
        noglobstar: true,
        noext: true,
        nocomment: true,
        nonegate: true
    });

    return [hits, line];
}

var readline = require("readline");
rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    completer: command_completion
});
rl.prompt();

person Lekensteyn    schedule 17.04.2013    source источник


Ответы (2)


Вот рабочее решение с некоторыми особенностями:

  • Не поддерживает относительные пути
  • При попытке отобразить предложения, дважды нажав вкладку, он отображает полный путь в списке предложений.
  • Он предпочитает '/' '\', но допускает разделители '\' в окнах.
  • Он поддерживает только каталоги и файлы. (без устройств, пайпов, сокетов, софтлинков)

Код:

const { promises: fsPromises } = require("fs"); 
const { parse, sep } = require("path");

function fileSystemCompleter(line, callback) {
  let { dir, base } = parse(line);
  fsPromises.readdir(dir, { withFileTypes: true })
    .then((dirEntries) => {
      // for an exact match that is a directory, read the contents of the directory
      if (dirEntries.find((entry) => entry.name === base && entry.isDirectory())) {
        dir = dir === "/" || dir === sep ? `${dir}${base}` : `${dir}/${base}`;
        return fsPromises.readdir(dir, { withFileTypes: true })
      }
      return dirEntries.filter((entry) => entry.name.startsWith(base));
    })
    .then((matchingEntries) => {
      if (dir === sep || dir === "/") {
        dir = "";
      }
      const hits = matchingEntries
        .filter((entry) => entry.isFile() || entry.isDirectory())
        .map((entry) => `${dir}/${entry.name}${entry.isDirectory() && !entry.name.endsWith("/") ? "/" : ""}`);
      callback(null, [hits, line]);
    })
    .catch(() => (callback(null, [[], line])));
}
person grahamaj    schedule 04.11.2020

Возможно, вы могли бы взглянуть на readdir: https://www.npmjs.com/package/readdir

Просто прочитайте каталог, в котором пользователь создает вкладку, затем сравните ввод пользователя с началом каждого файла в каталоге и, если имя файла совпадает, отобразите его пользователю. Что-то типа:

var readDir = require('readdir');

function strncmp(str1, str2, lgth) {
  var s1 = (str1 + '')
    .substr(0, lgth);
  var s2 = (str2 + '')
    .substr(0, lgth);

  return ((s1 == s2) ? 0 : ((s1 > s2) ? 1 : -1));
}

var userInput = // get user input;
var path = // get the path;
readDir.read(path, [*], function(err, files) {
    for (var i = 0; i < files.length; i++)
        if (strncmp(files[i], userInput, userInput.length) == 0)
            console.log(files[i]);
});
person Jérémy Pouyet    schedule 15.07.2015