'How to integrate backspace functionality in a program?

I have created a program that prints a character whenever that character is pressed but when i press "backspace" button to remove the last entered character then that character does not get removed

How to integrate backspace functionality in a program so while typing when i press backspace then the last character should get removed?

from dataclasses import field
from pynput import keyboard
import os
import sys
def on_press(key):
    try:
        #print('Alphanumeric key pressed: {0} '.format(
        #    key.char))
        file.write('{0}'.format(key.char))
    except AttributeError:
        #print('special key pressed: {0}'.format(
        #    key))
        #file.write('\nSpecial key pressed: {0}'.format(key))
        if(key == keyboard.Key.enter):
            file.write("\n")
        elif(key == keyboard.Key.space):
            file.write(" ")
        elif(key == keyboard.Key.esc):
            file.write("\nLogging Ended")
        elif(key == keyboard.Key.backspace):
            #Backspace code
            

def on_release(key):
    #print('Key released: {0}'.format(
    #    key))
    if key == keyboard.Key.esc:
        # Stop listener
        file.close()
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    save_path = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop')
    file_name = "A Text.txt"
    completeName = os.path.join(save_path, file_name)
    try:
        file = open(completeName,'x')
    except:
        file = open(completeName, "a")
        file.write("\n\n")
    listener.join()


Solution 1:[1]

What you want to do is possible, but kind of ugly, because arbitrary relative seeking within a text file is not allowed (since the encoding could easily be variable length, so a .seek(-1, io.SEEK_END), to jump back one character, would involve potentially jumping back 1-4 bytes, and Python won't try to do that).

If you want to make it work, you'll need to do two things:

  1. Don't use 'a' mode for your file (it's fine to have it function as opening for append, but you have to simulate it by opening for write and seeking to the end, so you're allowed to seek within the file)
  2. Before each write to the file, store a cookie for the current position in the file. When you hit backspace, use that cookie to seek to before the write in question and truncate.

This code does all those things and seems to work for me. Comments inline on changes (I deleted unused imports and your own commented out code to keep it succinct).

from pynput import keyboard
import os

from io import SEEK_END  # You could just pass 2 as whence, but name is self-documenting

cookies = []  # List of file offset cookies from .tell() calls

def on_press(key):
    cookies.append(file.tell())
    try:
        file.write(f'{key.char}')  # Use f-string for shorter/faster code
    except AttributeError:
        # Deleted unnecessary parens around conditions; Python doesn't need 'em, don't use 'em
        if key == keyboard.Key.enter:
            file.write("\n")
        elif key == keyboard.Key.space:
            file.write(" ")
        elif key == keyboard.Key.esc:
            file.write("\nLogging Ended")
        else:
            # Any character that doesn't lead to a write should delete the associated
            # cookie we just stored
            del cookies[-1]
            # backspace is a special case of non-writing key
            if key == keyboard.Key.backspace:
                try:
                    file.seek(cookies.pop())  # Prior cookie marks position before most
                                              # recent write; seek to before it
                    file.truncate()           # Remove data after that point
                except IndexError:
                    pass  # When nothing added yet, backspace does nothing, that's okay

def on_release(key):
    if key == keyboard.Key.esc:
        # Stop listener
        file.close()
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    save_path = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop')
    file_name = "A Text.txt"
    completeName = os.path.join(save_path, file_name)
    try:
        file = open(completeName, 'x')
    except FileExistsError:  # Never use bare except; handle only exceptions you expect
        file = open(completeName, 'r+')  # Can't use 'a' as it auto-seeks to end of file
                                        # causing problem for manual seeking; don't want 'w'
                                        # because it erases data
        file.seek(0, SEEK_END)          # 'w' + seeking to end manually fine for our purposes
        file.write("\n\n")
    listener.join()

For efficiency, it might make sense to only do cookies.append(file.tell()) when you know you're going to write, but frankly, if you're accepting input from a user typing, the I/O involved in potentially overdoing .tell() calls and repeatedly adding/removing an element from cookies is meaningless next to slow human typing.

Note that this design:

  1. Does not allow you to backspace into data from prior runs of the program (the first cookie would correspond to just after the two newlines you insert at the beginning). If you really needed to do so, you could build cookies from any existing file by replacing:

    file = open(completeName, 'r+')
    file.seek(0, SEEK_END)
    

    with:

    file = open(completeName, 'r+')
    cookies.append(0)
    while c := f.read(1):
        cookies.append(f.tell())
    file.seek(0, SEEK_END)  # Probably not strictly necessary, but nice to be clear all offsets should be at end now
    

    to prepopulate cookies for the existing data.

  2. Treats an output as an atomic unit; right now, the only output that isn't a single character is Esc (which ends the program, so backspaces won't typically follow it, though there is the possibility of holding down Esc then typing backspace I guess), so this won't come up, but if you write multiple characters per keypress, this will delete a whole keypress's worth of output, not just one character. That might be what you want, but if it's not, you'd need to get ugly and do a loop of writing and appending to cookies similar to the one for building cookies from an existing file above.

Solution 2:[2]

Your problem most likely lays in that you open your file in 'a' mode (append mode) and therefore you will not be able to change the contents before the previous ending of the file.

If you open the file, read the contents and then again open the file with the 'w' operator (write) you are able to change the contents in which every way you prefer.

# This now stores the full content in a string which can be manipulated.
file_contents = = open(completeName, 'r').read() # must first read content before opening with w to ensure no loss of content

file = open(completeName, 'w')

Then the backspace implementation becomes very easy:

def on_press(key):
    try:
        #print('Alphanumeric key pressed: {0} '.format(
        #    key.char))
        file_content += f"{key.char}"
    except AttributeError:
        #print('special key pressed: {0}'.format(
        #    key))
        #file.write('\nSpecial key pressed: {0}'.format(key))
        if(key == keyboard.Key.enter):
            file_content += "\n"
        elif(key == keyboard.Key.space):
            file_content += " "
        elif(key == keyboard.Key.esc):
            file_content += "\nLogging Ended"
        elif(key == keyboard.Key.backspace):
            file_content = file_content[:-1]

And in the closing argument you instead write the contents to the file and then close it.

def on_release(key):
    #print('Key released: {0}'.format(
    #    key))
    if key == keyboard.Key.esc:
        # Stop listener
        file.write(file_content)
        return False
 

Solution 3:[3]

Working Method:

from dataclasses import field
from pynput import keyboard
import os
import sys

output = []

def on_press(key):
    try:
        output.append(key.char)
    except AttributeError:
        if(key == keyboard.Key.enter):
            output.append('\n')
        elif(key == keyboard.Key.space):
            output.append(' ')       
        elif(key == keyboard.Key.backspace):
            output.pop()
            

def on_release(key):
    if key == keyboard.Key.esc:
        output.append("\nLogging Ended")
        return False

# Collect events until released
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    # save_path = os.path.join(os.path.join('~/'), 'Desktop')
    file_name = "A Text.txt"
    completeName = os.path.join(file_name)
    with open(completeName, 'w') as file:
        file.write("\n\n")
        listener.join()
        file.writelines(output)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 marc_s
Solution 3 BeRT2me