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

RauliL / juokse / 6087137666

05 Sep 2023 04:09PM UTC coverage: 63.327% (-3.3%) from 66.667%
6087137666

push

github

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

Add basic tab completion to REPL

174 of 280 branches covered (0.0%)

Branch coverage included in aggregate %.

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

534 of 838 relevant lines covered (63.72%)

122.62 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
          matches.add(path.join(dir, file));
×
39
        }
40
      }
41
    } catch {
42
      // Ignore.
43
    }
44
  }
45
};
46

47
const completeCommand = (
×
48
  context: Context,
49
  matches: Set<string>,
50
  word: string
51
) => {
52
  for (const command of Object.keys(builtinCommandMapping)) {
×
53
    if (command.startsWith(word)) {
×
54
      matches.add(command);
×
55
    }
56
  }
57

58
  for (const pathComponent of context.path) {
×
59
    try {
×
60
      for (const file of fs.readdirSync(pathComponent)) {
×
61
        if (
×
62
          file.startsWith(word) &&
×
63
          isExe.sync(path.join(pathComponent, file))
64
        ) {
65
          matches.add(file);
×
66
        }
67
      }
68
    } catch {
69
      // Ignore.
70
    }
71
  }
72
};
73

74
export const runInteractive = (context: Context) => {
×
75
  const rl = readline.createInterface({
×
76
    input: process.stdin,
77
    output: process.stdout,
78
    prompt: "juokse:1> ",
79
    completer(line: string): readline.CompleterResult {
80
      const words = line.split(/\s+/);
×
81

82
      if (words.length > 0) {
×
83
        const matches = new Set<string>();
×
84
        let lastWord = words[words.length - 1];
×
85
        const lastWordLength = lastWord.length;
×
86

87
        if (lastWord.startsWith("$")) {
×
88
          // It seems that we are completing variables.
89
          const isComplex = lastWord.startsWith("${");
×
90

91
          lastWord = lastWord.substring(isComplex ? 2 : 1);
×
92
          completeVariable(context, matches, lastWord, isComplex);
×
93
        } else {
94
          // It seems that we are completing commands and/or file paths.
95
          completePath(matches, lastWord);
×
96
          completeCommand(context, matches, lastWord);
×
97
        }
98

99
        if (matches.size > 0) {
×
100
          return [
×
101
            Array.from(matches).map(
102
              (match) =>
103
                `${line.substring(0, line.length - lastWordLength)}${match}`
×
104
            ),
105
            line,
106
          ];
107
        }
108
      }
109

110
      return [[], line];
×
111
    },
112
  });
113
  let lineNumber = 1;
×
114

115
  rl.on("line", (source) => {
×
116
    let script: Statement[];
117

118
    try {
×
119
      script = parse(lex("<stdin>", lineNumber++, source));
×
120
    } catch (err) {
121
      console.error(err);
×
122
      return;
×
123
    }
124

125
    executeScript(context, script)
×
126
      .catch((err) => {
127
        console.error(err);
×
128
      })
129
      .finally(() => {
130
        rl.setPrompt(`juokse:${lineNumber}> `);
×
131
        rl.prompt();
×
132
      });
133
  });
134

135
  rl.on("close", () => {
×
136
    process.exit(ExitStatus.OK);
×
137
  });
138

139
  rl.prompt();
×
140
};
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