Pokitext: text editor on pokitto

No. Go ahead!

1 Like

I would but I left GitHub, I can only provide a patch. Sorry for the inconvenience.

patch
diff --git a/Pokitto/POKITTO_CORE/PokittoCore.cpp b/Pokitto/POKITTO_CORE/PokittoCore.cpp
index 3af4bb9..3f7e197 100644
--- a/Pokitto/POKITTO_CORE/PokittoCore.cpp
+++ b/Pokitto/POKITTO_CORE/PokittoCore.cpp
@@ -1343,6 +1343,10 @@ void Core::setFrameRate(uint8_t fps) {
 	sound.prescaler = __avrmax(1, sound.prescaler);
 }
 
+uint8_t Core::getFrameRate() {
+	return 1000 / timePerFrame;
+}
+
 void Core::pickRandomSeed(){
         initRandom();
 }
diff --git a/Pokitto/POKITTO_CORE/PokittoCore.h b/Pokitto/POKITTO_CORE/PokittoCore.h
index 9e3ab1e..8e7a711 100644
--- a/Pokitto/POKITTO_CORE/PokittoCore.h
+++ b/Pokitto/POKITTO_CORE/PokittoCore.h
@@ -225,6 +225,8 @@ public:
     static void keyboard(char* text, uint8_t length);
     static void popup(const char* text, uint8_t duration);
     static void setFrameRate(uint8_t fps);
+    /** Get current framerate (which may slightly differ from the value set with setFrameRate because of rounding). */
+    static uint8_t getFrameRate();
 	static void pickRandomSeed();
 
 	static uint8_t getCpuLoad();

I’ve got multiline text editing somewhat working (also QWERTY/Z layouts):

out

firmware.bin (55.4 KB)

We can now start experimenting with the hotkeys.

EDIT:

And a question: what will undo look like? There is the command pattern, but that’s not what I mean now. Imagine a situation when user selects a large portion of text in the middle of the document, then deletes it and then wants to undo this. The command would require quite large amount of storage to keep the deleted text in order to undo the operation. So what do we do? Still do this and hope we have enough memory? Allow only small edits to be undone? Any ideas?

EDIT:

Actually I have an idea for the big delete issue: the deleted text could be taken out of the middle of the document, the rest of the document would be shifted to its place, and the deleted text would be copied after the 0 terminating char of the document. Like this (text buffer of length 10, 0 terminates the text):

ABCDEFG0..   <-- delete B to E
AFG0BCDE..   <-- undo
ABCDEFG0..

Without further complicating this we could only undo one “big” operation, but that’s something we can work with I guess.

EDIT:

However the text after the terminating 0 is not guaranteed to last if new characters are typed… so… we should probably just allow only one step undo.

4 Likes

Seems to be quite easy to use already! Good job!

2 Likes

The Pokitto has 36864 RAM total, of which roughly 32% is used by the Pokitto library.
That leaves approximately 25067 bytes (~24KB) for the program to use.

I’d hope people wouldn’t be trying to edit files over ~8-12KB.

You could always write the undo data to the SD card.
Otherwise you could prompt the user to warn them that they can’t undo the operation.

One problem with that is you then have to track the size of that text.

This is true, but it’s an option at least.
There are plenty of editors that have a limited number of undo operations.
The ones with unlimited operations usually end up writing to disk.


If you maintained a variable that tracked the size of the text instead of using a null terminator then you could trim the end of the text by subtracting from the length.
That might be a useful optimisation.


Have you ever heard of (a) rope? (No, not the Hitchcock film.)
Might be worth considering. I’ve never used one myself, but I’m aware of them.

There’s also gap buffers, which is what Emacs uses.

1 Like

I’ll simply terminate it with another 0 (or the end of the buffer, the size is known). I’ll probably make it so that this operation can only be undone immediately so that it doesn’t move around and stuff. That has to be enough for such a small editor.

I wouldn’t really want to write to SD card, it may not be there (alternative text storage can be EEPROM), or it may be full, and this feature would probably be meaningfully used only a handful of times in the history of the universe.

Didn’t know about ropes, thanks for reading material :smile:

1 Like

I made a PR.

3 Likes

Thanks Hanski, I will merge it

Edit: and @Pharap, that rope thing was very interesting indeed

2 Likes

I think being full is more likely than not being there.
I haven’t seen anyone with a Pokitto who doesn’t also have a micro SD with it.

(And of course, the EEPROM could be full too.)

Besides which, if there’s no SD card, there’s no file to save/load.
You can save/load a text file to/from EEPROM, but you might be stepping over someone else’s game save and you only have about 3900 bytes to work with (a certain number are reserved for certain device settings).

They’re a surprisingly uncommon data structure.
As far as I’m aware, text editors are the only things that use them.
Perhaps because trees are less common outside of functional languages?

The articles here and here seem to be easier to read than the Wikipedia article in case anyone’s interested.

1 Like

Yeah, I’m surprised I haven’t heard of them, they seem useful. Reminds me of things like B+ trees.

Sure, I don’t wanna use it for the undo either, just for optional very short documents, or rather notes, that could perhaps be stored in Pokitto Cookies in order not to rewrite other data as you say. Or you might actually want to edit and rewrite EEPROM text data of other programs on purpose. But this would be a low priority feature, it was just a quick idea.

The main thing to me is that the program shouldn’t require any storage to just run. A user might want to run the program without needing to save/load documents, for example as a pen and paper alternative while solving some math problem, or just having fun making ASCII art doodles, you never know.

i see the grid keybourd is popular, though im still thinking of the mulipress system, also i feel like “vim” like comands might be more convenient like inted of curoring around char by char can go word by word (idk if im making it complicated to soon)

3 Likes

Never fear complexity. (Even though vim is terrifying.)

Though whether such a design would be useful in practice isn’t something we’d be able to figure out without making a prototype.

Me too, ever since @carbonacat mentioned Typing of the Dead.
I’ll see if I can make a quick/rough prototype tomorrow.

3 Likes

It’s official, I’ve used templates, and I set up the B hotkey like this for now:

B+arrows: move cursor
B+A: backspace

You can try how it feels:

firmware.bin (55.7 KB)

Anyway, when we have a long text with a scrolling window, we may find ourselves in a need of more hotkeys like page up/down (like @adekto says, we don’t want to scroll through a long document by chars :confused:). So maybe I should first implement more of the editor functionality.

EDIT:

new version:

  • Buttons now behave like keyboard – they have a longer initial delay (6 frames) before starting a fast repeat (2 frames). This is so much closer to what we’re used to, try it out with B + arrows.
  • Since the movement is quite fast, I disabled wrapping the selection around vertically (from keyboard top to bottom and vice versa).
  • I started programming a TextView widget that will be able to scroll etc.
  • Fixed some bugs.
  • When moving the text cursor up/down, it remembers the “desired column” it wants to stay in – again, this is a behavior we’re intuitively used to from text editors without mostly consciously knowing about it. By adding it it now feels so much more natural.

firmware.bin (58.1 KB)

3 Likes

I am seeing some flickering in certain situations. Does Pokitto do some kind of double buffering for the screen? Or is everything drawn immediately?

Also here is what we have at the moment:

out

2 Likes

So a major concept in vim and I believe emacs to is ech word you can navigate to
It it’s simplest esance it be a map/array of strings
If we store that in memory we might kill a few birds with one stone like find and replace as well as recommending a a word you already wrote

I think string already can breack up a string into it’s components but that’s retroactively and might be memory hungry having duplicate data

Also forgot that syntax highlights be easier to do as well
Also forgot that you can turn language keywords into just IDs Wich could save some memory

1 Like

I’ve just now been in a situation where I’d really appreciate some smarter structure than a C string. The situation is this:

You have a looooong string, which is the document, and now you have to draw what’s seen in a rectangular window that has a [column;row] coordinates and a width/height in characters.

How do you do this quickly (every frame)? Turns out you pretty much can’t, without some additional info, that the C string lacks. Because what’s seen in the view is determined by newline characters randomly scattered around in the string. So you’d have to iterate over the string from the beginning and find all the newlines.

So I was thinking – should we store the document as an array of lines, rather than one long string containing newline chars? But that would involve dynamic allocation and stuff, and the keyboard handler I have is made for C strings in order to be generally usable by common people with their simple everyday strings. I could write an extra keyboard handler or something, but that would be one more thing to debug and maintain.

So I rather managed to solve this and still keep the whole text in a single string. I use some information that the keyboard handler keeps stored – the cursor column and row – and drawing relatively to the cursor position. These two pieces of information allow to reconstruct the text structure around the cursor in an acceptable time (provided the cursor is within or at least near the view window, which we can easily make it to be). It can also be slower if there are extremely long lines, but I am quite happy about the solution.

So I wouldn’t like to change the format of the document stored in the memory. I could accept some additional structures maybe – something like and index – but only if really necessary, and it would be hard to maintain such structure because the string can change quite unpredictably under your hands.

I started my prototype today and am side-stepping this problem by breaking up my keyboard class into 3:

  • KBInput takes care of handling input and calls a pure virtual method whenever a key is confirmed. This way it is not bound to a specific string representation. I did this because Typing of the Dead wouldn’t need to modify a string as you typed.
  • KBUI handles the visual representation of the keyboard, drawing directly to the LCD so that it would look the same no matter which screen mode is being used. Useful for a terminal emulator, which I’m planning on including with my custom loader. It would be possible to replace it
    with one that works specifically with a certain screen mode (useful for typing games).
  • Keyboard takes a KBInput and a KBUI, puts them together and makes it all work.
2 Likes

I may steal the idea of drawing directly to the screen. Now I use some default color indices that can optionally be changed.

My keyboard is independent of any string as well. It doesn’t even suppose it controls a text input, it’s simply a bunch of buttons. I have these components:

  • keyboard: Draws keyboard on screen and handles the Pokitto buttons to make user press keyboard buttons. It’s not dependent on anything else. It outputs a stream of KeyboardActions (characters + special actions like backspace, escape etc.).
  • keyboard handler: Something that you connect a keyboard to and it does something based on what actions come out of the keyboard.
    • multiline text keyboard handler: Subclass of above, takes care of cursor editing a string of some data types (mostly chars, but it’s templated), which can contain newlines. <— This is what supposes the C string (or similar, e.g. some kind of UTF).
  • text view: Widget that draws a window with text in it, with scrolling and stuff.
2 Likes

Wow, my compiler is pretty smart, it warns me about misleding indentation :slight_smile:

150     }                                                                           
151     else                                                                        
152       pokitto.display.bgcolor = 6;                                              
153                                                                                 
154       pokitto.display.print(menuItems[i].text);

../main.cpp:154:7: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'else'
       pokitto.display.print(menuItems[i].text);
       ^~~~~~~

EDIT:

basic menu and info bar:

out

You can test this and suggest GUI improvements.

firmware.bin (60.6 KB)