Hyperlinks: pasting partial commands next to the >

I’ve tried hyperlinks with Basic Hyperlinks by Emily Short, Inline Hyperlinks by Erik Temple, and the Flexible Windows extension, and with all three I run into these two things:

  1. When you click the hyperlink, the corresponding command appears and automatically gets submitted as if you have pushed enter. Is it possible to just paste the command without submitting it, so that the player can type additional text?

  2. The command always appears below the > instead of next to it. Is there a way to make it appear next to it?

Sample transcript of current behavior:

take
What do you want to take?

EDIT:
To address #2, I found this in another thread:

[code]When play begins:
sets echo line events off.

To sets echo line events off:
(- glk_set_echo_line_event(gg_mainwin,0); -)[/code]

Are there any side effects to watch out for?

You will probably have to add a test for whether that call is supported. Older interpreters (Zoom) will not execute it.

To sets echo line events off:
	(- if (glk_gestalt(gestalt_LineInputEcho, 0)) glk_set_echo_line_event(gg_mainwin,0); -)

Zoom also gets the linebreak behavior wrong when echoing is not turned off. That’s an old bug. Replicated in the Mac I7 IDE, unfortunately.

Thanks.

Hmm. I didn’t realize that when echoing was turned off, it would mess up regular typed commands, erasing them and putting the results next to the >.

There are a few related things here that should really be handled better by Inform (or a built-in extension).

The point of the glk_set_echo_line_event() call is that when it’s turned off, it’s the game’s responsibility to print the command that the player just typed. (Using the Glk “input” style.) When it’s turned on – the default setting – the interpreter takes care of it.

That helps a lot. Thanks!

Partial commands are sort of working now, except that you can’t delete a partial command that’s been pasted by the hyperlink. (I guess “say” isn’t going to work for pasting the partial command?)

Is there a way to fake keyboard input–to print the partial command on the screen, but make it deletable with the backspace key, as though the player had typed it in via the keyboard but hadn’t yet pressed enter?

Yes. There’s a line in VM_ReadKeyboard:

glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);

If you pre-load text into the input buffer, and pass the number of characters as the last argument (replacing 0), then that will be the initial text that the player is editing.

Thank you!

Would anyone mind giving me an example of pre-loading text into the input buffer and then putting that text onscreen for the player to edit? I don’t know, maybe like this?

[code]To copy (T - a text) into the input buffer:
???

To display fake keyboard input with (N - a number) characters:
(- glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, ({N})); -). [???]

When play begins:
Copy “Hello” into the input buffer;
display fake keyboard input with 5 characters.[/code]
Edit: This post seems somewhat relevant, but it seems to be using a partial command that’s previously been typed, rather than one that was never typed.

Edit#2: actually, “when play begins” is a bad example because there’s no command prompt…

Writing it in I7 code is not ideal. You want to load up the input line just before requesting input in the VM_ReadKeyboard function.

You could rig it up this way:

The preload-line is a text that varies. The preload-line is "".

Then, modifying VM_ReadKeyboard and replacing the glk_request_line_event call with these two lines:

	VM_PrintToBuffer(a_buffer, INPUT_BUFFER_LEN-WORDSIZE, TEXT_TY_Say, (+ preload-line +) );
	glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, a_buffer-->0);

The VM_PrintToBuffer call invokes TEXT_TY_Say, passing the global variable preload-line, and writes the resulting text to the input buffer. It stores the number of characters generated in the first word of the buffer (a_buffer–>0); this is what the rest of VM_ReadKeyboard expects. If preload-line is the empty string, then a_buffer–>0 winds up as zero and you get the standard input behavior.

Thank you!

Am I modifying this correctly, as far as including the appropriate section and putting the correct clause at the end (“instead of “Keyboard Input” in “Glulx.i6t””)? Or do I also need to include VM_KeyChar and VM_KeyDelay? I pasted VM_ReadKeyboard from inform7.com/sources/src/i6templa … B-glut.pdf and replaced the line as you said.

