Simulate Keyboard Inputs

Background

You know how in text editors and just text fields in general, there's always that blinking bar where you type? Yeah, simulate typing there.

You'll be given 3 inputs: The current text field, the position of the cursor, and the input sequence.

Here's an example:

Hello, World!
5
[U];[U];[B];[B];[B];[B];[B];[U];i;[R];[R];[R];[R];[R];[R];[R];[R];[R];[B];.
=> Hi, World.


Firstly, the number 5 tells us that the cursor is here, represented by the asterisk: Hello*, World!. If you take the string as zero-indexed, and choose the left side, you will find the cursor's starting point.

Next, we have 2 [U]s. [U] is a special input, representing undo. However, there is nothing to undo, so nothing happens.

Then, we have 5 [B]s. [B] is another special input, representing backspace. This tells us to erase 5 characters to the left of the cursor, one at a time. This gives *, World!.

Next, we have [U]. This tells us to cancel out the previous input (if there's anything to cancel out). If you have two [U]s, then you cancel out the last two instructions before the [U]s. Logic extends to all number of [U]s. Cancelling out the previous input, we have H*, World!.

Next, we have i. If there are no square brackets around the character, just add it to the text as a string, to the left of the cursor: Hi*, World!.

Now, we have 9 [R]s. [R] represents Move Cursor Right, so we move the cursor right 9 times: Hi, World!*. Note how it only actually takes 8 [R]s to get to the end, so the final [R] doesn't do anything.

Now, a [B]. This gives us Hi, World*.

Finally, .. This gives us Hi, World..

//

The list of special inputs is shown here:

[B] = Backspace
[U] = Undo
[R] = Move Cursor Right
[L] = Move Cursor Left


Of course, within reason, you can change these special inputs (as long as it won't conflict with any non-special inputs.)

//

With regards to other rules:

• Left or Backspace when the cursor is furthest left does nothing.
• Right when the cursor is furthest right also does nothing.
• Undo when there's nothing to undo also does nothing.

In terms of undoing, [L];[U] and [R];[U] moves the cursor to its original position, before the [L] or [R]. [B];[U] reverts the backspace.

In terms of stacking undos, an undo will not undo another undo. That is, considering the following:

z;a;b;[U];[U];c;[U];[U]

The first two undos would get rid of a and b. The third would get rid of c. Finally, the last undo would get rid of the z. You can think of it like a stack, where every undo removes itself and the input below it.

However, in the special case the previous input does nothing:

a;[R];[U]

The undo should also do nothing.

• Sample Input:
1. The current text field
2. The position of the text cursor (<= length of current text field)
3. The input sequence

Wrt the input sequence, in my examples I have used a string where each instruction is separated by semicolons. You can take the input sequence in any reasonable format - but note that the no input can contain this character. You can expect the non-special inputs and the text field to contain no \ns or \ts. They will also not contain any [B]s (and other), [, or whatever you're using for making the special input unique.

• Output: Return the final text field, after the sequence has been executed.

Test Cases

Input => Output

Hello, World!
5
[U];[U];[B];[B];[B];[B];[B];[U];i;[R];[R];[R];[R];[R];[R];[R];[R];[R];[B];.
=> Hi, World.

This is [tag:code-golf], so shortest answer wins.
13
=> This is [tag:code-challenge], so best response or answer will be accepted as answer!


This is , so shortest answer wins. (# of bytes)

• what happens to the cursor after a [L];[U] sequence? I see there's a test case with it but could you specify it in the challenge description?
– c--
Commented Jul 23, 2023 at 21:04
• Can a [U] undo a [U]? E.g. does (with empty start) a;[U];bc;[U];[U] give a because the last [U] undoes the first [U] removal of the a?
Commented Jul 23, 2023 at 21:10
• @c-- [L];[U] moves the cursor back to where it was (ie before the [L]. Adding now. Commented Jul 23, 2023 at 22:30
• @Adám No, in this case, the [U] would just not undo anything, leaving you with an empty string. Adding some clarifications now. Commented Jul 23, 2023 at 22:32
• In hindsight, I should have probably made it so logic follows what you wrote, but I wrote the test cases with the other rules in mind. Commented Jul 23, 2023 at 22:38

Ruby, 145 bytes

Not sure if it breaks the rules to have the special commands as integers. U=0, L=-1, R=1, B=8

The only reason B=8 is because the control code for backspace is \x08 lol

->s,l,i{i[(x=i.index 0)-[1,x].min..x]=[]while[]!=[0]&i
i.map{|e|l=l.clamp 0,s.size;e.ord==e ?e<5?l+=e:(s[l-=1]=''if l>0):(s[l,0]=e;l+=e.size)}
s}


Try it online!

Ruby, 153 bytes

Version that uses characters only in case the above answer breaks the rules: U=\x00, L=\x01, R=\x03, B=\x08

->s,l,i{i[(x=i.index"\0")-[1,x].min..x]=[]while[]!=[?\0]&i
i.map{|e|l=l.clamp 0,s.size;c=e.ord;c<9?c<5?l+=c-2:(s[l-=1]=''if l>0):(s[l,0]=e;l+=e.size)}
s}


Q, 248 bytes

w:{s:{x like"[[]",y,"[]]"};if[10=(@:)z;z:";"vs z];while[z[0]~"[U]";z:1_z];\$[0=(#:)z;x;not(^:)r:(*:)(&:)s[z;"U"];w[x;y;_[z _ r;r-1]];s[o:z 0;"R"];w[x;(#:)[x]&y+1;1_z];s[o;"L"];w[x;0|y-1;1_z];s[o;"B"];w[x _ y-1;0|y-1;1_z];w[#[y;x],o,_[y;x];y+1;1_z]]}


ungolfed:

wp: { [text; cursor; input]
if[10h=type input; input: ";" vs input];                    // convert semicolon-delimited string to list of strings
special: {x like "[[]",y,"[]]"};                            // function to check whether a string is like e.g. "[E]"
while[input[0]~"[U]"; input: 1 _ input];                    // remove leading undo's since there's nothing to undo
if[not null rm: first where special[input; "U"];            // find the first "[U]" operation -- re-call this function after removing it and the preceding operation
: wp[text; cursor; _[input _ rm;rm-1]];
];
if[0=count input; : text];                                  // base case -- return text if there are no more operations
op: input 0;
if[special[op; "R"];                                        // re-call this function with 1 added to cursor (capping at length of text)
: wp[text; count[text]&cursor+1; 1_input]];
if[special[op; "L"];                                        // re-call this function with 1 removed from the cursor (capping at 0)
: wp[text; 0|cursor-1; 1_input]];
if[special[op; "B"];                                        // re-call this function with a) the character before the cursor removed from text and b) 1 removed from the cursor (capping at 0)
: wp[text _ cursor-1; 0|cursor-1; 1_input]];
: wp[ #[cursor;text],op,_[cursor;text]; cursor+1; 1_input]; // re-call this function with the characters added after the cursor in text
}


Python 3.8 (pre-release), 228 bytes

def f(s,c,Z,d=[]):
for i in Z:d=d[:-1]if i[0]and'T'<i[1]else d+[i]
for S,D in d:
if S:
if'Q'<D:c=min(c+1,len(s))
elif'K'<D:c=max(c-1,0)
else:s=s[:(f:=max(c-1,0))]+s[c:];c=f
else:s=s[:c]+D+s[c:];c+=len(D)
return s


Try it online!

The first for loop uses the stack d to get rid of all the undos. The second for loop loops over each instruction and modifies the string s accordingly. Almost definitely still golfable.

Inputs: list of tuples: (opcode, string). The opcode is 1 if the string is a special command, and 0 otherwise. Ex: [(1, 'R'), (1, 'U'), (0, 'hi')] This may be stretching the acceptable input format a bit, so I made a version which can accept the input format that the OP used:

Python 3.8 (pre-release), 267 bytes

def f(s,c,Z,d=[]):
for i in Z.split(';'):g='['in i;i=i[1]if g else i;d=d[:-1]if g and'T'<i else d+[(g,i)]
for S,D in d:
if S:
if'Q'<D:c=min(c+1,len(s))
elif'K'<D:c=max(c-1,0)
else:s=s[:(f:=max(c-1,0))]+s[c:];c=f
else:s=s[:c]+D+s[c:];c+=len(D)
return s


Try it online!

Charcoal, 88 bytes

Ｓθ≔⁻ＬθＮηＷＳ≡ι´⟲≔∧υ⊟υι⊞υιＦυ≡ι´←≧⁺‹ηＬθη´→≧⁻‹⁰ηη´⮌≔⁺✂θ⁰±⊕η¹✂θ⁻ＬθηＬθ¹θ≔⁺⁺✂θ⁰⁻Ｌθη¹ι✂θ⁻ＬθηＬθ¹θθ


Try it online! Link is to verbose version of code. Takes the input sequence as a list of newline-terminated strings where ⟲ is undo, ← is left, → is right, and ⮌ is backspace. Explanation:

Ｓθ≔⁻ＬθＮη


Input the starting string and position, but calculate the position as a count from the end of the string, because that remains invariant under insertion and deletion.

ＷＳ


Loop through each command string.

≡ι´⟲≔∧υ⊟υι⊞υι


If it's a ⟲ then remove the previous command if any otherwise add the command.

Ｆυ≡ι


Loop through the remaining commands.

´←≧⁺‹ηＬθη


Handle left.

´→≧⁻‹⁰ηη


Handle right.

´⮌≔⁺✂θ⁰±⊕η¹✂θ⁻ＬθηＬθ¹θ


Handle backspace.

≔⁺⁺✂θ⁰⁻Ｌθη¹ι✂θ⁻ＬθηＬθ¹θ


Handle everything else.

θ


Output the resulting string.