Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor globalAliases #218

Merged
merged 6 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/kind-llamas-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
"@clack/prompts": minor
"@clack/core": minor
---

Adds a new `updateSettings()` function to support new global keybindings.

`updateSettings()` accepts an `aliases` object that maps custom keys to an action (`up | down | left | right | space | enter | cancel`).

```ts
import { updateSettings } from "@clack/prompts";

// Support custom keybindings
updateSettings({
aliases: {
w: "up",
a: "left",
s: "down",
d: "right",
},
});
```

> [!WARNING]
> In order to enforce consistent, user-friendly defaults across the ecosystem, `updateSettings` does not support disabling Clack's default keybindings.
14 changes: 14 additions & 0 deletions .changeset/quiet-actors-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@clack/prompts": minor
"@clack/core": minor
---

Updates default keybindings to support Vim motion shortcuts and map the `escape` key to cancel (`ctrl+c`).

| alias | action |
|------- |-------- |
| `k` | up |
| `l` | right |
| `j` | down |
| `h` | left |
| `esc` | cancel |
7 changes: 5 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export type { ClackState as State } from './types';
export type { ClackSettings } from './utils/settings';

export { default as ConfirmPrompt } from './prompts/confirm';
export { default as GroupMultiSelectPrompt } from './prompts/group-multiselect';
export { default as MultiSelectPrompt } from './prompts/multi-select';
Expand All @@ -6,5 +9,5 @@ export { default as Prompt } from './prompts/prompt';
export { default as SelectPrompt } from './prompts/select';
export { default as SelectKeyPrompt } from './prompts/select-key';
export { default as TextPrompt } from './prompts/text';
export type { ClackState as State } from './types';
export { block, isCancel, setGlobalAliases } from './utils';
export { block, isCancel } from './utils';
export { updateSettings } from './utils/settings';
19 changes: 11 additions & 8 deletions packages/core/src/prompts/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { WriteStream } from 'node:tty';
import { cursor, erase } from 'sisteransi';
import wrap from 'wrap-ansi';

import { ALIASES, CANCEL_SYMBOL, KEYS, diffLines, hasAliasKey, setRawMode } from '../utils';
import { CANCEL_SYMBOL, diffLines, isActionKey, setRawMode, settings } from '../utils';

import type { ClackEvents, ClackState, InferSetType } from '../types';
import type { ClackEvents, ClackState } from '../types';
import type { Action } from '../utils';

export interface PromptOptions<Self extends Prompt> {
render(this: Omit<Self, 'prompt'>): string | undefined;
Expand Down Expand Up @@ -181,11 +182,13 @@ export default class Prompt {
if (this.state === 'error') {
this.state = 'active';
}
if (key?.name && !this._track && ALIASES.has(key.name)) {
this.emit('cursor', ALIASES.get(key.name));
}
if (key?.name && KEYS.has(key.name as InferSetType<typeof KEYS>)) {
this.emit('cursor', key.name as InferSetType<typeof KEYS>);
if (key?.name) {
if (!this._track && settings.aliases.has(key.name)) {
this.emit('cursor', settings.aliases.get(key.name));
}
if (settings.actions.has(key.name as Action)) {
this.emit('cursor', key.name as Action);
}
}
if (char && (char.toLowerCase() === 'y' || char.toLowerCase() === 'n')) {
this.emit('confirm', char.toLowerCase() === 'y');
Expand Down Expand Up @@ -214,7 +217,7 @@ export default class Prompt {
}
}

if (hasAliasKey([char, key?.name, key?.sequence], 'cancel')) {
if (isActionKey([char, key?.name, key?.sequence], 'cancel')) {
this.state = 'cancel';
}
if (this.state === 'submit' || this.state === 'cancel') {
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { KEYS } from './utils';

export type InferSetType<T> = T extends Set<infer U> ? U : never;
import type { Action } from "./utils/settings";

/**
* The state of the prompt
Expand All @@ -16,7 +14,7 @@ export interface ClackEvents {
cancel: (value?: any) => void;
submit: (value?: any) => void;
error: (value?: any) => void;
cursor: (key?: InferSetType<typeof KEYS>) => void;
cursor: (key?: Action) => void;
key: (key?: string) => void;
value: (value?: string) => void;
confirm: (value?: boolean) => void;
Expand Down
49 changes: 0 additions & 49 deletions packages/core/src/utils/aliases.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type { Key } from 'node:readline';
import * as readline from 'node:readline';
import type { Readable } from 'node:stream';
import { cursor } from 'sisteransi';
import { hasAliasKey } from './aliases';
import { isActionKey } from './settings';

export * from './aliases';
export * from './string';
export * from './settings';

const isWindows = globalThis.process.platform.startsWith('win');

Expand Down Expand Up @@ -39,7 +39,7 @@ export function block({

const clear = (data: Buffer, { name, sequence }: Key) => {
const str = String(data);
if (hasAliasKey([str, name, sequence], 'cancel')) {
if (isActionKey([str, name, sequence], 'cancel')) {
if (hideCursor) output.write(cursor.show);
process.exit(0);
return;
Expand Down
73 changes: 73 additions & 0 deletions packages/core/src/utils/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const actions = ['up', 'down', 'left', 'right', 'space', 'enter', 'cancel'] as const;
export type Action = (typeof actions)[number];

/** Global settings for Clack programs, stored in memory */
interface InternalClackSettings {
actions: Set<Action>;
aliases: Map<string, Action>;
}

export const settings: InternalClackSettings = {
actions: new Set(actions),
aliases: new Map<string, Action>([
// vim support
['k', 'up'],
['j', 'down'],
['h', 'left'],
['l', 'right'],
['\x03', 'cancel'],
// opinionated defaults!
['escape', 'cancel'],
]),
};

export interface ClackSettings {
/**
* Set custom global aliases for the default actions.
* This will not overwrite existing aliases, it will only add new ones!
*
* @param aliases - An object that maps aliases to actions
* @default { k: 'up', j: 'down', h: 'left', l: 'right', '\x03': 'cancel', 'escape': 'cancel' }
*/
aliases: Record<string, Action>;
}

export function updateSettings(updates: ClackSettings) {
for (const _key in updates) {
const key = _key as keyof ClackSettings;
if (!Object.hasOwn(updates, key)) continue;
const value = updates[key];

switch (key) {
case 'aliases': {
for (const alias in value) {
if (!Object.hasOwn(value, alias)) continue;
if (!settings.aliases.has(alias)) {
settings.aliases.set(alias, value[alias]);
}
}
break;
}
}
}
}

/**
* Check if a key is an alias for a default action
* @param key - The raw key which might match to an action
* @param action - The action to match
* @returns boolean
*/
export function isActionKey(key: string | Array<string | undefined>, action: Action) {
if (typeof key === 'string') {
return settings.aliases.get(key) === action;
}

for (const value of key) {
if (value === undefined) continue;
if (isActionKey(value, action)) {
return true;
}
}
return false;
}
3 changes: 2 additions & 1 deletion packages/prompts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import isUnicodeSupported from 'is-unicode-supported';
import color from 'picocolors';
import { cursor, erase } from 'sisteransi';

export { isCancel, setGlobalAliases } from '@clack/core';
export { isCancel } from '@clack/core';
export { updateSettings, type ClackSettings } from '@clack/core';

const unicode = isUnicodeSupported();
const s = (c: string, fallback: string) => (unicode ? c : fallback);
Expand Down
Loading