Skip to content

Commit

Permalink
Fix multiline support (updated) (#110)
Browse files Browse the repository at this point in the history
* styling and multiline handling

* styling (alignment and scroll)

* backspace handling

* update: multiline support (needs work)

* multiline fixes
  • Loading branch information
jon-v2 authored May 29, 2024
1 parent ba617ad commit aabc215
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@
"typescript": "^5.4.5",
"vite": "^5.0.12"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}
6 changes: 5 additions & 1 deletion src/main/framework/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ export class Prompt {

set value(value: string) {
this._value = value
this.parts = value.split(' ')
this.parts = value
.split('\n')
.map((line) => line.trim())
.join(' ')
.split(' ')

const [cmd, ...args] = this.parts

Expand Down
57 changes: 49 additions & 8 deletions src/renderer/src/assets/runner.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,67 @@ body {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: auto;
/* grid-template-columns: 100px; */
grid-template-rows: auto;
justify-items: center;
align-items: center;
}

.runner-input {
width: 100%
height: 100%;
padding: 10px;
display: flex;
flex-wrap: wrap;
max-height: 100%;
align-content: stretch;
justify-content: space-around;
flex-direction: column;
}

.multiline-input-container {
display:flex;
align-items:flex-end;
height: 50%;
}

.runner-input-field,
.runner-textarea-field {
font-size: 20px;
background: transparent;
border: 0;
font-family: "Roboto Mono", monospace;
flex-grow: 1;
}

.runner-input-field {
height: 50px;
text-align: center;
width: 100%;
font-size: 20px;
background: transparent;
border: 0;
overflow-x: auto;
white-space: nowrap;
font-family: "Roboto Mono", monospace
}

.runner-textarea-field {
max-width:100%;
text-align: left;
padding-left: 20px;
flex: 1 1 auto;
overflow: auto;
resize: none;
}

.runner-textarea-field:focus {
border: 0;
font-family: "Roboto Mono", monospace;
outline: none;
color: white;
}

.multi-line {
text-align: left !important;
}

.runner-input-field {
color: #cac7c7
}
Expand Down Expand Up @@ -275,13 +316,13 @@ body {
grid-template-columns: 15px auto;
}

.runner-result::-webkit-scrollbar {
.multi-line::-webkit-scrollbar, .runner-result::-webkit-scrollbar {
width: 15px;
}
.runner-result::-webkit-scrollbar-corner {
.multi-line::-webkit-scrollbar-corner, .runner-result::-webkit-scrollbar-corner {
background: rgba(0,0,0,0);
}
.runner-result::-webkit-scrollbar-thumb {
.multi-line::-webkit-scrollbar-thumb, .runner-result::-webkit-scrollbar-thumb {
background-color: #294794;
border-radius: 6px;
border: 4px solid rgba(0,0,0,0);
Expand Down
160 changes: 142 additions & 18 deletions src/renderer/src/runner/runner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ export default function Runner(): ReactElement {
const [suggestionSelection, setSuggestionSelection] = useState<number>(0)

const inputRef = useRef<HTMLInputElement>(null)
const textAreaRef = useRef<HTMLTextAreaElement>(null)
const historyRefs = useRef<Array<RefObject<HTMLDivElement>>>([])

const [isMultiLine, setIsMultiLine] = useState<boolean>(false)
const [multiLineArgs, setMultiLineArgs] = useState<string>('')

const reloadRuntimesFromBackend = async (): Promise<void> => {
const isCommanderMode = await window.electron.ipcRenderer.invoke('runner.isCommanderMode')
const runtimesFetch: Runtime[] = await window.electron.ipcRenderer.invoke('runtimes')
Expand Down Expand Up @@ -57,7 +61,7 @@ export default function Runner(): ReactElement {
const command: Command = await window.electron.ipcRenderer.invoke(
'runtime.prepareExecute',
runtime.id,
historicalExecution ? historicalExecution.prompt : runtime.prompt,
runtime.prompt,
'default'
)

Expand All @@ -66,6 +70,9 @@ export default function Runner(): ReactElement {
list: []
})

setMultiLineArgs('')
setIsMultiLine(false)

await reloadRuntimesFromBackend()

// renderer -> "backend"
Expand Down Expand Up @@ -97,7 +104,9 @@ export default function Runner(): ReactElement {
setSuggestionSelection(0)
})
}
const handlePromptChange = (event: ChangeEvent<HTMLInputElement>): void => {
const handlePromptChange = (
event: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>
): void => {
const value = event.target.value
if (historyIndex !== -1) {
applyHistoryIndex(-1)
Expand Down Expand Up @@ -152,25 +161,79 @@ export default function Runner(): ReactElement {
applyHistoryIndex(historyIndex)
}

const handleLineBreak = (e): void => {
const cursorPosition = e.target?.selectionStart
if (e.target === inputRef.current) {
if (!isMultiLine) setIsMultiLine(true)
e.preventDefault()
setMultiLineArgs(
runtime?.prompt.slice(cursorPosition, runtime.prompt.length) +
(multiLineArgs ? '\n' + multiLineArgs : '')
)
if (runtime) setPrompt(runtime.prompt.slice(0, cursorPosition))
textAreaRef.current?.focus()
}
}

const handleKeyDown = (e): void => {
if (e.key === 'Enter') {
execute(runtime).catch((error) => console.error(error))
if (e.key === 'Enter' && e.shiftKey) {
handleLineBreak(e)
}
if ((e.code === 'Backslash' || e.code === 'Backquote') && !e.shiftKey) {
e.preventDefault()
setIsMultiLine(!isMultiLine)
}
if (e.code === 'Backspace' && isMultiLine) {
if (e.target === textAreaRef.current && e.target.selectionStart === 0) {
e.preventDefault()
const multiLineSplit = multiLineArgs.trim().split('\n')
const _firstLine = runtime?.prompt.trim()
setPrompt(_firstLine + (_firstLine ? ' ' : '') + multiLineSplit.shift())
setMultiLineArgs(multiLineSplit.join('\n'))

if (multiLineSplit.length < 1) {
setIsMultiLine(false)
}

inputRef.current?.focus()
}
}
if (e.key === 'Enter' && !e.shiftKey) {
if (!runtime) return
execute({
...runtime,
prompt:
runtime.prompt.trim() +
(isMultiLine && multiLineArgs ? '\n' + multiLineArgs : multiLineArgs)
}).catch((error) => console.error(error))
}
if (e.code === 'ArrowDown') {
if (runtime && historyIndex > -1) {
applyHistoryIndex(historyIndex - 1)
} else if (historyIndex === -1) {
if (historyIndex === -1) {
if (suggestionSelection < suggestion.list.length - 1) {
setSuggestionSelection(suggestionSelection + 1)
} else if (isMultiLine && !e.ctrlKey && e.target === inputRef.current) {
const linebreakIndex = multiLineArgs.indexOf('\n')
const cursorPosition = e.target?.selectionStart
window.electron.ipcRenderer.invoke('runtime.complete', runtime?.prompt, 0).then(() => {
textAreaRef.current?.focus()
cursorPosition < linebreakIndex || linebreakIndex === -1
? textAreaRef.current?.setSelectionRange(cursorPosition, cursorPosition)
: textAreaRef.current?.setSelectionRange(linebreakIndex, linebreakIndex)
})
}
} else if (runtime && historyIndex > -1) {
if (historyIndex === 0) {
setPrompt('')
setIsMultiLine(false)
setMultiLineArgs('')
}
applyHistoryIndex(historyIndex - 1)
}
}

if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
let cursor = inputRef?.current?.selectionEnd ?? -1

console.log(cursor, runtime?.prompt.length)

// the selectionEnd hasn't updated yet. add and go
if (e.code === 'ArrowLeft') {
cursor--
Expand Down Expand Up @@ -202,9 +265,25 @@ export default function Runner(): ReactElement {
}
}

if (e.code === 'Escape') {
//exit suggestions
setSuggestion({
list: []
})
}

if (e.code === 'ArrowUp') {
if (suggestionSelection > 0) {
setSuggestionSelection(suggestionSelection - 1)
} else if (isMultiLine && !e.ctrlKey) {
const linebreakIndex = multiLineArgs.indexOf('\n')
const cursorPosition = e.target?.selectionStart
if (linebreakIndex < 0 || cursorPosition <= linebreakIndex) {
window.electron.ipcRenderer.invoke('runtime.complete', runtime?.prompt, 0).then(() => {
inputRef.current?.focus()
inputRef.current?.setSelectionRange(cursorPosition, cursorPosition)
})
}
} else if (runtime && historyIndex < runtime.history.length - 1) {
applyHistoryIndex(historyIndex + 1)
setSuggestion({
Expand Down Expand Up @@ -349,6 +428,37 @@ export default function Runner(): ReactElement {
return () => {}
}, [runtimeList])

useEffect(() => {
if (isMultiLine) {
textAreaRef.current?.focus()
return
}

if (runtime) setPrompt(runtime.prompt.trim() + (multiLineArgs ? ' ' : '') + multiLineArgs)
setMultiLineArgs('')
inputRef.current?.focus()
}, [isMultiLine])

useEffect(() => {
if (!historicalExecution || !historicalExecution.prompt) {
return
}

const splitPoint = historicalExecution?.prompt.indexOf('\n')
if (!splitPoint || splitPoint === -1) {
setIsMultiLine(false)
setMultiLineArgs('')
setPrompt(historicalExecution.prompt)
return
}

setIsMultiLine(true)
setPrompt(historicalExecution.prompt.substring(0, splitPoint))
setMultiLineArgs(
historicalExecution.prompt.substring(splitPoint + 1, historicalExecution.prompt.length)
)
}, [historicalExecution])

if (!runtime) {
return <p>Loading</p>
}
Expand Down Expand Up @@ -464,15 +574,29 @@ export default function Runner(): ReactElement {
<div className="runner-main">
<div className="runner-input-container">
<div className="runner-input">
<input
ref={inputRef}
autoFocus
className="runner-input-field"
placeholder=">"
onChange={handlePromptChange}
onKeyDown={handleKeyDown}
value={historicalExecution ? historicalExecution.prompt : runtime.prompt}
/>
<div className={isMultiLine ? 'multiline-input-container' : ''}>
<input
ref={inputRef}
placeholder=">"
className={`runner-input-field ${isMultiLine ? 'multi-line' : ''}`}
onChange={handlePromptChange}
onKeyDown={handleKeyDown}
value={runtime.prompt}
/>
</div>

{isMultiLine ? (
<textarea
ref={textAreaRef}
placeholder=">>"
className={`runner-textarea-field ${isMultiLine ? 'multi-line' : ''}`}
onChange={(e) => setMultiLineArgs(e.target.value)}
onKeyDown={handleKeyDown}
value={multiLineArgs}
/>
) : (
''
)}
<RunnerAC suggestion={suggestion} selection={suggestionSelection} />
</div>
</div>
Expand Down

0 comments on commit aabc215

Please sign in to comment.