• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RauliL / juokse / 6087158516

05 Sep 2023 04:11PM UTC coverage: 63.158% (-0.2%) from 63.327%
6087158516

push

github

web-flow
Merge pull request #29 from RauliL/tab-completion

Add basic tab completion to REPL

174 of 282 branches covered (0.0%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

534 of 839 relevant lines covered (63.65%)

122.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/interactive.ts
1
import fs from "fs";
×
2
import isExe from "isexe";
×
3
import path from "path";
×
4
import * as readline from "readline";
×
5

6
import { Statement } from "./ast";
7
import { builtinCommandMapping } from "./builtins";
×
8
import { Context } from "./context";
9
import { executeScript } from "./execute";
×
10
import { lex } from "./lexer";
×
11
import { parse } from "./parser";
×
12
import { ExitStatus } from "./status";
×
13

14
const completeVariable = (
×
15
  context: Context,
16
  matches: Set<string>,
17
  word: string,
18
  isComplex: boolean
19
) => {
20
  for (const mapping of [context.variables, context.environment]) {
×
21
    for (const variable of Object.keys(mapping)) {
×
22
      if (variable.startsWith(word)) {
×
23
        matches.add(isComplex ? `$\{${variable}}` : `$${variable}`);
×
24
      }
25
    }
26
  }
27
};
28

29
const completePath = (matches: Set<string>, word: string) => {
×
30
  const base = path.basename(word);
×
31

32
  if (base.length > 0) {
×
33
    const dir = path.dirname(word);
×
34

35
    try {
×
36
      for (const file of fs.readdirSync(dir)) {
×
37
        if (file.startsWith(base)) {
×
38
          const fullPath = path.join(dir, file);
×
39

40
          matches.add(
×
41
            fs.statSync(fullPath).isDirectory
×
42
              ? `${fullPath}${path.sep}`
43
              : fullPath
44
          );
45
        }
46
      }
47
    } catch {
48
      // Ignore.
49
    }
50
  }
51
};
52

53
const completeCommand = (
×
54
  context: Context,
55
  matches: Set<string>,
56
  word: string
57
) => {
58
  for (const command of Object.keys(builtinCommandMapping)) {
×
59
    if (command.startsWith(word)) {
×
60
      matches.add(command);
×
61
    }
62
  }
63

64
  for (const pathComponent of context.path) {
×
65
    try {
×
66
      for (const file of fs.readdirSync(pathComponent)) {
×
67
        if (
×
68
          file.startsWith(word) &&
×
69
          isExe.sync(path.join(pathComponent, file))
70
        ) {
71
          matches.add(file);
×
72
        }
73
      }
74
    } catch {
75
      // Ignore.
76
    }
77
  }
78
};
79

80
export const runInteractive = (context: Context) => {
×
81
  const rl = readline.createInterface({
×
82
    input: process.stdin,
83
    output: process.stdout,
84
    prompt: "juokse:1> ",
85
    completer(line: string): readline.CompleterResult {
86
      const words = line.split(/\s+/);
×
87

88
      if (words.length > 0) {
×
89
        const matches = new Set<string>();
×
90
        let lastWord = words[words.length - 1];
×
91
        const lastWordLength = lastWord.length;
×
92

93
        if (lastWord.startsWith("$")) {
×
94
          // It seems that we are completing variables.
95
          const isComplex = lastWord.startsWith("${");
×
96

97
          lastWord = lastWord.substring(isComplex ? 2 : 1);
×
98
          completeVariable(context, matches, lastWord, isComplex);
×
99
        } else {
100
          // It seems that we are completing commands and/or file paths.
101
          completePath(matches, lastWord);
×
102
          completeCommand(context, matches, lastWord);
×
103
        }
104

105
        if (matches.size > 0) {
×
106
          return [
×
107
            Array.from(matches).map(
108
              (match) =>
109
                `${line.substring(0, line.length - lastWordLength)}${match}`
×
110
            ),
111
            line,
112
          ];
113
        }
114
      }
115

116
      return [[], line];
×
117
    },
118
  });
119
  let lineNumber = 1;
×
120

121
  rl.on("line", (source) => {
×
122
    let script: Statement[];
123

124
    try {
×
125
      script = parse(lex("<stdin>", lineNumber++, source));
×
126
    } catch (err) {
127
      console.error(err);
×
128
      return;
×
129
    }
130

131
    executeScript(context, script)
×
132
      .catch((err) => {
133
        console.error(err);
×
134
      })
135
      .finally(() => {
136
        rl.setPrompt(`juokse:${lineNumber}> `);
×
137
        rl.prompt();
×
138
      });
139
  });
140

141
  rl.on("close", () => {
×
142
    process.exit(ExitStatus.OK);
×
143
  });
144

145
  rl.prompt();
×
146
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc