Author Topic: qXed: linked list text editor  (Read 3281 times)

0 Members and 1 Guest are viewing this topic.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1062
  • TOXIC
qXed: linked list text editor
« on: September 17, 2018, 12:44:06 AM »
OBSOLETE POST


Hi folks,

In the interest of keeping the top most most relevant, I bumped the original text downward to make space for today's update. This code has crystallized enough so I can post somewhat of a "clean" milestone. (Still deciding on some internal and external behaviors - what you see might not be final.)

What makes this update different however, is I've decided to a explain a little on how to use it.

Startup and File IO
The code you see below can run as stand-alone. No external files needed for this showing. The auxiliary text files attached are samples that you can drag+drop onto the compiled *.exe file once you throw it through your QB64 compiler. That is, unlike earlier prototypes, it now loads blank like a real editor, and the psychedelic rant has to be loaded separately. To save a file along the way, press F6 and a snapshot will be dumped into the directory alongside the exe.

Cursors
So there are seemingly three cursors on screen. You'll notice the grey single-character highlight just follows that mouse - it's a completely benign pointer (not really a cursor). There are two relevant cursors though. Cursor 1 (the main one) is blue, and causes the character under it to blink. Cursor 2 is a brown, and only has a function/appearance when it is downstream of cursor 1. That is, cursor 2 is always to the right, else it disappears. The one exception is when the cursors overlap, in which the color blends to purple. The ESC key sill send cursor 2 to either (i) the end of the text file, or (ii) onto cursor 1, depending on initial position.

Typing text
In "normal" mode, i.e., with cursor 1 appearing as blue, any typed or inserted characters appear *after* the cursor position. However, when cursors are aligned and appear as purple, typed or inserted characters appear *before* the cursor.

Mouse Buttons
Mouse button 1 moves the position of cursor 1. Observe it can't be moved anywhere - only within the topology of the existing text file. Mouse button 2 moves the position of cursor 2, and is rendered provided it occurs downstream of cursor 1. *Any* qualified movement of cursor 2 invokes a clipboard copy of all characters between cursors. Mouse button 3 pastes the contents of the clipboard at the position of cursor 1.

Navigation and Scrolling
The normal ways of getting around a text file work here: arrow keys, home/end, pgup/pgdn, auto-scrolling when your text insertions reach the bottom... most of the expected behaviors are there. The scrollbar on the right indicates not what page or line we're on, but the position of cursor 1 with respect to the whole file. That is, you can "scroll" with the mouse by clicking the right edge through a file of any length, even two characters. Of course, clicking on the right edge is the only fast way to get to the bottom of a big file. There are so far no dialog windows, jump-to-line options, or search. Mouse scrollwheel does what you think.

Wrapping Modes
That "gotcha" part of text editors... There are three wrapping modes in this editor. By default it loads "fluid" mode, which makes all efforts to never chop words in half. It breaks on spaces and hyphens. Next there is "square" mode, which makes zero effort to never chop words in half. The text appears as a big rectangle. Finally, there is the "none" case, in where lines never break, and horizontal scrolling must be used to see the end (like the IDE). In the special cases where a single word is too long for any of the first two modes, it spills onto the next line without incident. Press F12 to cycle through these modes.

Formatting Option
Because I am in the cult of Doug Crockford and agree with most of what he thinks, I hate using TABS for spacing. What a horrible throwback to the typewriter that is... So we use spaces for spacing in qXed. Break returns are all converted to CHR$(13). To see the hidden characters that slice up the text file, press F11. This solves the problem (for me) of opening text files and being thrown off by white space that renders as invisible. This option was inspired by the VI editor.

...

Okay, all that said, the code is below. Compile to an exe, and then drag+drop reasonably-size text files onto there. The downloads include some fun reading and demonstrations, so please do have a look.

Code: QB64: [Select]
  1. _TITLE "qXed"
  2. _DELAY .15
  3. SCREEN _NEWIMAGE(90, 30, 0)
  4.  
  5. ' Define fundamental structures.
  6. TYPE Vector
  7.     X AS INTEGER
  8.     Y AS INTEGER
  9.  
  10. TYPE Cell
  11.     Identity AS LONG
  12.     Pointer AS LONG
  13.     Lagger AS LONG
  14.     Content AS STRING * 1
  15.  
  16. DIM SHARED ChainLimit AS LONG
  17. DIM SHARED BOC AS LONG ' Beginning of chain.
  18. DIM SHARED EOC AS LONG ' End of chain.
  19. ChainLimit = 128000
  20. BOC = -1
  21. EOC = ChainLimit
  22.  
  23. ' Define text window properties.
  24. DIM SHARED VisibleLines
  25. DIM SHARED TopIndent
  26. DIM SHARED LeftIndent
  27. DIM SHARED TextHeight
  28. DIM SHARED TextWidth
  29. DIM SHARED HScroll
  30. DIM SHARED TextWrapping
  31. DIM SHARED TextFormatting
  32. DIM SHARED InsertKey
  33. TopIndent = 1
  34. LeftIndent = 1
  35. TextHeight = _HEIGHT - 2 * TopIndent + 1
  36. TextWidth = _WIDTH - 2 * LeftIndent
  37. HScroll = 1
  38. TextWrapping = 1
  39. TextFormatting = -1
  40. InsertKey = -1
  41.  
  42. ' Initiate text inside window.
  43. DIM SHARED StartIndex ' First visible character address.
  44. DIM SHARED LineAsMapped(TextHeight) AS STRING
  45. DIM SHARED Cursor1 AS Vector
  46. DIM SHARED Cursor2 AS Vector
  47.  
  48. DIM SHARED AuxGrid(TextWidth, TextHeight, 2) AS STRING
  49.  
  50. ' Load text file into memory if applicable, use example string if not.
  51. DIM SHARED FileName$
  52. IF (c$ <> "") THEN
  53.     q$ = ""
  54.     OPEN c$ FOR INPUT AS #1
  55.     DO WHILE NOT EOF(1)
  56.         LINE INPUT #1, r$
  57.         q$ = q$ + r$ + CHR$(13)
  58.     LOOP
  59.     CLOSE #1
  60.     i = INSTR(c$, ".")
  61.     IF (i <> 0) THEN j = i - 1 ELSE j = LEN(c$)
  62.     FileName$ = LEFT$(c$, j) + "-" + LTRIM$(RTRIM$(STR$(INT(TIMER)))) + ".txt"
  63.     FileName$ = "Newfile" + "-" + DATE$ + "-" + LTRIM$(RTRIM$(STR$(INT(TIMER)))) + ".txt"
  64.     'q$ = "I sank to the floor. I [experienced] this hallucination of tumbling forward into these fractal geometric spaces made of light and then I found myself in the equivalent of the Pope's private chapel and there were insect elf machines proffering strange little tablets with strange writing on them, and I was aghast, completely appalled, because [in] a matter of seconds . . . my entire expectation of the nature of the world was just being shredded in front of me. I've never actually gotten over it. These self-transforming machine elf creatures were speaking in a colored language which condensed into rotating machines that were like Faberge eggs but crafted out of luminescent superconducting ceramics and liquid crystal gels. All this stuff was just so weird and so alien and so un-English-able that it was a complete shock - I mean, the literal turning inside out of [my] intellectual universe!" + CHR$(13) + CHR$(13) + "This went on for two or three minutes, this situation of [discontinuous] orthogonal dimensions to reality just engulfing me. As I came out of it and the room reassembled itself, I said, " + CHR$(34) + "I can't believe it, it's impossible." + CHR$(34) + " To call that a drug is ridiculous; that just means that you just don't have a word for it and so you putter around and you come upon this sloppy concept [that] something goes into your body and there's a change. It's not like that; it's like being struck by noetic lightning. [Note: " + CHR$(34) + "Noetic" + CHR$(34) + " derives from the theologian Pierre Teilhard de Chardin's " + CHR$(34) + "noosphere" + CHR$(34) + " - the collective consciousness of humankind conceived of as a sort of philosophical virtuality.]" + CHR$(13) + CHR$(13) + "[What] astonished me was [that] . . . in the carpets of Central Asia, in the myths of the Maya, in the visions of an Arcimboldi or a Fra Angelico or a Bosch, there is not a hint, not a clue, not an atom of the presence of this thing, This was more [multiplex] than the universe that we share with each other. It was the victory of Neo-Platonic metaphysics; everything [was] made out of a fourth-dimensional mosaic of energy. I was knocked off my feet, and set myself the goal of understanding this. There was really no choice, you see."
  65.     'q$ = "                                          " + CHR$(13) + "                                          " + CHR$(13) + "                                          " + CHR$(13) + "                                          " + CHR$(13) + "                         **         **    " + CHR$(13) + "                        * *         **    " + CHR$(13) + "  **       **           **                " + CHR$(13) + "  **      * *                             " + CHR$(13) + "          **      **                      " + CHR$(13) + "                  * *                     " + CHR$(13) + "                  *                       " + CHR$(13) + "                                     **   " + CHR$(13) + "                                     * *  " + CHR$(13) + "                                     *    " + CHR$(13) + "                                          " + CHR$(13) + "                                          " + CHR$(13) + "                          ***             " + CHR$(13) + "                          *               " + CHR$(13) + "                           *              " + CHR$(13) + "                                          " + CHR$(13) + "                                          " + CHR$(13) + "                                          " + CHR$(13)
  66.     q$ = " "
  67.  
  68. ' Create memory space for string.
  69. DIM SHARED TheChain(ChainLimit) AS Cell
  70.  
  71. ' Create character list.
  72. CALL Assimilate(q$)
  73.  
  74. ' Prime main loop.
  75. CALL MapText
  76. CALL CalibrateCursor(ID1)
  77. CALL CalibrateCursor(ID2)
  78. CALL PrintEverything
  79.  
  80. DIM SHARED DEBUG$
  81.  
  82. ' Main loop.
  83.     CALL StateChange
  84.     CALL PrintEverything
  85.  
  86.     _DISPLAY
  87.     _LIMIT 120
  88.  
  89. SUB PrintEverything
  90.     CLS
  91.  
  92.     COLOR 11, 0
  93.     c$ = "qXed" + DEBUG$
  94.     _PRINTSTRING (1, 1), c$
  95.  
  96.     COLOR 7, 1
  97.     FOR i = 1 TO VisibleLines
  98.         c$ = LineAsMapped(i)
  99.         IF ((TextFormatting = 1) AND (TextWrapping <> 2)) THEN
  100.             FOR j = 1 TO TextWidth - LEN(c$)
  101.                 c$ = c$ + "_"
  102.             NEXT
  103.         END IF
  104.         _PRINTSTRING (LeftIndent + 1, TopIndent + i), MID$(c$, HScroll, TextWidth)
  105.     NEXT
  106.  
  107.     IF ((Cursor2.X > 0 AND Cursor2.X < _WIDTH) AND ((Cursor2.Y > 0) AND (Cursor2.Y < _HEIGHT))) THEN
  108.         p1 = LinearCount(StartIndex, ID1)
  109.         p2 = LinearCount(StartIndex, ID2)
  110.         pe = LinearCount(StartIndex, EOC)
  111.         IF ((p2 > p1) AND (p2 < pe)) THEN
  112.             c$ = TheChain(ID2).Content
  113.             IF (c$ = " ") THEN c$ = "_"
  114.             IF (c$ = CHR$(13)) THEN c$ = "~"
  115.             COLOR 0, 6
  116.             _PRINTSTRING (Cursor2.X, Cursor2.Y), c$
  117.         END IF
  118.     END IF
  119.  
  120.     IF ((Cursor1.X > 0 AND Cursor1.X < _WIDTH) AND ((Cursor1.Y > 0) AND (Cursor1.Y < _HEIGHT))) THEN
  121.         IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN COLOR 16, 5 ELSE COLOR 16, 3
  122.         c$ = TheChain(ID1).Content
  123.         IF (c$ = " ") THEN c$ = "_"
  124.         IF (c$ = CHR$(13)) THEN c$ = "~"
  125.         _PRINTSTRING (Cursor1.X, Cursor1.Y), c$
  126.     END IF
  127.  
  128.     d$ = TheChain(ID1).Content
  129.     e$ = TheChain(ID2).Content
  130.     IF ((ASC(d$) = 10) OR (ASC(d$) = 13)) THEN d$ = "~"
  131.     IF ((ASC(e$) = 10) OR (ASC(e$) = 13)) THEN e$ = "~"
  132.     IF (ASC(d$) = 32) THEN d$ = "_"
  133.     IF (ASC(e$) = 32) THEN e$ = "_"
  134.  
  135.     IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN COLOR 0, 5 ELSE COLOR 0, 3
  136.     c$ = "(" + LTRIM$(RTRIM$(STR$(Cursor1.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor1.Y - TopIndent))) + ":" + " " + LTRIM$(RTRIM$(d$)) + " " + LTRIM$(RTRIM$(STR$(ID1))) + ")"
  137.     _PRINTSTRING (2, _HEIGHT), c$
  138.     IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  139.         COLOR 0, 6
  140.         _PRINTSTRING (3 + LEN(c$), _HEIGHT), "(" + LTRIM$(RTRIM$(STR$(Cursor2.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor2.Y - TopIndent))) + ":" + " " + LTRIM$(RTRIM$(e$)) + " " + LTRIM$(RTRIM$(STR$(ID2))) + ")"
  141.     END IF
  142.  
  143.     p = LinearCount(ID1, NthP(ID1, ChainLimit + 1))
  144.     q = LinearCount(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  145.     IF (q = 0) THEN r = 1 ELSE r = 1 - p / q
  146.     COLOR 8, 7
  147.     _PRINTSTRING (_WIDTH, 1 + INT(r * (_HEIGHT - 1))), "*"
  148.  
  149.     COLOR 15, 0
  150.     SELECT CASE TextWrapping
  151.         CASE 0: d$ = "Square"
  152.         CASE 1: d$ = "Fluid"
  153.         CASE 2: d$ = "None"
  154.     END SELECT
  155.     c$ = "[F6=Save] [F11=Formatting] [F12=Wrapping: " + d$ + "]"
  156.     IF (TextWrapping = 2) THEN c$ = "[F1/2=HScroll] " + c$
  157.     c$ = c$ + STR$(INT(100 * r)) + "%"
  158.     _PRINTSTRING (_WIDTH - LEN(c$), 1), c$
  159.  
  160.     c$ = "[Esc=Sync] [Mouse2=Copy] [Mouse3=Paste]"
  161.     IF (InsertKey = 1) THEN c$ = "[Ins] " + c$
  162.     _PRINTSTRING (_WIDTH - LEN(c$), _HEIGHT), c$
  163.  
  164.     IF ((_MOUSEX >= 1) AND (_MOUSEX <= _WIDTH) AND (_MOUSEY >= 1) AND (_MOUSEY <= _HEIGHT)) THEN
  165.         a$ = CHR$(SCREEN(_MOUSEY, _MOUSEX))
  166.         COLOR 0, 15
  167.         _PRINTSTRING (_MOUSEX, _MOUSEY), a$
  168.     END IF
  169.  
  170.     COLOR 15, 0
  171.  
  172. SUB Assimilate (a AS STRING)
  173.     ' Load a string to initialize chain.
  174.     FOR k = 1 TO ChainLimit
  175.         TheChain(k).Identity = 0
  176.     NEXT
  177.     StartIndex = 1
  178.     PreviousIdentity = BOC
  179.     NextIdentity = NextOpenIdentity(StartIndex)
  180.     FOR k = 1 TO LEN(a)
  181.         j = NextIdentity
  182.         TheChain(j).Identity = j
  183.         TheChain(j).Content = ReFormat$(MID$(a, k, 1))
  184.         TheChain(j).Lagger = PreviousIdentity
  185.         PreviousIdentity = j
  186.         IF (k < LEN(a)) THEN
  187.             NextIdentity = NextOpenIdentity(j)
  188.             TheChain(j).Pointer = NextIdentity
  189.         ELSE
  190.             TheChain(j).Pointer = EOC
  191.         END IF
  192.         PRINT TheChain(j).Content
  193.     NEXT
  194.     ID1 = StartIndex
  195.     ID2 = ID1
  196.  
  197. FUNCTION ReFormat$ (a AS STRING)
  198.     c$ = a
  199.     IF c$ = CHR$(10) THEN c$ = CHR$(13)
  200.     IF c$ = CHR$(9) THEN c$ = "    "
  201.     ReFormat = c$
  202.  
  203. FUNCTION NthP (a AS LONG, b AS LONG)
  204.     ' Returns the address that is b jumps ahead of address a.
  205.     i = a
  206.     IF (i <> EOC) THEN
  207.         k = 0
  208.         j = 0
  209.         DO WHILE (k < b)
  210.             k = k + 1
  211.             j = TheChain(i).Identity
  212.             i = TheChain(j).Pointer
  213.             IF (i = EOC) THEN EXIT DO
  214.         LOOP
  215.     END IF
  216.     NthP = j
  217.  
  218. FUNCTION NthPC (a AS LONG, b AS STRING)
  219.     ' Returns the address holding b first enLinearCountered from a.
  220.     i = a
  221.     DO
  222.         j = TheChain(i).Identity
  223.         i = TheChain(j).Pointer
  224.         IF (TheChain(j).Content = b) THEN EXIT DO
  225.         IF (i = EOC) THEN
  226.             j = BOC
  227.             EXIT DO
  228.         END IF
  229.     LOOP
  230.     NthPC = j
  231.  
  232. FUNCTION NthL (a AS LONG, b AS LONG)
  233.     ' Returns the address that is b jumps behind address a.
  234.     i = a
  235.     k = 0
  236.     DO WHILE k < b
  237.         k = k + 1
  238.         j = TheChain(i).Identity
  239.         i = TheChain(j).Lagger
  240.         IF (i = BOC) THEN EXIT DO
  241.     LOOP
  242.     NthL = j
  243.  
  244. FUNCTION NextOpenIdentity (a AS LONG)
  245.     ' Returns first nonzero identity.
  246.     FOR j = a TO ChainLimit
  247.         IF (TheChain(j).Identity = 0) THEN EXIT FOR
  248.     NEXT
  249.     IF (j > ChainLimit) THEN
  250.         PRINT "Out of memory: "; ChainLimit
  251.         SLEEP
  252.         SYSTEM
  253.     END IF
  254.     NextOpenIdentity = j
  255.  
  256. FUNCTION BackBreak (a AS LONG)
  257.     ' Function for scrolling up.
  258.     j = a
  259.     lastbreak = 0
  260.     c$ = ""
  261.     DO
  262.         IF (j = BOC) THEN EXIT DO
  263.         k = TheChain(j).Lagger
  264.         IF (k = BOC) THEN
  265.             lastbreak = j
  266.             EXIT DO
  267.         END IF
  268.         j = k
  269.         d$ = TheChain(j).Content
  270.         IF ((TextWrapping = 1) AND (d$ = " ")) THEN lastbreak = j
  271.         c$ = d$ + c$
  272.         IF (TextWrapping <> 2) AND (LEN(c$) = TextWidth) THEN EXIT DO
  273.         IF (d$ = CHR$(13)) THEN EXIT DO
  274.     LOOP
  275.     IF (lastbreak <> 0) THEN j = TheChain(lastbreak).Identity
  276.     BackBreak = j
  277.  
  278. SUB InsertBefore (a AS LONG, b AS STRING)
  279.     ' Inserts a single cell before address a in the chain.
  280.     j = NextOpenIdentity(a)
  281.     al = TheChain(a).Lagger
  282.     TheChain(j).Identity = j
  283.     TheChain(j).Pointer = a
  284.     TheChain(j).Lagger = al
  285.     TheChain(j).Content = ReFormat$(b)
  286.     TheChain(a).Lagger = j
  287.     IF (al = BOC) THEN StartIndex = j ELSE TheChain(al).Pointer = j
  288.  
  289. SUB InsertAfter (a AS LONG, b AS STRING)
  290.     ' Inserts a single cell after address a in the chain.
  291.     j = NextOpenIdentity(a)
  292.     ap = TheChain(a).Pointer
  293.     TheChain(j).Identity = j
  294.     TheChain(j).Pointer = ap
  295.     TheChain(j).Lagger = a
  296.     TheChain(j).Content = ReFormat$(b)
  297.     TheChain(a).Pointer = j
  298.     IF (ap <> EOC) THEN TheChain(ap).Lagger = j
  299.  
  300. SUB InsertRange (a AS LONG, b AS STRING)
  301.     ' Inserts a sub-chain anywhere.
  302.     FOR k = 1 TO LEN(b)
  303.         c$ = MID$(b, k, 1)
  304.         CALL InsertBefore(a, c$)
  305.     NEXT
  306.  
  307. SUB UnlinkCell (a AS LONG)
  308.     ' Remove single cell from chain and clear identity.
  309.     ap = TheChain(a).Pointer
  310.     al = TheChain(a).Lagger
  311.     IF ((ap = EOC) AND (al = BOC)) THEN
  312.         TheChain(a).Content = " "
  313.         'ID1 = a
  314.         'ID2 = ID1
  315.     ELSE
  316.         TheChain(a).Identity = 0
  317.         IF ((ap <> EOC) AND (al <> BOC)) THEN
  318.             TheChain(al).Pointer = ap
  319.             TheChain(ap).Lagger = al
  320.         END IF
  321.         IF (ap = EOC) THEN TheChain(al).Pointer = EOC
  322.         IF (al = BOC) THEN
  323.             StartIndex = ap
  324.             TheChain(ap).Lagger = BOC
  325.         END IF
  326.     END IF
  327.  
  328. SUB UnlinkRange (a AS LONG, b AS LONG)
  329.     ' Remove sub-chain and clear identity of each cell.
  330.     bp = TheChain(b).Pointer
  331.     al = TheChain(a).Lagger
  332.     IF ((al = BOC) AND (bp = EOC)) THEN
  333.         CALL UnlinkRange(NthP(a, 2), b)
  334.         TheChain(a).Content = " "
  335.         TheChain(a).Pointer = bp
  336.     ELSE
  337.         k = a
  338.         DO WHILE ((k <> b) AND (k <> EOC))
  339.             TheChain(k).Identity = 0
  340.             k = TheChain(k).Pointer
  341.         LOOP
  342.         TheChain(b).Identity = 0
  343.         TheChain(bp).Lagger = al
  344.         IF (al = BOC) THEN StartIndex = bp ELSE TheChain(al).Pointer = bp
  345.     END IF
  346.  
  347. FUNCTION LinearCount (a AS LONG, b AS LONG)
  348.     ' Returns number of links between two addresses.
  349.     i = a
  350.     k = 0
  351.     DO WHILE (i <> b)
  352.         k = k + 1
  353.         j = TheChain(i).Identity
  354.         i = TheChain(j).Pointer
  355.         IF (i = EOC) THEN EXIT DO
  356.     LOOP
  357.     LinearCount = k
  358.  
  359. FUNCTION LinearCount2 (a AS LONG, b AS LONG, c AS LONG)
  360.     ' Returns number of links between two addresses.
  361.     i = a
  362.     k = 0
  363.     DO WHILE (i <> b)
  364.         k = k + 1
  365.         j = TheChain(i).Identity
  366.         i = TheChain(j).Pointer
  367.         IF (i = EOC) THEN EXIT DO
  368.         IF (k = c) THEN EXIT DO
  369.     LOOP
  370.     LinearCount2 = k
  371.  
  372. FUNCTION Projection$ (a AS LONG, b AS LONG)
  373.     ' Returns the linear content for all address between a and b, inclusive.
  374.     DIM TheReturn AS STRING
  375.     TheReturn = ""
  376.     IF (a = b) THEN
  377.         TheReturn = TheChain(a).Content
  378.     ELSE
  379.         j = a
  380.         DO
  381.             c$ = TheChain(j).Content
  382.             TheReturn = TheReturn + c$
  383.             k = TheChain(j).Pointer
  384.             IF (j = b) THEN EXIT DO
  385.             IF (k = EOC) THEN EXIT DO
  386.             j = k
  387.         LOOP
  388.     END IF
  389.     Projection$ = TheReturn
  390.  
  391. SUB MapText
  392.     IF (TextFormatting = 1) THEN br$ = "~" ELSE br$ = " "
  393.     j = StartIndex
  394.     i = 1
  395.     q$ = ""
  396.     d$ = ""
  397.     DO ' Begin with any left-over text from previous iteration.
  398.         q$ = d$
  399.         d$ = ""
  400.         r = TextWidth - LEN(q$)
  401.         IF (TextWrapping <> 2) THEN k1 = NthP(j, r) ELSE k1 = EOC
  402.         k2 = NthPC(j, CHR$(13))
  403.         IF (TextWrapping <> 2) THEN c1 = LinearCount(j, k1) ELSE c1 = LinearCount2(j, k1, TextWidth * TextHeight)
  404.         c2 = LinearCount(j, k2)
  405.         IF (c2 = 0) THEN ' Line is blank-returned.
  406.             k = k2
  407.             q$ = q$ + br$
  408.             j = NthP(k, 2)
  409.         ELSE
  410.             IF (c1 = c2) THEN ' Possible end of chain.
  411.                 k = TheChain(k1).Lagger
  412.                 q$ = q$ + Projection$(j, k)
  413.                 j = NthP(k, 2)
  414.             END IF
  415.             IF (c1 < c2) THEN ' Width limit case (not always maximum).
  416.                 k = k1
  417.                 q$ = q$ + Projection$(j, k)
  418.                 j = NthP(k, 2)
  419.             END IF
  420.             IF (c1 > c2) THEN ' Break return somewhere in line (not first).
  421.                 k = k2
  422.                 q$ = q$ + Projection$(j, TheChain(k).Lagger) + br$
  423.                 n = TheChain(k).Pointer
  424.                 IF (n <> EOC) THEN j = n
  425.             END IF
  426.         END IF
  427.         IF (TextWrapping = 1) THEN ' Wrap text at first space from right, send remainder to next line.
  428.             IF (LEN(q$) >= TextWidth) THEN
  429.                 FOR m = LEN(q$) TO 1 STEP -1
  430.                     c$ = MID$(q$, m, 1)
  431.                     IF (c$ = " ") OR (c$ = "-") THEN
  432.                         q$ = LEFT$(q$, m)
  433.                         EXIT FOR
  434.                     END IF
  435.                     d$ = c$ + d$
  436.                     IF (m = 1) THEN ' Line is too long for allowed space and contains no wrapping characters.
  437.                         q$ = LEFT$(q$, TextWidth)
  438.                         d$ = ""
  439.                         EXIT FOR
  440.                     END IF
  441.                 NEXT
  442.             END IF
  443.         END IF
  444.         LineAsMapped(i) = q$
  445.         i = i + 1
  446.         IF (i >= TextHeight) THEN EXIT DO
  447.         IF (j = k) THEN EXIT DO
  448.     LOOP
  449.     VisibleLines = i - 1
  450.  
  451. SUB StateChange
  452.     MH = 0
  453.     MW = 0
  454.     MT = 0
  455.         MH1 = _MOUSEBUTTON(1)
  456.         MH2 = _MOUSEBUTTON(2)
  457.         MH3 = _MOUSEBUTTON(3)
  458.         MW = _MOUSEWHEEL
  459.         IF (MW <> 0) THEN MT = MW
  460.     LOOP
  461.     MW = MT
  462.  
  463.     IF (MH1 = -1) THEN
  464.         ' Move Cursor1 among text.
  465.         MH = 1
  466.         IF (_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight + 1) THEN
  467.             Cursor1.X = _MOUSEX
  468.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent))
  469.             IF (Cursor1.X > q) THEN Cursor1.X = q
  470.             Cursor1.Y = _MOUSEY
  471.             CALL ReassignID1
  472.         END IF
  473.         ' Move by scrollbar.
  474.         IF (_MOUSEX = _WIDTH) THEN
  475.             i = NthL(ID1, ChainLimit + 1)
  476.             j = NthP(ID1, ChainLimit + 1)
  477.             IF (_MOUSEY = _HEIGHT) THEN i = j
  478.             IF (_MOUSEY > 1) AND (_MOUSEY < _HEIGHT) THEN
  479.                 t = LinearCount(i, j)
  480.                 f = _MOUSEY / _HEIGHT
  481.                 FOR k = 1 TO t
  482.                     IF (k / t) >= f THEN EXIT FOR
  483.                     i = TheChain(i).Pointer
  484.                 NEXT
  485.             END IF
  486.             StartIndex = i
  487.             ID1 = i
  488.         END IF
  489.     END IF
  490.     IF (MH2 = -1) THEN
  491.         ' Move Cursor2 and copy anything between Cursor1 and Cursor2 to clipboard.
  492.         MH = 1
  493.         IF (_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight + 1) THEN
  494.             Cursor2.X = _MOUSEX
  495.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent))
  496.             IF (Cursor2.X > q) THEN Cursor2.X = q
  497.             Cursor2.Y = _MOUSEY
  498.             CALL ReassignID2
  499.             IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN _CLIPBOARD$ = Projection$(ID1, ID2)
  500.         END IF
  501.     END IF
  502.     IF (MH3 = -1) THEN
  503.         ' Paste at Cursor1 position.
  504.         MH = 1
  505.         IF (LinearCount(StartIndex, ID2) >= LinearCount(StartIndex, ID1)) THEN CALL InsertRange(ID1, _CLIPBOARD$)
  506.     END IF
  507.     IF (MW = -1) THEN
  508.         ' Wheel up
  509.         MH = 1
  510.         StartIndex = BackBreak(StartIndex)
  511.         CALL ReassignID1
  512.     END IF
  513.     IF (MW = 1) THEN
  514.         ' Wheel down
  515.         MH = 1
  516.         IF (VisibleLines > 1) THEN
  517.             StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  518.             CALL MapText
  519.         END IF
  520.         CALL ReassignID1
  521.     END IF
  522.  
  523.     KH = 0
  524.     KH = _KEYHIT
  525.  
  526.     ' Bksp
  527.     IF (KH = 8) THEN
  528.         r = TheChain(ID1).Pointer
  529.         q = TheChain(ID1).Lagger
  530.         CALL UnlinkCell(ID1)
  531.         IF ((r = EOC) AND (q = BOC)) THEN
  532.         ELSE
  533.             IF (q <> BOC) THEN ID1 = q ELSE ID1 = r
  534.             IF (r = EOC) THEN ID2 = ID1
  535.         END IF
  536.     END IF
  537.     ' Tab
  538.     IF (KH = 9) THEN CALL InsertRange(ID1, "    ")
  539.     ' Esc
  540.     IF (KH = 27) THEN
  541.         IF (ID2 <> ID1) THEN ID2 = ID1 ELSE ID2 = NthP(ID2, ChainLimit + 1)
  542.     END IF
  543.     ' Enter, Alphanumerics
  544.     IF (KH = 13) OR ((KH >= 32) AND (KH <= 126)) THEN
  545.         IF (InsertKey = -1) THEN
  546.             IF (ID1 = ID2) THEN
  547.                 CALL InsertBefore(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  548.             ELSE
  549.                 CALL InsertAfter(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  550.                 ID1 = NthP(ID1, 2)
  551.             END IF
  552.         ELSE
  553.             TheChain(ID1).Content = LTRIM$(RTRIM$(CHR$(KH)))
  554.             IF (ID1 = ID2) THEN
  555.                 ID1 = NthP(ID1, 2)
  556.                 ID2 = ID1
  557.             ELSE
  558.                 ID1 = NthP(ID1, 2)
  559.             END IF
  560.         END IF
  561.     END IF
  562.     ' F1
  563.     IF (KH = 15104) THEN
  564.         IF (TextWrapping = 2) THEN
  565.             HScroll = HScroll - 1
  566.             IF (HScroll < 1) THEN HScroll = 1
  567.             CALL ReassignID1
  568.             CALL ReassignID2
  569.         END IF
  570.     END IF
  571.     ' F2
  572.     IF (KH = 15360) THEN
  573.         IF (TextWrapping = 2) THEN
  574.             HScroll = HScroll + 1
  575.             CALL ReassignID1
  576.             CALL ReassignID2
  577.         END IF
  578.     END IF
  579.     ' F5
  580.     IF (KH = 16128) THEN
  581.         q$ = Projection$(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  582.         Assimilate q$
  583.     END IF
  584.     ' F4
  585.     IF (KH = 15872) THEN
  586.         'CALL InsertRange(NthP(ID2, 2), "=" + SxriptEval$(Projection(ID1, ID2)))
  587.     END IF
  588.     ' F6
  589.     IF (KH = 16384) THEN
  590.         OPEN FileName$ FOR OUTPUT AS #1
  591.         q$ = Projection$(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  592.         PRINT #1, q$
  593.         CLOSE #1
  594.     END IF
  595.     ' F7
  596.     IF (KH = 16640) THEN
  597.         CALL ConvertToGrid
  598.         CALL GOL
  599.         CALL ConvertFromGrid
  600.     END IF
  601.     ' F8
  602.     'IF (KH = 16896) THEN
  603.     ' Home
  604.     IF (KH = 18176) THEN
  605.         IF (TextWrapping = 2) THEN HScroll = 1
  606.         Cursor1.X = LeftIndent + 1
  607.         CALL ReassignID1
  608.     END IF
  609.     ' UpArrow
  610.     IF (KH = 18432) THEN
  611.         IF (Cursor1.Y > TopIndent + 1) THEN
  612.             Cursor1.Y = Cursor1.Y - 1
  613.         ELSE
  614.             StartIndex = BackBreak(StartIndex)
  615.         END IF
  616.         q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) + 1
  617.         IF (Cursor1.X > q) THEN Cursor1.X = q
  618.         CALL ReassignID1
  619.     END IF
  620.     ' PgUp
  621.     IF (KH = 18688) THEN
  622.         FOR k = 1 TO INT(TextHeight / 2)
  623.             StartIndex = BackBreak(StartIndex)
  624.         NEXT
  625.         CALL ReassignID1
  626.     END IF
  627.     ' LeftArrow
  628.     IF (KH = 19200) THEN
  629.         ID1 = NthL(ID1, 2)
  630.         IF (TextWrapping = 2) THEN
  631.             IF (Cursor1.X = LeftIndent + 1) THEN
  632.                 IF (HScroll > 1) THEN
  633.                     HScroll = HScroll - 1
  634.                 ELSE
  635.                     j = Cursor1.Y - TopIndent - 1
  636.                     IF (j >= 1) THEN
  637.                         k = LEN(LineAsMapped(j)) - TextWidth + 1
  638.                         IF (k >= 1) THEN
  639.                             HScroll = k
  640.                         END IF
  641.                     END IF
  642.                 END IF
  643.             END IF
  644.         ELSE
  645.             IF ((Cursor1.X - LeftIndent = 1) AND Cursor1.Y - TopIndent = 1) THEN
  646.                 StartIndex = BackBreak(StartIndex)
  647.             END IF
  648.         END IF
  649.     END IF
  650.     ' RightArrow
  651.     IF (KH = 19712) THEN
  652.         ID1 = NthP(ID1, 2)
  653.         m = Cursor1.X - LeftIndent
  654.         n = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - HScroll + 1
  655.         IF (TextWrapping = 2) THEN
  656.             IF (m >= TextWidth) THEN
  657.                 HScroll = HScroll + 1
  658.                 CALL ReassignID1
  659.             END IF
  660.             IF (m >= n) THEN
  661.                 j = Cursor1.Y - TopIndent + 1
  662.                 IF (j <= TextHeight) THEN HScroll = 1
  663.             END IF
  664.         ELSE
  665.             IF ((m >= n) AND (Cursor1.Y - TopIndent = VisibleLines)) THEN
  666.                 IF (VisibleLines > 1) THEN StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  667.             END IF
  668.         END IF
  669.     END IF
  670.     ' End
  671.     IF (KH = 20224) THEN
  672.         Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  673.         CALL ReassignID1
  674.         IF (TextWrapping = 2) THEN
  675.             q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - TextWidth + 1
  676.             IF (q >= 1) THEN HScroll = q
  677.         END IF
  678.     END IF
  679.     ' DownArrow
  680.     IF (KH = 20480) THEN
  681.         IF (Cursor1.Y = TopIndent + VisibleLines) THEN
  682.             IF (VisibleLines > 1) THEN
  683.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  684.                 CALL MapText
  685.             END IF
  686.         ELSE
  687.             Cursor1.Y = Cursor1.Y + 1
  688.         END IF
  689.         q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) + 1
  690.         IF (Cursor1.X > q) THEN
  691.             Cursor1.X = q
  692.         END IF
  693.         CALL ReassignID1
  694.     END IF
  695.     ' PgDn
  696.     IF (KH = 20736) THEN
  697.         FOR k = 1 TO INT(TextHeight / 2)
  698.             IF (VisibleLines > 1) THEN
  699.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  700.                 CALL MapText
  701.             END IF
  702.         NEXT
  703.         CALL ReassignID1
  704.     END IF
  705.     ' Insert
  706.     IF (KH = 20992) THEN
  707.         InsertKey = -InsertKey
  708.     END IF
  709.     ' Del
  710.     IF (KH = 21248) THEN
  711.         IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  712.             r = TheChain(ID2).Pointer
  713.             q = TheChain(ID1).Lagger
  714.             p = ID1
  715.             CALL UnlinkRange(ID1, ID2)
  716.             IF ((r = EOC) AND (q = BOC)) THEN
  717.                 ID1 = p
  718.                 ID2 = ID1
  719.                 StartIndex = p
  720.             ELSE
  721.                 IF (q <> BOC) THEN ID1 = q ELSE ID1 = r
  722.                 ID2 = NthP(ID1, 2)
  723.             END IF
  724.         END IF
  725.     END IF
  726.     ' F11
  727.     IF (KH = 34048) THEN TextFormatting = -TextFormatting
  728.     ' F12
  729.     IF (KH = 34304) THEN
  730.         TextWrapping = TextWrapping + 1
  731.         IF (TextWrapping > 2) THEN TextWrapping = 0
  732.         ID1 = StartIndex
  733.         ID2 = ID1
  734.         HScroll = 1
  735.     END IF
  736.     ' Cursor sync and autoscrolling.
  737.     IF ((MH <> 0) OR (KH > 0)) THEN
  738.         CALL MapText
  739.         CALL CalibrateCursor(ID1)
  740.         CALL CalibrateCursor(ID2)
  741.         IF (Cursor1.Y > TopIndent + TextHeight - 1) THEN StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  742.     END IF
  743.     _KEYCLEAR
  744.  
  745. SUB CalibrateCursor (a AS LONG)
  746.     ' Place Cursor under ID on rendered line.
  747.     s = StartIndex
  748.     IF ((TextWrapping = 2) AND (HScroll > 1)) THEN s = NthP(s, HScroll)
  749.     c = LinearCount(s, a)
  750.     k = 0
  751.     i = -1
  752.     FOR j = 1 TO VisibleLines
  753.         n = LEN(LineAsMapped(j))
  754.         IF (k + n < c) THEN
  755.             k = k + n
  756.         ELSE
  757.             i = c - k + 1
  758.             EXIT FOR
  759.         END IF
  760.     NEXT
  761.     IF (i >= LeftIndent + LEN(LineAsMapped(j))) THEN
  762.         IF (j <= VisibleLines) THEN
  763.             i = 1 ' LeftIndent
  764.             j = j + 1
  765.             'ELSE
  766.             '    i = 1 ' LeftIndent '+ LEN(LineAsMapped(j))
  767.             '    j = j + 1
  768.         END IF
  769.     END IF
  770.     IF (a = ID1) THEN
  771.         Cursor1.X = LeftIndent + i
  772.         Cursor1.Y = TopIndent + j
  773.     END IF
  774.     IF (a = ID2) THEN
  775.         Cursor2.X = LeftIndent + i
  776.         Cursor2.Y = TopIndent + j
  777.     END IF
  778.  
  779. FUNCTION FindID (a AS INTEGER, b AS LONG)
  780.     ' Find identity under a map location.
  781.     RelX = a - LeftIndent
  782.     RelY = b - TopIndent
  783.     FOR k = 1 TO RelY - 1
  784.         t = t + LEN(LineAsMapped(k))
  785.     NEXT
  786.     t = t + RelX
  787.     FindID = t
  788.  
  789. SUB ReassignID1
  790.     ' Reassign identity under Cursor1.
  791.     ID1 = NthP(StartIndex, FindID(Cursor1.X, Cursor1.Y) + (HScroll - 1))
  792.  
  793. SUB ReassignID2
  794.     ' Reassign identity under Cursor2.
  795.     ID2 = NthP(StartIndex, FindID(Cursor2.X, Cursor2.Y) + (HScroll - 1))
  796.  
  797. SUB ConvertToGrid
  798.     FOR j = 1 TO VisibleLines
  799.         c$ = LineAsMapped(j)
  800.         FOR i = 1 TO LEN(c$) - 1 ' BR offset to exclude break return at line end.
  801.             AuxGrid(i, j, 1) = MID$(c$, i, 1)
  802.         NEXT
  803.     NEXT
  804.  
  805. SUB ConvertFromGrid
  806.     q$ = ""
  807.     FOR j = 1 TO VisibleLines
  808.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  809.             q$ = q$ + AuxGrid(i, j, 1)
  810.         NEXT
  811.         q$ = q$ + CHR$(13) ' Undoes BR offset.
  812.     NEXT
  813.     Assimilate q$
  814.  
  815. SUB GOL
  816.     FOR j = 1 TO VisibleLines
  817.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  818.             c$ = AuxGrid(i, j, 1)
  819.             IF (c$ = " ") THEN c$ = "0" ELSE c$ = "1"
  820.             AuxGrid(i, j, 1) = c$
  821.             AuxGrid(i, j, 2) = c$
  822.         NEXT
  823.     NEXT
  824.     FOR j = 2 TO VisibleLines - 2 ' BR offset.
  825.         FOR i = 2 TO LEN(LineAsMapped(j)) - 2 ' BR offset.
  826.             c$ = AuxGrid(i, j, 1)
  827.             a1 = VAL(AuxGrid(i - 1, j + 1, 1))
  828.             a2 = VAL(AuxGrid(i, j + 1, 1))
  829.             a3 = VAL(AuxGrid(i + 1, j + 1, 1))
  830.             a4 = VAL(AuxGrid(i - 1, j, 1))
  831.             a6 = VAL(AuxGrid(i + 1, j, 1))
  832.             a7 = VAL(AuxGrid(i - 1, j - 1, 1))
  833.             a8 = VAL(AuxGrid(i, j - 1, 1))
  834.             a9 = VAL(AuxGrid(i + 1, j - 1, 1))
  835.             t = a1 + a2 + a3 + a4 + a6 + a7 + a8 + a9
  836.             IF (c$ = "1") THEN
  837.                 SELECT CASE t
  838.                     CASE IS < 2
  839.                         AuxGrid(i, j, 2) = "0"
  840.                     CASE 2
  841.                         AuxGrid(i, j, 2) = "1"
  842.                     CASE 3
  843.                         AuxGrid(i, j, 2) = "1"
  844.                     CASE IS > 3
  845.                         AuxGrid(i, j, 2) = "0"
  846.                 END SELECT
  847.             ELSE
  848.                 IF (t = 3) THEN AuxGrid(i, j, 2) = "1"
  849.             END IF
  850.         NEXT
  851.     NEXT
  852.     FOR j = 1 TO VisibleLines
  853.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  854.             c$ = AuxGrid(i, j, 2)
  855.             IF (c$ = "0") THEN c$ = " " ELSE c$ = CHR$(219)
  856.             AuxGrid(i, j, 1) = c$
  857.             AuxGrid(i, j, 2) = c$
  858.         NEXT
  859.     NEXT
  860.  
  861.  

------------ Old Post ----------

So I'm not sure what any of you would do in your 6th month of having no Internet access at home, occasionally clinging to the underbelly of the digital world via mobile hot spot. I can only compare this to the coder's equivalent to a silent mediation retreat. Out of the silence echoed an idea though that's about ready to share.

This post is about a new text editor - I needed a way to avoid all the foreseeable pitfalls ahead of time, which required a little thinking outside the (text) box. First is a topological comment: text is fundamentally one dimensional. The fact that we blob text into rectangles is completely a human interest - the information is really linear. We tend to forget this fact at step 1 though (without mentioning names, people tend to start with the box and work backwards to the text, amirite?)

So this is a completely different approach. There is not one big 1D array, or a screen-fitted 2D array to hang the characters on. That will get stupidly slow when your text is 50,000 characters large and you decide to paste something in the middle. This approach must completely avoid looping through arrays all the time - even some of the time.

What I do is define a type "cell" that has an address (addresses are not in linear order, or any order whatsoever, this is the key!), the content (just a single character), and then two bits of information: a "pointer", which contains the address of the next character, and a "lagger", which is the address of the previous character. To loop through the text, start at any identity and follow the pointer. This means you can insert and delete arbitrary blobs of text into the main body without bumping all the content up or down an array. It's more like adding or subtracting frames from an old movie strip.

So this program is somewhat polished and I can explain it all day, there are a ton of features engineered in: wrapping, handling absurdly long lines with no breaks, jumping around, copy+paste, etc. It uses two cursors to do everything - I've learned to be really handy with this as a general tool. A few more tweaks and I can delete Notepad. One thing I will admit is the mechanism component is probably bug free after all this testing, but there is at least one shallow off-by-one policy bug I just noticed but ran out of time to smooth out.

The program loads a default text blob, or you can click+drag other files onto the exe. For no good reason whatsoever, the character limit is 64000. I'll bump that up to infinity in the next version.

(Btw Fellippe, this is a candidate mechanism for InForm's textbox if you want it. It doesn't need to be monospace font forever either. This whole thing is less than 500 lines and half of that is policy junk you would strip off.)[/s]
« Last Edit: January 29, 2021, 01:07:24 AM by STxAxTIC »
TOXIC

Offline Pete

  • Forum Resident
  • Posts: 2567
  • Cuz I sez so, varmint!
Re: qXed: linked list text editor
« Reply #1 on: September 17, 2018, 05:20:54 AM »
Well it's a bit buggy but text editors are a real challenge. I had one I did not finish that was about 1600 lines in SCREEN 0. Wow, that was back in January, time flies. I know the blinking cursor needs to be programmed for other screens, so nice job there! Delete needs work for some reason. I wish I could be more specific but I just fiddled around with it a bit before shutting down and now off to put what's left of my aging intellect to bed.

Pete

Offline freetrav

  • Newbie
  • Posts: 66
Re: qXed: linked list text editor
« Reply #2 on: September 17, 2018, 08:26:58 AM »
It's an interesting approach, and should work in theory, but its memory efficiency... well, let's just say that that phrase doesn't apply.  You might do better to acknowledge some human perception of file structure, and go with larger chunks in your linked list - if this is strictly a text editor, then chunking at the line level (Newline/CRLF as delimiter) might be the way to go; if you're planning on making it a general-purpose file editor, with separate text and binary modes, the binary mode might do better with 16- or 32-byte chunking.

Offline bplus

  • Forum Resident
  • Posts: 6843
  • b = b + ...
Re: qXed: linked list text editor
« Reply #3 on: September 17, 2018, 09:31:46 AM »
Oh yeah! That is an interesting idea.

I knew array(s) of strings don't work but I seem to remember 20+ years ago the giant string approach working even for selecting text. Alas, code gone with the DOS. Reading someone else's beautiful monster, no fun, but 500 lines might be doable!

Nice meditation.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1062
  • TOXIC
Re: qXed: linked list text editor
« Reply #4 on: September 17, 2018, 09:35:16 AM »
Hi guys, thanks for your hasty replies.

Pete - I didnt bother explaining the behaviors, so it wont be acting like Wordpad any time time soon. There are several "smart" cursor behaviors that will probably be confusing until you game it enough or I explain how things go. There is one actual tiny bug that I know of.

Trav - something tells me you didnt test it, or at least test its limits. Its lightning fast out of the box even with 50k characters- in this design, memory management is done on the fly, junk data does not linger. Certain hardcoded values will change once I sit down and make it handle arbitrary text sizes.
TOXIC

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1062
  • TOXIC
Re: qXed: linked list text editor
« Reply #5 on: September 17, 2018, 09:38:45 AM »
Thanks bplus-

Just FYI, this does not deal in giant strings. It can churn a big string into cells, and sometimes boots that way, but doesnt stay there.

(Using phone to write replies so pardon my terseness.)
TOXIC

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2935
  • Let it go, this too shall pass.
    • QB64.org
Re: qXed: linked list text editor
« Reply #6 on: September 17, 2018, 09:44:32 AM »
This is cool! A different approach, nonetheless.

I just ran it but didn't read through it yet. One of the greatest struggles I found with my preliminary multiline text box control for InForm was to handle variable-width characters, as that must be taken into account when deciding what part of the text is being displayed at what part of the screen so a click has the cursor properly placed.

I'll get back to you once I've done my homework with this.

Offline bplus

  • Forum Resident
  • Posts: 6843
  • b = b + ...
Re: qXed: linked list text editor
« Reply #7 on: September 17, 2018, 10:00:13 AM »
Thanks bplus-

Just FYI, this does not deal in giant strings. It can churn a big string into cells, and sometimes boots that way, but doesnt stay there.

(Using phone to write replies so pardon my terseness.)

Yes, I understand not giant string, this qXed works like snake in Snake Game and Pathfinding, each cell of snake or path only knows the one before (or after) it, and yet the whole thing can be assembled as one. Great idea to apply it to strings.

On occasion, a giant string could be reassembled but what would occasion the need? Don't even need to do it for filing.

Hi Fellippe,

I wondered what the holdup was with EditBox control. Now I know, the variable width Fonts. Certainly was tricky for ListBox control getting even MONOSPACE in a predictable cell size.


Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1062
  • TOXIC
Re: qXed: linked list text editor
« Reply #8 on: September 17, 2018, 10:01:12 AM »
Thanks Fellippe-

You'll see that most of the time, cursor positions are not a function of X,Y on the screen, but they follow the topology of the text. This engineers out any width calculations in that ballbark.

At other moments, you do in fact need to reverse-calculate the position under a cursor. Anticipating this from the git go, I assure its not that big a leap.
TOXIC

Offline Petr

  • Forum Resident
  • Posts: 1580
  • The best code is the DNA of the hops.
Re: qXed: linked list text editor
« Reply #9 on: September 17, 2018, 10:27:02 AM »
Nice work, Stxaxtic!

Quote
One of the greatest struggles I found with my preliminary multiline text box control for InForm was to handle variable-width characters, as that must be taken into account when deciding what part of the text is being displayed at what part of the screen so a click has the cursor properly placed.

 Invalid value _FONTWIDTH 0 is sometimes returned when it comes to working with variable characters, if you mean fonts, and the MONOSPACE type. It can be calculated as _PRINTWIDTH / LEN (Text $). Well, some fonts are displayed poorly, cut at the top and some on the right. It's just one or two pixels, but it's visible.


Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2935
  • Let it go, this too shall pass.
    • QB64.org
Re: qXed: linked list text editor
« Reply #10 on: September 17, 2018, 10:35:21 AM »
Invalid value _FONTWIDTH 0 is sometimes returned when it comes to working with variable characters, if you mean fonts, and the MONOSPACE type. It can be calculated as _PRINTWIDTH / LEN (Text $).

InForm uses a custom text rendering set of routines (that falcon.h file that must be present in QB64's folder is the rendering library provided by Luke) and these provide an array of width data for each character printed.

Besides having that advantage, using falcon also solves the font rendering issues you mentioned:

Well, some fonts are displayed poorly, cut at the top and some on the right. It's just one or two pixels, but it's visible.

Offline Petr

  • Forum Resident
  • Posts: 1580
  • The best code is the DNA of the hops.
Re: qXed: linked list text editor
« Reply #11 on: September 17, 2018, 10:39:22 AM »
I see. Thank you for the information. Will the repair not be in the foreseeable future?

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2935
  • Let it go, this too shall pass.
    • QB64.org
Re: qXed: linked list text editor
« Reply #12 on: September 17, 2018, 10:48:08 AM »
It's deep in the C++ level, not my area. Let's wait and see if it gets incorporated eventually.

Offline Pete

  • Forum Resident
  • Posts: 2567
  • Cuz I sez so, varmint!
Re: qXed: linked list text editor
« Reply #13 on: September 17, 2018, 01:38:10 PM »
Started playing around with it this morning and it locked up using delete and F12 with some cursors. I could not replicate it, as I'm not keeping track of exact keystrokes at this moment. It also locked up again playing with the delete key. I had almost all of the text deleted. I see what you did using delete to remove text between the white and orange cursor. It was going great until I had only three letters left on the screen. At that point, I couldn't get any key responses. Cursor wouldn't work and I could no longer delete or type other characters.

This is generally how I go about testing my own WP creations, willy-nilly at first. I figure if it can't handle hard random typing it's buggy in more than one place. That usually gets the worst mistakes taken out but there are usually small unusual bugs that need to be addressed later. Of course I've seen irregularities in even professional programs like Notepad and Word. Some situations in WP are simply the Kobayashi Maru with how some word wrap situation get handled. I'm not the type that documents. Why? Because I hate the time it takes away from coding. If I liked documentation, I'd probably have a book of all the conditions that need to be met and the various ways to go about designing a WP by now. I've played around with single strings when QB64 came out. QB45 couldn't handle that. I have also used RA files and arrays. They all have there pluses and minuses but require different coding approaches, which makes it fun to try different approaches.

I would disagree on how you position your cursor for backspace. In Open Office you can press INSERT to make the | cursor a square. Now you can overwrite characters. So if you place the cursor square over a letter and use backspace, it eliminates the letter behind the cursor and the cursor moves back one space. Your WP actually eliminates the letter under the cursor and then drags the remains back one space. Your method works, but I'd call it unusual.

I just got an error 5 at line 128 when playing around with the right scroll bar. I was using the mouse to move it up and down and mouse and it threw the error at one of the many times I had the scroll bar pulled all the way to the bottom of the screen.

Why is cursor down so random between paragraphs? Put the cursor at the end of line one and continuously cursor all the way down through the paragraphs. I expected it to either stay in the column, stay at the left edge, or go to the right margin when it moved down to the new paragraph. Conversely, cursor up is refuses to jump up to the above paragraph immediately. Instead when the cursor is somewhere in the middle of the start of a paragraph, pressing cursor up acts as if cursor left was pressed. This continues until it reaches the right margin, then another cursor up press finally gets the cursor to go up. If that's the way you intended it to act, why did you not make the cursor down key work in the reverse fashion?

Here's more unexpected behavior. Start the program and mouse click to the "]" in "multiplex" near the right bottom of the screen. Now either cursor down or mouse click directly down under the "]" (which is a black blank space). Anyway, the cursor re-appears not on the next line, but two lines down and not on the first letter, but on the second, the "e" in metaphysics.

One final thing, programming a WP should come with a warning label. Caution, may cause significant hair loss and Transient Tourette Syndrome. If you can handle the ******* baldness, you will eventually have it ready for prime time.

Pete



Offline TerryRitchie

  • Forum Resident
  • Posts: 611
  • Semper Fidelis
Re: qXed: linked list text editor
« Reply #14 on: September 17, 2018, 01:56:57 PM »

One final thing, programming a WP should come with a warning label. Caution, may cause significant hair loss and Transient Tourette Syndrome. If you can handle the ******* baldness, you will eventually have it ready for prime time.

Pete

I've always found processing text to be a hair-raising experience.

I find it to be a paradox in programming. Text "looks" easy to manipulate while graphics "looks" difficult. I find it to be the exact opposite.
In order to understand recursion, one must first understand recursion.