Include (- [ VM_ReadKeyboard a_buffer a_table done ix; if (gg_commandstr ~= 0 && gg_command_reading ~= false) { done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE, (INPUT_BUFFER_LEN-WORDSIZE)-1); if (done == 0) { glk_stream_close(gg_commandstr, 0); gg_commandstr = 0; gg_command_reading = false; ! L__M(##CommandsRead, 5); would come after prompt ! fall through to normal user input. } else { ! Trim the trailing newline if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1; a_buffer-->0 = done; VM_Style(INPUT_VMSTY); glk_put_buffer(a_buffer+WORDSIZE, done); VM_Style(NORMAL_VMSTY); print "^"; jump KPContinue; } } done = false; VM_PrintToBuffer(a_buffer, INPUT_BUFFER_LEN-WORDSIZE, TEXT_TY_Say, (+ preload-line +) ); glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, a_buffer-->0); while (~~done) { glk_select(gg_event); switch (gg_event-->0) { 5: ! evtype_Arrange DrawStatusLine(); 3: ! evtype_LineInput if (gg_event-->1 == gg_mainwin) { a_buffer-->0 = gg_event-->2; done = true; } } ix = HandleGlkEvent(gg_event, 0, a_buffer); if (ix == 2) done = true; else if (ix == -1) done = false; } if (gg_commandstr ~= 0 && gg_command_reading == false) { glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0); glk_put_char_stream(gg_commandstr, 10); ! newline } .KPContinue; VM_Tokenise(a_buffer,a_table); ! It’s time to close any quote window we’ve got going. if (gg_quotewin) { glk_window_close(gg_quotewin, 0); gg_quotewin = 0; } #ifdef ECHO_COMMANDS; print "** "; for (ix=WORDSIZE: ix<(a_buffer-->0)+WORDSIZE: ix++) print (char) a_buffer->ix; print "^"; #endif; ! ECHO_COMMANDS ]; -) instead of "Keyboard Input" in "Glulx.i6t".

Also, is there a simple way to replace this conditionally (i.e. only if hyperlinks are being used)? (It’s not a big deal if not.)

The more I think about it, the more I think all three sections need to be replaced, like this

[code]Include (- [ VM_KeyChar win nostat done res ix jx ch;
jx = ch; ! squash compiler warnings
if (win == 0) win = gg_mainwin;
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
done = glk_get_line_stream(gg_commandstr, gg_arguments, 31);
if (done == 0) {
glk_stream_close(gg_commandstr, 0);
gg_commandstr = 0;
gg_command_reading = false;
! fall through to normal user input.
} else {
! Trim the trailing newline
if (gg_arguments->(done-1) == 10) done = done-1;
res = gg_arguments->0;
if (res == ’\’) {
res = 0;
for (ix=1 : ix<done : ix++) {
ch = gg_arguments->ix;
if (ch >= ’0’ && ch <= ’9’) {
@shiftl res 4 res;
res = res + (ch-’0’);
} else if (ch >= ’a’ && ch <= ’f’) {
@shiftl res 4 res;
res = res + (ch+10-’a’);
} else if (ch >= ’A’ && ch <= ’F’) {
@shiftl res 4 res;
res = res + (ch+10-’A’);
}
}
}
jump KCPContinue;
}
}
done = false;
glk_request_char_event(win);
while (~~done) {
glk_select(gg_event);
switch (gg_event–>0) {
5: ! evtype_Arrange
if (nostat) {
glk_cancel_char_event(win);
res = $80000000;
done = true;
break;
}
DrawStatusLine();
2: ! evtype_CharInput
if (gg_event–>1 == win) {
res = gg_event–>2;
done = true;
}
}
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
if (ix == 2) {
res = gg_arguments–>0;
done = true;
} else if (ix == -1) done = false;
}
if (gg_commandstr ~= 0 && gg_command_reading == false) {
if (res < 32 || res >= 256 || (res == ’\’ or ’ ’)) {
glk_put_char_stream(gg_commandstr, ’\’);
done = 0;
jx = res;
for (ix=0 : ix<8 : ix++) {
@ushiftr jx 28 ch;
@shiftl jx 4 jx;
ch = ch & $0F;
if (ch ~= 0 || ix == 7) done = 1;
if (done) {
if (ch >= 0 && ch <= 9) ch = ch + ’0’;
else ch = (ch - 10) + ’A’;
glk_put_char_stream(gg_commandstr, ch);
}
}
} else {
glk_put_char_stream(gg_commandstr, res);
}
glk_put_char_stream(gg_commandstr, 10); ! newline
}
.KCPContinue;
return res;
];
[ VM_KeyDelay tenths key done ix;
glk_request_char_event(gg_mainwin);
glk_request_timer_events(tenths*100);
while (~~done) {
glk_select(gg_event);
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
if (ix == 2) {
key = gg_arguments–>0;
done = true;
}
else if (ix >= 0 && gg_event–>0 == 1 or 2) {
key = gg_event–>2;
done = true;
}
}
glk_cancel_char_event(gg_mainwin);
glk_request_timer_events(0);
return key;
];
[ VM_ReadKeyboard a_buffer a_table done ix;
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE,
(INPUT_BUFFER_LEN-WORDSIZE)-1);
if (done == 0) {
glk_stream_close(gg_commandstr, 0);
gg_commandstr = 0;
gg_command_reading = false;
! L__M(##CommandsRead, 5); would come after prompt
! fall through to normal user input.
}
else {
! Trim the trailing newline
if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1;
a_buffer–>0 = done;
VM_Style(INPUT_VMSTY);
glk_put_buffer(a_buffer+WORDSIZE, done);
VM_Style(NORMAL_VMSTY);
print “^”;
jump KPContinue;
}
}
done = false;
! New code begins here
VM_PrintToBuffer(a_buffer, INPUT_BUFFER_LEN-WORDSIZE, TEXT_TY_Say, (+ preload-line +) );
glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, a_buffer–>0);
! New code ends here
while (~~done) {
glk_select(gg_event);
switch (gg_event–>0) {
5: ! evtype_Arrange
DrawStatusLine();
3: ! evtype_LineInput
if (gg_event–>1 == gg_mainwin) {
a_buffer–>0 = gg_event–>2;
done = true;
}
}
ix = HandleGlkEvent(gg_event, 0, a_buffer);
if (ix == 2) done = true;
else if (ix == -1) done = false;
}
if (gg_commandstr ~= 0 && gg_command_reading == false) {
glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer–>0);
glk_put_char_stream(gg_commandstr, 10); ! newline
}
.KPContinue;
VM_Tokenise(a_buffer,a_table);
! It’s time to close any quote window we’ve got going.
if (gg_quotewin) {
glk_window_close(gg_quotewin, 0);
gg_quotewin = 0;
}
#ifdef ECHO_COMMANDS;
print "** ";
for (ix=WORDSIZE: ix<(a_buffer–>0)+WORDSIZE: ix++) print (char) a_buffer->ix;
print “^”;
#endif; ! ECHO_COMMANDS
];

-) instead of “Keyboard Input” in “Glulx.i6t”.[/code]

When I do something like this, the preload-line doesn’t appear until the next turn (amongst other weirdness).

[code]
To specially re-request line event in the/-- main window:
(- glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer–>0); -)

The player’s previous input this turn is a snippet variable.

To get typed-command as (S - a snippet): (-
KeyboardPrimitive(buffer, parse);
{S} = 100 + WordCount(); -)

Last for processing hyperlinks:
repeat through the chosen table of commands:
if (there is a link number entry) and (the hyperlink ID is link number entry):
if there is a partial entry and the partial entry is true: [if we are clicking a partial command]
get typed-command as the player’s previous input this turn;
if the player’s previous input this turn is “”:
cancel line input in the main window;
now preload-line is the substituted form of "[hyperlinked command entry] ";
specially re-request line event in the main window;
otherwise:
let new addition be the substituted form of “[hyperlinked command entry]”;
cancel line input in the main window;
now preload-line is the substituted form of “[player’s previous input this turn][new addition]”;
specially re-request line event in the main window;
otherwise: [if it’s a complete command]
now the glulx replacement command is the Typed Command entry;
rule succeeds;
now glulx replacement command is “”;[/code]

Any suggestions?

(Sorry for all the questions. I didn’t realize it was going to be this involved.)

Yeah, it’s getting messy. Let me toss some ideas around. We need this improved input loop anyhow, I might as well write it out.

