hcchengithub/ChatFORTH

Improve auto-complete with Shift+Tab

Opened this issue · 0 comments

ChatFORTH helped me coding to modify itself!

jeforth.3we 系列都有 Tab key auto-complete 的功能,自動找出類似的 word (command) 所以 word name 可以很長沒關係。今進一步使 Shift+Tab 把可能的 words 列為選單,用上下鍵選擇或直接 click。用 AI 來幫我做到!

Can you understand this snippet? it's in FORTH programming language but with inline JavaScript:

    : history-selector ( -- ) \ Popup command history for selection
            <o> <br><select style="width:90%;padding-left:2px;font-size:16px;"></select></o> ( select )
            <js>
                for (var i=0; i<vm.cmdhistory.array.length; i++){
                    if(vm.cmdhistory.array[i].split('\n').length>1) continue;
                    var option = document.createElement("option");
                    option.text = vm.cmdhistory.array[i];
                    js: tos().add(option);
                }
                tos().size = Math.min(16,tos().length);
                tos().selectedIndex=tos().length-1;
                vm.scroll2inputbox();tos().focus();
                var select = tos().onclick = function(){
                    inputbox.value = tos().value;
                    execute("removeElement");
                    vm.scroll2inputbox();
                    return (false);
                }
                tos().onkeydown = function(e){
                    e = (e) ? e : event; var keycode = (e.keyCode) ? e.keyCode : (e.which) ? e.which : false;
                    switch(keycode) {
                        case 27: /* Esc  */ execute("removeElement"); vm.scroll2inputbox(); break;
                        case 38: /* Up   */ tos().selectedIndex = Math.max(0,tos().selectedIndex-1); break;
                        case 40: /* Down */ tos().selectedIndex = Math.min(tos().length-1,tos().selectedIndex+1); break;
                        case 13: /* Enter*/ setTimeout(select,1); break;
                    }
                    return (false);
                }
            </js> ; private

As an AI language model, I can understand the syntax of FORTH programming language and JavaScript, but I am not capable of executing code. However, based on my understanding of these languages, this snippet appears to be defining a function called "history-selector" that creates a popup window with a dropdown menu containing command history options. The JavaScript code inside the tags is responsible for populating the dropdown list with items from an array called "vm.cmdhistory.array." It also sets up event handlers for various user interactions such as selecting an item from the list or pressing arrow keys to navigate through it.

Overall, it seems like this code is designed to enhance user experience by providing easy access to previously used commands in a convenient way.


Very good! also this snippet:

    code {Tab}      ( -- ) \ Inputbox auto-complete
            with(this){
                if(index == 0){ // index 初值來自 document.onkeydown event, 這是剛按下 Tab 的線索。
                    var a=('h '+inputbox.value+' t').split(/\s+/);
                    a.pop(); a.shift();
                    this.hint = a.pop()||""; // the partial word to be autocompleted
                    this.cmdLine = inputbox.value.slice(0,inputbox.value.lastIndexOf(hint))||"";
                    this.candidate = [];
                    if(hint){
                        for(var key in wordhash) {
                            if(key.toLowerCase().indexOf(hint.toLowerCase())!=-1) candidate.push(key);
                        }
                        candidate.push(hint);
                    }
                }
                if(hint){
                    if(index >= candidate.length) index = 0;
                    inputbox.value = cmdLine + candidate[index++];
                    push(false); // 吃掉這個 Tab key。
                } else {
                    push(true); // 不吃掉這個 Tab key,給別人處理。
                }
            }
            end-code
            last :: index=0

This snippet is defining a function called "code" that handles input box auto-complete when the user presses the Tab key. The code uses several variables such as "hint," "cmdLine," and "candidate" to store information about the partial word to be autocompleted, current command line text, and possible candidates for autocompletion.

When the Tab key is pressed, this function checks if it's an initial press or not by checking whether index equals zero. If it's the first press of Tab, then it sets up some variables based on the current input value in the input box. It creates an array of words separated by spaces from 'h' at start till 't' at end of string entered so far and stores them in a variable called 'a'. Then it pops off both ends of this array ('h' and 't') and assigns its second last element (if present) to a variable called hint which is used as partial word for autocomplete suggestions.

If there are any candidate words available for auto-completion based on what has been typed so far, then they are added to an array called "candidate." This code also increments through each candidate sequentially upon each subsequent press of Tab until all candidates have been cycled through.

Finally, depending on whether there is any hint available or not after pressing tab key once or multiple times consecutively before typing anything else further into inputbox; this function either replaces part/all contents in textbox with suggested completion (i.e., cmdLine + candidate[index++]) or passes control back to other handlers using push(true).


Very good! Now read this snippet:

    : {up}          ( -- boolean ) \ Inputbox keydown handler, get previous command history.
            js> event.altKey if
                history-selector false \ eat the key
            else
                js> event.ctrlKey if
                    js: inputbox.value=vm.cmdhistory.up() false \ eat the key
                else
                    js> inputbox.value==""||inputbox.value=="\n"
                    up/down-recall-needs-alt-key? not and
                    if
                        history-selector false \ eat the key
                    else
                        true \ don't eat the key, let it pass down
                    then
                then
            then ;

This snippet defines a function called "{up}" that is a keydown event handler for the input box. This function checks whether the "Alt" key or "Ctrl" key has been pressed along with the "Up" arrow key.

If the Alt Key is pressed, then it calls another function named 'history-selector' and returns false to prevent further processing of this event by other handlers.

If Ctrl Key is pressed, then it sets the value of inputbox as vm.cmdhistory.up(), which retrieves previous command history from an array called "vm.cmdhistory". It also returns false to prevent any further handling of this event by other handlers.

If neither Alt nor Ctrl keys are being held down while pressing Up Arrow Key but there is some text in InputBox (i.e., if inputbox.value==""||inputbox.value=="\n") and up/down-recall-needs-alt-key? flag is NOT set; then it calls another function named 'history-selector' and returns false to prevent any further processing of this event by other handlers.

Otherwise, if none of these conditions apply (i.e., no special keys are being held down, and there's no need for history recall), it lets the default behavior occur without eating up this keystroke.


Help me to 模仿 history-selector to write a command-selector that allow user to select the wanted command. I will use command-selector to improve {Tab}


Sure, I can help you with that. Here's an example implementation of a "command-selector" function:

: command-selector ( -- )
    <o> <br><select style="width:90%;padding-left:2px;font-size:16px;"></select></o>
    <js>
        for(var key in wordhash) {
            var option = document.createElement("option");
            option.text = key;
            js: tos().add(option);
        }
        tos().size = Math.min(16,tos().length);
        tos().selectedIndex=tos().length-1;
        vm.scroll2inputbox();tos().focus();
        var select = tos().onclick = function(){
            inputbox.value = tos().value + ' ';
            execute("removeElement");
            vm.scroll2inputbox();
            return (false);
        }
        
        // Handle keyboard events
         tos().onkeydown = function(e){
             e = (e) ? e : event; 
             var keycode = (e.keyCode) ? e.keyCode : (e.which) ? e.which : false;
             
             switch(keycode) {
                 case 27: /* Esc  */ execute("removeElement"); vm.scroll2inputbox(); break;
                 case 38: /* Up   */ 
                     if(tos() && tos()[tos().selectedIndex - 1]) {
                         inputbox.value=tos()[tos().selectedIndex - 1].text+' '; 
                     } 
                     break;
                 case 40: /* Down */
                    if(tos() && tos()[tos().selectedIndex + 1]) { 
                        inputbox.value=tos()[tos().selectedIndex + 1].text+' '; 
                    } else if(tos()) {  
                        inputbox.value='';   
                    }
                    break;
                case 13:
                setTimeout(select,100); // wait a bit before selecting so the dropdown menu disappears first
                break;    
             }
             
             return false; // prevent default behavior of arrow keys and Enter Key.
         };
         
    </js> ; private

This code creates a dropdown list containing all available commands from your FORTH program using the "wordhash" dictionary. It also sets up various event handlers for handling user interactions such as clicking on an item or pressing arrow keys.

When the user selects an item from the list by clicking on it, this code updates the text in InputBox accordingly and removes the dropdown menu. Similarly, when users press Up/Down Arrow Keys while Command Selector is open then it moves up/down through items in drop-down list one-by-one and updates InputBox value accordingly.

You can use this "command-selector" function along with "{Tab}" handler for autocomplete suggestions based on previously used commands.


Good, now modify the definition of {Tab}, let it call command-selector when Alt-Tab is pressed.


Sure, here's an updated version of the "{Tab}" function that calls the "command-selector" function when Alt-Tab is pressed:

: {Tab} ( -- boolean ) \ Inputbox keydown handler, inputbox autocomplete
    js> event.keyCode==9 js> event.altKey and if
        command-selector false \ eat the key
    else 
        code {
            with(this){
                if(index == 0){ // index 初值來自 document.onkeydown event, 這是剛按下 Tab 的線索。
                    var a=('h '+inputbox.value+' t').split(/\s+/);
                    a.pop(); a.shift();
                    this.hint = a.pop()||""; // the partial word to be autocompleted
                    this.cmdLine = inputbox.value.slice(0,inputbox.value.lastIndexOf(hint))||"";
                    this.candidate = [];
                    if(hint){
                        for(var key in wordhash) {
                            if(key.toLowerCase().indexOf(hint.toLowerCase())!=-1) candidate.push(key);
                        }
                        candidate.push(hint);
                    }
                }

                if(hint){
                    if(index >= candidate.length) index = 0;
                    
                     // Use Command Selector instead of direct replacement.
                     cmdLine + candidate[index++] -> inputBoxValue;
                    
                     push(false); // eat up Tab Key.
                     
                } else {
                    
                      push(true); // Let others handle it.
                      
                 }
              }
           end-code last :: index=0 
     then 
   then ;

This modified "{Tab}" function checks whether both "Alt" and "Tab" keys are being held down using event.altKey and event.keyCode, respectively. If they are both pressed together, then it calls the "command-selector" function and returns false to prevent any further handling of this event by other handlers.

If only Tab key is pressed without holding Alt-key at same time or there's no hint available for auto-completion based on what has been typed so far in InputBox; it falls back to its original behavior (as described earlier), providing suggestions based on previously used commands.

I hope this helps!


jeforth 的 Alt-{up},{down} command history popup menu 也有這個小問題:萬一 user click 它處,使得 popup menu 失去 focus 此後就只剩 click 其內容可以用了,hotkey 都無效了。

In HTML js, how to always focus an element even when the user click the web page at other element?


To always focus an element even when the user clicks elsewhere on the web page, you can use JavaScript to add a "blur" event listener to the other elements and then immediately re-focus your target element.

Here's an example code snippet that demonstrates this:

<input id="myInput" type="text">

<script>
const myInput = document.getElementById("myInput");

// Add blur event listeners to all other elements
document.addEventListener("click", (event) => {
  if (event.target !== myInput) {
    myInput.focus();
  }
});

// Focus on our target element initially
myInput.focus();
</script>

In this code, we first retrieve a reference to our target input element using getElementById. Then we add a click event listener to the entire document using addEventListener.

Whenever a click occurs anywhere in the document, this listener checks whether it was triggered by any element other than our target input. If so, it calls focus method of our desired Input Element which brings back focus on it.

Finally, we call focus method once at beginning for initial focusing of InputBox.


In this snippet, the index of object tick('{Tab}') will not be assigned with new value, why?

: command-selector ( -- ) \ Create the selector menu and return with selected index by {Tab}.index 
    <o> <br><select style="width:90%;padding-left:2px;font-size:16px;"></select></o> ( select )
    <js>
        with (tick('{Tab}')){
            for(var key in candidate ) {
                var option = document.createElement("option");
                option.text = candidate[key];
                js: tos().add(option);
            }
            tos().size = Math.min(16,tos().length);
            tos().selectedIndex=tos().length-1;
            vm.scroll2inputbox();tos().focus();
            var select = tos().onclick = function(){
                index = tos().selectedIndex;
                execute("removeElement");
                vm.scroll2inputbox();
                return (false);
            }
        }
        // Handle keyboard events
        tos().onkeydown = function(e){
            e = (e) ? e : event; var keycode = (e.keyCode) ? e.keyCode : (e.which) ? e.which : false;
            switch(keycode) {
                case 27: /* Esc  */ execute("removeElement"); vm.scroll2inputbox(); break;
                case 38: /* Up   */ tos().selectedIndex = Math.max(0,tos().selectedIndex-1); break;
                case 40: /* Down */ tos().selectedIndex = Math.min(tos().length-1,tos().selectedIndex+1); break;
                case 13: /* Enter*/ setTimeout(select,1); break; // wait a bit before selecting so the dropdown menu disappears first
            }
            return (false); // prevent default behavior of arrow keys and Enter Key.
        }
    </js> ;

The reason why the index of object tick('{Tab}') will not be assigned with a new value in this snippet is that tick('{Tab}') returns the current value of {Tab} at runtime, which is an anonymous function defined elsewhere. The variable index inside that function has its own local scope and cannot be directly accessed or modified from within the "command-selector" function.

To update the value of index, you need to either pass it as a parameter to the "command-selector" function or use a closure to capture its value. Here's an example implementation using closures:

: command-selector ( -- ) \ Create the selector menu and return with selected index by {Tab}.index
    <o> <br><select style="width:90%;padding-left:2px;font-size:16px;"></select></o> ( select )
    <js>
        var tabIndex = tick('{Tab}').index;
        for(var key in candidate ) {
            var option = document.createElement("option");
            option.text = candidate[key];
            js: tos().add(option);
    ... snip .....

In this updated version of "command-selector," we define a local variable called tabIndex that captures the current value of

AI 答錯了,正確答案不太好懂。

正確的寫法如下:

if(hint){
    if (event.shiftKey) {
        execute("command-selector");
    } else {
        if(index >= candidate.length) index = 0;
        inputbox.value = cmdLine + candidate[index++];
    }
    push(false); // 吃掉這個 Tab key。
} else {
    push(true); // 不吃掉這個 Tab key,給別人處理。
}

錯誤的寫法如下:

if(hint){
    if (event.shiftKey) {
        execute("command-selector");
    }
    if(index >= candidate.length) index = 0;
    inputbox.value = cmdLine + candidate[index++];
    push(false); // 吃掉這個 Tab key。
} else {
    push(true); // 不吃掉這個 Tab key,給別人處理。
}

先前的想法是 command-selector 只傳回 user 選定的 tick('{Tab}').index 就好。但是執行到 execute("command-selector"); 不會停留!
它布置好 selector menu 之後馬上回來繼續執行,因此錯誤的寫法之下 index 永遠都是 0, user 的 selection 不會有效。

只能用 {Tab} 與 {Shift-Tab} 的原因

因為 {Alt-Tab} 是 Windows 的 "Task Switcher" 而 {Ctrl-Tab} 是一般 Web browser 的 "Switch To Next Tab"。


最後成功的結果:

    code {Tab}  ( -- ) \ Inputbox auto-complete
            with(this){
                if(index == 0){ // index 初值來自 document.onkeydown event, 這是剛按下 Tab 的線索。
                    var a=('h '+inputbox.value+' t').split(/\s+/);
                    a.pop(); a.shift();
                    this.hint = a.pop()||""; // the partial word to be autocompleted
                    this.cmdLine = inputbox.value.slice(0,inputbox.value.lastIndexOf(hint))||"";
                    this.candidate = [];
                    if(hint){
                        for(var key in wordhash) {
                            if(key.toLowerCase().indexOf(hint.toLowerCase())!=-1) candidate.push(key);
                        }
                        candidate.push(hint);
                    }
                }
                if(hint){
                    if (event.shiftKey) {
                        execute("command-selector");
                    } else {
                        if(index >= candidate.length) index = 0;
                        inputbox.value = cmdLine + candidate[index++];
                    }
                    push(false); // 吃掉這個 Tab key。
                } else {
                    push(true); // 不吃掉這個 Tab key,給別人處理。
                }
            }
            end-code
            last :: index=0

    : command-selector ( -- ) \ Work with {Tab} auto-complete. Create the selector menu and return the inputbox.value with selected command.
            <o> <br><select style="width:90%;padding-left:2px;font-size:16px;"></select></o> ( select )
            <js>
                with (tick('{Tab}')){
                    for(var key in candidate ) {
                        var option = document.createElement("option");
                        option.text = candidate[key];
                        js: tos().add(option);
                    }
                    tos().size = Math.min(16,tos().length);
                    tos().selectedIndex=tos().length-1;
                    vm.scroll2inputbox();tos().focus();
                    var select = tos().onclick = function(){
                        index = tos().selectedIndex;
                        inputbox.value = cmdLine + candidate[index];                
                        execute("removeElement");
                        vm.scroll2inputbox();
                        return (false);
                    }
                }
                // Handle keyboard events
                tos().onkeydown = function(e){
                    e = (e) ? e : event; var keycode = (e.keyCode) ? e.keyCode : (e.which) ? e.which : false;
                    switch(keycode) {
                        case 27: /* Esc  */ execute("removeElement"); vm.scroll2inputbox(); break;
                        case 38: /* Up   */ tos().selectedIndex = Math.max(0,tos().selectedIndex-1); break;
                        case 40: /* Down */ tos().selectedIndex = Math.min(tos().length-1,tos().selectedIndex+1); break;
                        case 13: /* Enter*/ setTimeout(select,1); break; // wait a bit before selecting so the dropdown menu disappears first
                    }
                    return (false); // prevent default behavior of arrow keys and Enter Key.
                }
            </js> ;

--- The End ---