diff --git a/src/components/Shell/Shell.tsx b/src/components/Shell/Shell.tsx
index 96768ee..76d4a69 100644
--- a/src/components/Shell/Shell.tsx
+++ b/src/components/Shell/Shell.tsx
@@ -1,13 +1,21 @@
// src/components/Shell/Shell.tsx
'use client';
+import React from 'react';
// hooks
import { useShell } from './hooks/useShell';
+import { SyntaxPart } from '@/data/commandSyntaxMap';
interface ShellProps {
decreaseCommandsLeft: () => void;
}
+const InlineHint = ({ part }: { part: SyntaxPart }) => (
+
+ {' ' + part.syntax}
+
+);
+
export default function Shell({ decreaseCommandsLeft }: ShellProps) {
const {
handleInputChange,
@@ -16,7 +24,9 @@ export default function Shell({ decreaseCommandsLeft }: ShellProps) {
inputRef,
output,
command,
+ remainingSyntax,
} = useShell(decreaseCommandsLeft);
+
return (
))}
diff --git a/src/components/Shell/__tests__/index.test.tsx b/src/components/Shell/__tests__/index.test.tsx
index 1fd31f8..704f812 100644
--- a/src/components/Shell/__tests__/index.test.tsx
+++ b/src/components/Shell/__tests__/index.test.tsx
@@ -109,4 +109,52 @@ describe('Shell Component', () => {
await user.keyboard('[ArrowDown]');
expect(cliInputElement.value).toBe(newCommand);
});
+
+ it('should show syntax usage hint for SET', async () => {
+ const { cliInputElement, user, getByTestId } = setupTest();
+
+ const newCommand = 'set';
+ await user.type(cliInputElement, newCommand);
+
+ const inlineHint = getByTestId('inline-hint');
+ expect(inlineHint.childElementCount).toBe(4);
+
+ const inlineHintChild = inlineHint.childNodes;
+
+ expect(inlineHintChild[0]).toHaveTextContent('Key');
+ expect(inlineHintChild[1]).toHaveTextContent('Value');
+ expect(inlineHintChild[2]).toHaveTextContent('[NX | XX]');
+ expect(inlineHintChild[3]).toHaveTextContent(
+ '[EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]',
+ );
+ });
+
+ it('should show syntax usage hint for GET', async () => {
+ const { cliInputElement, user, getByTestId } = setupTest();
+
+ const newCommand = 'get';
+ await user.type(cliInputElement, newCommand);
+
+ const inlineHint = getByTestId('inline-hint');
+ expect(inlineHint.childElementCount).toBe(1);
+
+ const inlineHintChild = inlineHint.childNodes;
+
+ expect(inlineHintChild[0]).toHaveTextContent('Key');
+ });
+
+ it('should show syntax usage hint for DEL', async () => {
+ const { cliInputElement, user, getByTestId } = setupTest();
+
+ const newCommand = 'del';
+ await user.type(cliInputElement, newCommand);
+
+ const inlineHint = getByTestId('inline-hint');
+ expect(inlineHint.childElementCount).toBe(2);
+
+ const inlineHintChild = inlineHint.childNodes;
+
+ expect(inlineHintChild[0]).toHaveTextContent('Key');
+ expect(inlineHintChild[1]).toHaveTextContent('[Key ...]');
+ });
});
diff --git a/src/components/Shell/hooks/useShell.tsx b/src/components/Shell/hooks/useShell.tsx
index 153e51a..0a944ff 100644
--- a/src/components/Shell/hooks/useShell.tsx
+++ b/src/components/Shell/hooks/useShell.tsx
@@ -3,7 +3,8 @@ import { useState, useEffect, useRef, KeyboardEvent, ChangeEvent } from 'react';
// utils
import { handleCommand } from '@/shared/utils/shellUtils';
-import blacklistedCommands from '@/shared/utils/blacklist';
+import blacklistedCommands from '@/shared/utils/blacklist'; // Assuming you added blacklist here
+import { syntaxMap, SyntaxPart } from '@/data/commandSyntaxMap';
export const useShell = (decreaseCommandsLeft: () => void) => {
// states
@@ -13,6 +14,7 @@ export const useShell = (decreaseCommandsLeft: () => void) => {
// Initialise the command history with sessionStorage
const [commandHistory, setCommandHistory] = useState
([]);
const [historyIndex, setHistoryIndex] = useState(-1);
+ const [remainingSyntax, setRemainingSyntax] = useState([]);
// useRefs
const terminalRef = useRef(null);
@@ -34,6 +36,24 @@ export const useShell = (decreaseCommandsLeft: () => void) => {
decreaseCommandsLeft(); // Call to update remaining commands
};
+ const updateSyntax = (value: string) => {
+ const inputParts = value.trim().split(' ');
+ const command = inputParts[0].toUpperCase();
+ if (syntaxMap[command]) {
+ const parts = syntaxMap[command].parts;
+ if (inputParts.length === 1) {
+ // Only command typed, show all parts
+ setRemainingSyntax(parts);
+ } else {
+ // Show remaining parts based on what's already typed
+ const remainingParts = parts.slice(inputParts.length - 1);
+ setRemainingSyntax(remainingParts);
+ }
+ } else {
+ setRemainingSyntax([]);
+ }
+ };
+
useEffect(() => {
if (terminalRef.current) {
terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
@@ -59,9 +79,10 @@ export const useShell = (decreaseCommandsLeft: () => void) => {
}, []);
const handleInputChange = (e: ChangeEvent) => {
- setCommand(e.target.value);
- // Save current input when starting to navigate history
+ const value = e.target.value;
+ setCommand(value);
setTempCommand(e.target.value);
+ updateSyntax(value);
};
const handleKeyDown = (e: KeyboardEvent) => {
@@ -71,6 +92,7 @@ export const useShell = (decreaseCommandsLeft: () => void) => {
setCommandHistory((prev) => [...prev, command]);
setHistoryIndex(-1);
}
+ setRemainingSyntax([]);
return;
}
@@ -120,5 +142,6 @@ export const useShell = (decreaseCommandsLeft: () => void) => {
command,
tempCommand,
setTempCommand,
+ remainingSyntax,
};
};
diff --git a/src/data/commandSyntaxMap.ts b/src/data/commandSyntaxMap.ts
new file mode 100644
index 0000000..ff7acc5
--- /dev/null
+++ b/src/data/commandSyntaxMap.ts
@@ -0,0 +1,56 @@
+export type SyntaxPart = {
+ syntax: string;
+ doc: string;
+};
+
+export type CommandSyntax = {
+ parts: SyntaxPart[];
+};
+
+export type SyntaxMap = {
+ [command: string]: CommandSyntax;
+};
+
+export const syntaxMap: SyntaxMap = {
+ SET: {
+ parts: [
+ {
+ syntax: 'Key',
+ doc: 'The key under which to store the value',
+ },
+ {
+ syntax: 'Value',
+ doc: 'The value to be stored',
+ },
+ {
+ syntax: '[NX | XX]',
+ doc: 'NX - Only set if key does not exist. XX - Only set if key exists',
+ },
+ {
+ syntax:
+ '[EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]',
+ doc: 'Options to set the key expiration: EX (seconds), PX (milliseconds), EXAT/PXAT (unix timestamp), or KEEPTTL to retain existing TTL',
+ },
+ ],
+ },
+ GET: {
+ parts: [
+ {
+ syntax: 'Key',
+ doc: 'Key of the value you want to retrive',
+ },
+ ],
+ },
+ DEL: {
+ parts: [
+ {
+ syntax: 'Key',
+ doc: 'Key that you want to delete',
+ },
+ {
+ syntax: '[Key ...]',
+ doc: 'Multiple keys you want to delete',
+ },
+ ],
+ },
+};