If we can get something rugged then I’ll include it in Glulx Entry Points for the next I7 release.

I’ve started sketching this out at github.com/erkyrath/i7-exts/blo … 0Input.i7x

Right now there’s a lot of documentation and almost no code there. Scroll down to the DOCUMENTATION line to get an overview of what I’m thinking.

It turns out to require some deep hacking – I will be replacing Parser Letter A at a minimum. So it won’t be suitable for including in Glulx Entry Points.

Perhaps before you do too much more I should explain what my plans are for GEP.

Currently GEP only gets involved when HandleGlkEvent() is called. I will change it (as you have, except I plan to do as much in I7 as possible) so that GEP handles the entire loop. GEP will provide a phrase taking a ‘description of g-events’ parameter. When an event comes in it will run the glulx input handling rules as it does now. The rules will have two outcomes, one which says stop processing and go back to the loop, and one which says stop processing and exit the loop. The last rule will be built in and compare the current event to the description we passed in, stopping the loop if it matches. A description will allow us to stop for a specific type, or for multiple types. I think it would also help to have before and after rulebooks.

So for example, my debounce arrange events rule could be changed to simply set a flag in the loop, and then run the proper rules through an after rule. I’m not sure if that will work reliably. It would be much handier if we could have more timers!

This will require changes to the three keyboard input functions, but not to the parser code. What would you be replacing Parser Letter A for in your plan?

Edit: From looking at yours in a little more detail, it looks like you’re preparing for a future situation in which other events get called to rise to the parser level. My plan is not to do that yet, but to more robustly handle events. They can do things outside the parser system or modify the line input buffer. It should be possible to port yours to build on GEP later.

If I’d known this was going to be so much work for you guys, I probably wouldn’t have asked about it. I had initially assumed the hyperlinks extensions already handled this. Sorry. :-/

Nah, the flaws with GEP have been raised before.

That’s right.

The context here (let me explain to everybody else) is an email thread from a couple of months ago, where some of us were talking about what it would take for I7 to cleanly handle games without a command line – hyperlink-only games, keystroke-based roguelikes – as well as parser games and hybrids (parser-plus-hyperlink).

I said (paraphrased) “Oh, that’s easy, you shouldn’t have to touch the parser much at all!” And Dannii said (paraphrase) “Um, maybe, maybe not.”

This forum thread reminded me of the problem, so now I am looking at it. Including the scary low-level parser stuff that I’ve always been afraid to mess with. The good news is, once you start messing with it, you can clean up quite a bit of historical cruft.

(E.g.: The prompt for YesOrNo is half hardwired into that function and half left up to the caller. DrawStatusLine is called from three different places, each right before a KeyboardPrimitive call. There’s no way to have blank lines not be ignored. Etc.)

(Plus, I want to make the system handle glk_cancel_line_event calls for you where possible, instead of forcing you to spray-and-pray.)

This is all experimental. I want to try various toy games, including pure-hyperlink and hybrid, until the programming model feels solid. I’ll contribute it to the extensions library, but as an experimental possible-way-of-the-future, not a standard component for everybody to use.

An alternative for the really big picture view would be to change to an entirely event driven model, i.e., that the event loop is in Main() and the turn process happens as a result of input events.

Problem is I6 doesn’t have either closures or continuations, so this would be tricky as it is currently. I don’t really understand how such things are implemented, but maybe there could be a code and memory-lite system we could implement either through a I6 compiler change, or in 6.3x currently, to make it easier to have callbacks etc. Or a change to the Glulx VM so that the callstack can be emptied and then glk events could start new callstacks.

Let’s come back to that in another five year’s time :stuck_out_tongue_winking_eye: