157 lines
3.7 KiB
TypeScript
157 lines
3.7 KiB
TypeScript
import { Callable, Callable1, CanvasLayer, Expression, GArray, GDictionary, GError, InputEvent, RichTextLabel } from 'godot'
|
|
import { onready } from 'godot.annotations'
|
|
import AutocompleteLine from './autocomplete_line'
|
|
import { GArrayEnumerator } from '../../src/collection/enumerable'
|
|
import { Enumerable } from '../enumerable-ts/src/index'
|
|
|
|
export const Action = {
|
|
Toggle: '_dev_console_toggle',
|
|
Cancel: 'ui_cancel',
|
|
Up: 'ui_up',
|
|
Down: 'ui_down',
|
|
TextComplete: 'ui_text_completion_replace',
|
|
FocusNext: 'ui_focus_next',
|
|
FocusPrevious: 'ui_focus_prev',
|
|
} as const
|
|
|
|
export default class DevConsole extends CanvasLayer {
|
|
|
|
@onready("Container/VBoxContainer/Output")
|
|
private output!: RichTextLabel
|
|
|
|
@onready("Container/VBoxContainer/Input")
|
|
private input!: AutocompleteLine
|
|
|
|
private history: string[] = []
|
|
private expression = new Expression()
|
|
private parse_callable!: Callable1<string>
|
|
private history_index: number = -1
|
|
|
|
_ready(): void {
|
|
this._toggle()
|
|
const method_names = Enumerable.from(new GArrayEnumerator(this.get_method_list()))
|
|
.map((x: GDictionary) => x.get('name'))
|
|
.filter((x: string) => !x.startsWith('_'))
|
|
|
|
this.input.autocomplete_list = method_names.toArray()
|
|
this.parse_callable = Callable.create(
|
|
this,
|
|
this.submit_command
|
|
)
|
|
}
|
|
|
|
_input(event: InputEvent): void {
|
|
|
|
if (event.is_action_pressed(Action.Toggle)) {
|
|
this._toggle()
|
|
return
|
|
}
|
|
|
|
if (!this.visible) {
|
|
return
|
|
}
|
|
|
|
if (event.is_action_pressed(Action.Cancel)) {
|
|
this._cancel()
|
|
} else if (event.is_action_pressed(Action.Up)) {
|
|
this._move_history(-1)
|
|
} else if (event.is_action_pressed(Action.Down)) {
|
|
this._move_history(1)
|
|
}
|
|
}
|
|
|
|
_reset() {
|
|
this.history_index = -1
|
|
}
|
|
|
|
_cancel() {
|
|
this._reset()
|
|
|
|
if (this.input.has_focus()) {
|
|
this.input.release_focus()
|
|
} else {
|
|
this._hide()
|
|
}
|
|
}
|
|
|
|
async _move_history(direction: (-1 | 1)) {
|
|
this.history_index = (this.history_index + direction) % this.history.length
|
|
|
|
if (this.history_index < 0) {
|
|
this.history_index = this.history.length - 1
|
|
}
|
|
|
|
this.input.text = this.history[this.history_index]
|
|
await this.get_tree().process_frame.as_promise()
|
|
this.input.set_caret_to_end()
|
|
}
|
|
|
|
_autocomplete() {
|
|
this.input.autocomplete()
|
|
}
|
|
|
|
async _show() {
|
|
await this.get_tree().process_frame.as_promise()
|
|
|
|
this.input.text_submitted.connect(this.parse_callable)
|
|
this.input.grab_focus()
|
|
}
|
|
|
|
_hide() {
|
|
this.input.text_submitted.disconnect(this.parse_callable)
|
|
this.input.release_focus()
|
|
}
|
|
|
|
async _toggle() {
|
|
this._reset()
|
|
|
|
this.visible = !this.visible
|
|
|
|
if (this.visible) {
|
|
this._show()
|
|
} else {
|
|
this._hide()
|
|
}
|
|
}
|
|
|
|
print(value: string) {
|
|
this.output.append_text(value)
|
|
this.output.append_text('\n')
|
|
}
|
|
|
|
wrap_in_tag(text: string, tag: [string, string]): string {
|
|
return `[${tag[0]}=${tag[1]}]${text}[/${tag[0]}]`
|
|
}
|
|
|
|
format(text: string, tags: Record<string, string>): string {
|
|
return Object.entries(tags).reduce(this.wrap_in_tag, text)
|
|
}
|
|
|
|
print_error(error: string) {
|
|
this.print(this.format(error, { color: 'red' }))
|
|
}
|
|
|
|
echo(command: string) {
|
|
this.print(this.format(`> ${command}`, { color: 'white' }))
|
|
}
|
|
|
|
submit_command(command: string) {
|
|
this.input.clear()
|
|
this.history.push(command)
|
|
this.echo(command)
|
|
const error = this.expression.parse(command)
|
|
|
|
if (error !== GError.OK) {
|
|
this.print_error(this.expression.get_error_text())
|
|
return
|
|
}
|
|
|
|
const result = this.expression.execute(new GArray(), this)
|
|
|
|
if (this.expression.has_execute_failed()) {
|
|
this.print_error(this.expression.get_error_text())
|
|
} else {
|
|
this.print(result.toString())
|
|
}
|
|
}
|
|
}
|