QB64.org Forum

Active Forums => Programs => Topic started by: STxAxTIC on September 17, 2018, 12:44:06 am

Title: qXed: linked list text editor
Post by: STxAxTIC 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]
Title: Re: qXed: linked list text editor
Post by: Pete 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
Title: Re: qXed: linked list text editor
Post by: freetrav 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.
Title: Re: qXed: linked list text editor
Post by: bplus 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.
Title: Re: qXed: linked list text editor
Post by: STxAxTIC 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.
Title: Re: qXed: linked list text editor
Post by: STxAxTIC 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.)
Title: Re: qXed: linked list text editor
Post by: FellippeHeitor 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.
Title: Re: qXed: linked list text editor
Post by: bplus 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.

Title: Re: qXed: linked list text editor
Post by: STxAxTIC 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.
Title: Re: qXed: linked list text editor
Post by: Petr 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.

Title: Re: qXed: linked list text editor
Post by: FellippeHeitor 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.
Title: Re: qXed: linked list text editor
Post by: Petr on September 17, 2018, 10:39:22 am
I see. Thank you for the information. Will the repair not be in the foreseeable future?
Title: Re: qXed: linked list text editor
Post by: FellippeHeitor 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.
Title: Re: qXed: linked list text editor
Post by: Pete 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


Title: Re: qXed: linked list text editor
Post by: TerryRitchie 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.
Title: Re: qXed: linked list text editor
Post by: Pete on September 17, 2018, 02:46:06 pm
LOL - Sure, this coming from the guy in the pic wearing the big hat! Yeah, like I should talk (looks at own avatar.)

Pete

- People who talk out of their hats aren't so bad... unless they're sitting on them at the time.
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 17, 2018, 07:03:39 pm
Ah, a computer. The freedom.

Thanks everyone for discussing and motivating this topic, especially Pete - I concur with just about all of the observations you've made. You have picked out plenty of the places where the policy (the word I keep insisting on using to differentiate from the fundamental mechanism) falls outside expectations. The dual-cursor delete stroke is hardly tested, and so are the edge cases. The thing crashes when there are 2 or fewer characters, or more than 64000. That's gotta be dealt with for sure. As for the cursor jumping when scrolling - again I think this is a shallow fix. All duly noted.... Except the scrollbar thing - that is just an indicator, and is not a mouse control. Thought that analysis was still useful though, there might be crash around the corner when the cursor goes off screen.

Thanks again and, I'm gonna keep refining what I have. Let's hope it stays under 500 lines (haha).
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 18, 2018, 12:18:29 am
Update-

So I chipped away at a few things Pete pointed out, and tightened up the operation overall. The code in the top post has already been updated, so don't worry about finding the old version on the forum. By that token, I refrain from explaining the controls in depth because they are subject to change and don't need junk information floating around.

All that said, enjoy fixed scrolling, cursor confinement, and more magic when you press F11.

https://www.qb64.org/forum/index.php?topic=597.0 (https://www.qb64.org/forum/index.php?topic=597.0)
Title: Re: qXed: linked list text editor
Post by: Pete on September 18, 2018, 01:46:03 am
Bill, this job will probably drive you crazy before you drive it home. WPs have a weird way of a fix in one place causes a fault in another. The first thing I noticed is wrap failed to work upon initiation. The third paragraph that starts with, "[What astonished..." does not wrap correctly.  It ends at the right margin with the "t" in the, which should have been wrapped to the next line.

The cursor problem I mentioned going from the "]" on multiplex now works. The mouse doesn't. I see that what happens is spaces in the line below get added to the next line. This means if I click the last blank space below "]" the cursor is advanced 4 spaces past the left margin on the next line. The code needs to be told when advancing to the next line to ignore the blank spaces so it ends up on the first character. Another cursor issue is the automatic scroll up that happens when you click a character on the last line. I do that in my WP apps, too, but after the scroll up, the cursor needs to be positioned one-line up, too.

Cursor down works great but cursor up is still problematic. This happens if you scroll the text so only the last paragraph is on the scree, put cursor on a character at the left margin, and then keep pressing cursor up. You will see when it starts to jump text lines down that the cursor will migrate to the right instead of staying at the left margin, as expected.

It still gets an illegal function when you delete it down to the last couple of characters.

Now this one is another wrap problem. Place the cursor at the very end of the last paragraph and hold the delete key. keep deleting characters until it gets to the first character on the second line. When you delete it, the text will auto-scroll down, but the wrap function will have "hapel" instead of "chapel" and this will continue to fail as you go up subsequent lines as characters are deleted. Also, the cursor when the line jumps seems to go off to the upper right instead of staying on the last character but strangely enough, if you press delete, without changing the cursor position, it seems to remember where it left off and deletes the last character, of the text, as expected.

Good job on the home-end functions, btw. People don't get that it is a bit of a trick to manage spaces in that right margin.

Nice job on trailing spaces!

I like that you handled hyphenated words so anything after the hyphen would wrap. I can't remember if I addressed that one in my apps, or not.

Pete
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 18, 2018, 07:24:58 am
Good morning,

Now *there's* the Pete I remember. Niiiiice and encouraging, haha. Yeah I'll just quit by tomorrow, you're right.

Sarcasm aside, it seems you noticed the actual bug I haven't bothered seeking out yet - its an off-by-one error that doesn't wrap the first line in any paragraph, even in non-wrap mode. The bug ins't even in the wrapping code, so its super shallow.

You're totally right about the cursor jumping when scrolling up. I concentrated on scrolling up by the right amount but apparently didn't bother putting the cursor where it belongs - will take one line to fix that.

I still acknowledge that it will crash when you delete basically all the characters. I save edge cases like that for last, dont worry.

As for the juicier observation you made about wrapping - damn, I'm just out of time at the moment. Ill have to pick that up tonight.

Closing comments before I rush out: This code is tight enough for me to aspire for it to be bug-free. I heartfully appreciate your (and everyone's) observations, and the time it takes to spell them all out. Maybe by pure accident this will feel like a Wordpad or a Gedit toward the end, but for now all it needs to do is keep me busy. Now that I've joined the race, I'm dying to know how far other people got with this. Say Pete, is your code from a few months (or years, pick any snapshot) still laying around the Internet somewhere?
Title: Re: qXed: linked list text editor
Post by: Aurel on September 18, 2018, 07:25:39 am
STATIC and others
this approach have advantage only if your editor is used as code editor for programming or
scripting language and makes tekenization or parsing easier.
 
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 18, 2018, 09:24:26 am
Not sure I follow, Aurel. This method is no better for parsing than MID$, it does not lend itself to parsing for any good reason.

(What it *is* good for, data structure-wise, is nonlinear chains: trees, nodes, neural nets, that ballpark.)

Back to your point though: isnt general text editing almost a special case of coding? What approach is better in your view?
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 27, 2018, 10:28:05 pm
Ahoy everyone,

I found enough time to iron out the visible bugs, added a few new things as well. Still deciding how the behaviors will really work in the end, so there is no manual at the moment.  Needless to say, it's crystallizing nicely. Code updated in the top post.
Title: Re: qXed: linked list text editor
Post by: bplus on September 28, 2018, 11:48:39 am
Hi STxAxTIC,

Looks like fixes cost 200 lines, 2/5 ths the first edition .

Thanks for update. :)
Title: Re: qXed: linked list text editor
Post by: Pete on September 29, 2018, 03:31:08 pm
Now I'm starting to look at the typing, and I discovered this unexpected behavior.

Place the cursor on the second line between "geometrical" and "spaces"

Type 123

That results in the expected: geometrical123 spaces

Without moving the current cursor position, hit the backspace key 3 times. This should undo the 123 that was inserted, but it doesn't.

Results: geometric1spaces

That "1" should have been removed. Instead what got removed is the space, which should have preserved.

This is very odd behavior for any WP. Backspace needs to cursor back first before it deletes a character. I'm pretty sure that's universal in WP applications. That fix, hopefully, will only take calling a cursor back routine before the delete character routine.

I'd like to check more, but I'm pretty tied up with all this SCOTUS appointment drama. Hey Bill, maybe you can help me by adding a replace feature to your WP. Please add a suitable auto-suggestion list of replacement words for the things I'm placing in my rough drafts to congress. It shouldn't take up to many lines. Most of the words that would need to be flagged are only 4-letters long.

Pete

Title: Re: qXed: linked list text editor
Post by: bplus on September 29, 2018, 03:38:32 pm
Save time, flag the signature. ;)

bplus ;D
Title: Re: qXed: linked list text editor
Post by: Pete on September 29, 2018, 03:46:06 pm
Thanks, "Mark"

Pete :D
Title: Re: qXed: linked list text editor
Post by: bplus on September 29, 2018, 03:52:22 pm
Hey, just call me Mark123.
Title: Re: qXed: linked list text editor
Post by: Pete on September 29, 2018, 04:02:31 pm
Yeah, thanks to my suggestion, I bet Steve is LHAO at Bill, Pete, Mark, and Fell and thanking his folks today for not naming him Jack.

Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 29, 2018, 06:00:00 pm
Quote
Without moving the current cursor position, hit the backspace key 3 times. This should undo the 123 that was inserted, but it doesn't.

Hi Pete-

That's not a bug right now, it's perfectly expected and as the program is not prematurely optimized. I know you don't like it. One day I'll make a mode that says "behave exactly like Pete's favorite, Wordpad." Until then, it's confusing probably because you aren't playing the ESC key to change modes, which is probably because I haven't released a manual yet, which is probably because I said a few times already that there will be no manual til I get this how I want it. (So I'm saying press ESC first and redo your test but I don't blame you.)

That said, thanks for testing. Was that the only thing?
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 29, 2018, 06:10:21 pm
Aright screw it I'll make that change of behavior the next thing I do.
Damn you Pete in the most respectful way possible!
Title: Re: qXed: linked list text editor
Post by: Pete on September 29, 2018, 06:23:00 pm
Well, the ESC key just reverses the backspace quirk with an insert quirk. Also, trying that a couple of times made the system lock up.

Anyway, is it my understanding you are also making this as a library for others to use? If so, reinventions are usually expected to follow established norms first, and then add new features. People who are used to the way most WP programs work get a little bent out of shape when they look at what they thought they typed, but the screen results show something different. Especially people like me, who have to look at porn... I mean the keyboard, while I'm typing.

Pete :D
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 29, 2018, 06:40:18 pm
Lemme take a quick guess - you don't use VI(m) do you? Want to see a learning curve in a text editor that you will curse up and down and then curse again left and right because somehow its the most pervasive in the world? Pop open a linux terminal, type "vi", and try to get *anything* done. It's almost uncrackable, but somehow the most argued-for text editor ever. Just weird.

Anyway, I'll be updating the top post in a number of minutes. The cursor will do the more natural thing.

Thanks again, these posts become very motivational on my end...
Title: Re: qXed: linked list text editor
Post by: Pete on September 29, 2018, 06:59:38 pm
Yeah, I'm nothing if not helpful... HEY, where'd I go!

Pete
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on September 30, 2018, 09:24:57 am
Okay, I couldn't resist having a little fun with this one.

I've taken the liberty of pressing the Insert key *for* you in case you want to edit this thing as it goes.

Otherwise, press and hold F7 to watch the Gosper Glider Gun a la Conway's Game of Life.

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 MemLimit AS LONG
  17. DIM SHARED BOC AS LONG ' Beginning of chain.
  18. DIM SHARED EOC AS LONG ' End of chain.
  19. MemLimit = 64000
  20. BOC = -1
  21. EOC = MemLimit
  22.  
  23. ' Define text window properties.
  24. DIM SHARED TextWrapping
  25. DIM SHARED TextFormatting
  26. DIM SHARED VisibleLines
  27. DIM SHARED TopIndent
  28. DIM SHARED LeftIndent
  29. DIM SHARED TextHeight
  30. DIM SHARED TextWidth
  31. DIM SHARED HScroll
  32. DIM SHARED InsertKey
  33. TextWrapping = 1
  34. TextFormatting = -1
  35. TopIndent = 1
  36. LeftIndent = 1
  37. TextHeight = _HEIGHT - 2 * TopIndent
  38. TextWidth = _WIDTH - 2 * LeftIndent
  39. HScroll = 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 FunGrid(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.  
  67. ' Create memory space for string.
  68. DIM SHARED TheChain(MemLimit) AS Cell
  69.  
  70. ' Create character list.
  71. CALL Assimilate(q$)
  72.  
  73. ' Prime main loop.
  74. CALL MapText
  75. CALL CalibrateCursor(ID1)
  76. CALL CalibrateCursor(ID2)
  77. CALL PrintEverything
  78.  
  79. DIM SHARED DEBUG$
  80.  
  81. DO ' Main loop.
  82.     IF StateChange THEN PrintEverything
  83.     _DISPLAY
  84.     _LIMIT 120
  85.  
  86. SUB PrintEverything
  87.     CLS
  88.     COLOR 7, 1
  89.     FOR i = 1 TO VisibleLines
  90.         c$ = LineAsMapped(i)
  91.         IF ((TextFormatting = 1) AND (TextWrapping <> 2)) THEN
  92.             FOR j = 1 TO TextWidth - LEN(c$)
  93.                 c$ = c$ + "_"
  94.             NEXT
  95.         END IF
  96.         _PRINTSTRING (LeftIndent + 1, TopIndent + i), MID$(c$, HScroll, TextWidth)
  97.     NEXT
  98.  
  99.     COLOR 11, 0
  100.     c$ = "qXed" + DEBUG$
  101.     _PRINTSTRING (1, 1), c$
  102.  
  103.     IF ((Cursor2.X > 0 AND Cursor2.X < _WIDTH) AND ((Cursor2.Y > 0) AND (Cursor2.Y < _HEIGHT))) THEN
  104.         p1 = LinearCount(StartIndex, ID1)
  105.         p2 = LinearCount(StartIndex, ID2)
  106.         pe = LinearCount(StartIndex, EOC)
  107.         IF ((p2 > p1) AND (p2 < pe)) THEN
  108.             COLOR 0, 6
  109.             c$ = TheChain(ID2).Content
  110.             IF (c$ = " ") THEN c$ = "_"
  111.             IF (c$ = CHR$(13)) THEN c$ = "~"
  112.             _PRINTSTRING (Cursor2.X, Cursor2.Y), c$
  113.         END IF
  114.     END IF
  115.  
  116.     IF ((Cursor1.X > 0 AND Cursor1.X < _WIDTH) AND ((Cursor1.Y > 0) AND (Cursor1.Y < _HEIGHT))) THEN
  117.         IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN COLOR 16, 5 ELSE COLOR 16, 3
  118.         c$ = TheChain(ID1).Content
  119.         IF (c$ = " ") THEN c$ = "_"
  120.         IF (c$ = CHR$(13)) THEN c$ = "~"
  121.         _PRINTSTRING (Cursor1.X, Cursor1.Y), c$
  122.     END IF
  123.  
  124.     d$ = TheChain(ID1).Content
  125.     e$ = TheChain(ID2).Content
  126.     IF ((ASC(d$) = 10) OR (ASC(d$) = 13)) THEN d$ = "~"
  127.     IF ((ASC(e$) = 10) OR (ASC(e$) = 13)) THEN e$ = "~"
  128.     IF (ASC(d$) = 32) THEN d$ = "_"
  129.     IF (ASC(e$) = 32) THEN e$ = "_"
  130.  
  131.     IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN COLOR 0, 5 ELSE COLOR 0, 3
  132.     c$ = "(" + LTRIM$(RTRIM$(STR$(Cursor1.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor1.Y - TopIndent))) + ":" + " " + LTRIM$(RTRIM$(d$)) + " " + LTRIM$(RTRIM$(STR$(ID1))) + ")"
  133.     _PRINTSTRING (2, _HEIGHT), c$
  134.     IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  135.         COLOR 0, 6
  136.         _PRINTSTRING (3 + LEN(c$), _HEIGHT), "(" + LTRIM$(RTRIM$(STR$(Cursor2.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor2.Y - TopIndent))) + ":" + " " + LTRIM$(RTRIM$(e$)) + " " + LTRIM$(RTRIM$(STR$(ID2))) + ")"
  137.     END IF
  138.  
  139.     COLOR 8, 7
  140.     p = LinearCount(ID1, NthP(ID1, MemLimit + 1))
  141.     q = LinearCount(NthL(ID1, MemLimit + 1), NthP(ID1, MemLimit + 1))
  142.     IF (q = 0) THEN r = 1 ELSE r = 1 - p / q
  143.     _PRINTSTRING (_WIDTH, 1 + INT(r * (_HEIGHT - 1))), "*"
  144.  
  145.     COLOR 15, 0
  146.     SELECT CASE TextWrapping
  147.         CASE 0: d$ = "Square"
  148.         CASE 1: d$ = "Fluid"
  149.         CASE 2: d$ = "None"
  150.     END SELECT
  151.     c$ = "[F6=Save] [F11=Formatting] [F12=Wrapping: " + d$ + "]"
  152.     IF (TextWrapping = 2) THEN c$ = "[F1/2=HScroll] " + c$
  153.     c$ = c$ + STR$(INT(100 * r)) + "%"
  154.     _PRINTSTRING (_WIDTH - LEN(c$), 1), c$
  155.  
  156.     c$ = "[Esc=Sync] [Mouse2=Copy] [Mouse3=Paste]"
  157.     IF (InsertKey = 1) THEN c$ = "[Ins] " + c$
  158.     _PRINTSTRING (_WIDTH - LEN(c$), _HEIGHT), c$
  159.  
  160. SUB Assimilate (a AS STRING)
  161.     ' Load a string to initialize chain.
  162.     FOR k = 1 TO MemLimit
  163.         TheChain(k).Identity = 0
  164.     NEXT
  165.     StartIndex = 1
  166.     PreviousIdentity = BOC
  167.     NextIdentity = NextOpenIdentity(StartIndex)
  168.     FOR k = 1 TO LEN(a)
  169.         j = NextIdentity
  170.         TheChain(j).Identity = j
  171.         TheChain(j).Content = ReFormat$(MID$(a, k, 1))
  172.         TheChain(j).Lagger = PreviousIdentity
  173.         PreviousIdentity = j
  174.         IF (k < LEN(a)) THEN
  175.             NextIdentity = NextOpenIdentity(j)
  176.             TheChain(j).Pointer = NextIdentity
  177.         ELSE
  178.             TheChain(j).Pointer = EOC
  179.         END IF
  180.         PRINT TheChain(j).Content
  181.     NEXT
  182.     ID1 = StartIndex
  183.     ID2 = ID1
  184.  
  185. FUNCTION ReFormat$ (a AS STRING)
  186.     c$ = a
  187.     IF c$ = CHR$(10) THEN c$ = CHR$(13)
  188.     IF c$ = CHR$(9) THEN c$ = "    "
  189.     ReFormat = c$
  190.  
  191. FUNCTION NthP (a AS LONG, b AS LONG)
  192.     ' Returns the address that is b jumps ahead of address a.
  193.     i = a
  194.     IF (i <> EOC) THEN
  195.         k = 0
  196.         j = 0
  197.         DO WHILE (k < b)
  198.             k = k + 1
  199.             j = TheChain(i).Identity
  200.             i = TheChain(j).Pointer
  201.             IF (i = EOC) THEN EXIT DO
  202.         LOOP
  203.     END IF
  204.     NthP = j
  205.  
  206. FUNCTION NthPC (a AS LONG, b AS STRING)
  207.     ' Returns the address holding b first enLinearCountered from a.
  208.     i = a
  209.     DO
  210.         j = TheChain(i).Identity
  211.         i = TheChain(j).Pointer
  212.         IF (TheChain(j).Content = b) THEN EXIT DO
  213.         IF (i = EOC) THEN
  214.             j = BOC
  215.             EXIT DO
  216.         END IF
  217.     LOOP
  218.     NthPC = j
  219.  
  220. FUNCTION NthL (a AS LONG, b AS LONG)
  221.     ' Returns the address that is b jumps behind address a.
  222.     i = a
  223.     k = 0
  224.     DO WHILE k < b
  225.         k = k + 1
  226.         j = TheChain(i).Identity
  227.         i = TheChain(j).Lagger
  228.         IF (i = BOC) THEN EXIT DO
  229.     LOOP
  230.     NthL = j
  231.  
  232. FUNCTION NextOpenIdentity (a AS LONG)
  233.     ' Returns first nonzero identity.
  234.     FOR j = a TO MemLimit
  235.         IF (TheChain(j).Identity = 0) THEN EXIT FOR
  236.     NEXT
  237.     IF (j > MemLimit) THEN
  238.         PRINT "Out of memory: "; MemLimit
  239.         SLEEP
  240.         SYSTEM
  241.     END IF
  242.     NextOpenIdentity = j
  243.  
  244. FUNCTION BackBreak (a AS LONG)
  245.     ' Function for scrolling up.
  246.     j = a
  247.     lastbreak = 0
  248.     c$ = ""
  249.     DO
  250.         IF (j = BOC) THEN EXIT DO
  251.         k = TheChain(j).Lagger
  252.         IF (k = BOC) THEN
  253.             lastbreak = j
  254.             EXIT DO
  255.         END IF
  256.         j = k
  257.         d$ = TheChain(j).Content
  258.         IF ((TextWrapping = 1) AND (d$ = " ")) THEN lastbreak = j
  259.         c$ = d$ + c$
  260.         IF (TextWrapping <> 2) AND (LEN(c$) = TextWidth) THEN EXIT DO
  261.         IF (d$ = CHR$(13)) THEN EXIT DO
  262.     LOOP
  263.     IF (lastbreak <> 0) THEN j = TheChain(lastbreak).Identity
  264.     BackBreak = j
  265.  
  266. SUB InsertBefore (a AS LONG, b AS STRING)
  267.     ' Inserts a single cell before address a in the chain.
  268.     j = NextOpenIdentity(a)
  269.     al = TheChain(a).Lagger
  270.     TheChain(j).Identity = j
  271.     TheChain(j).Pointer = a
  272.     TheChain(j).Lagger = al
  273.     TheChain(j).Content = ReFormat$(b)
  274.     TheChain(a).Lagger = j
  275.     IF (al = BOC) THEN StartIndex = j ELSE TheChain(al).Pointer = j
  276.  
  277. SUB InsertAfter (a AS LONG, b AS STRING)
  278.     ' Inserts a single cell after address a in the chain.
  279.     j = NextOpenIdentity(a)
  280.     ap = TheChain(a).Pointer
  281.     TheChain(j).Identity = j
  282.     TheChain(j).Pointer = ap
  283.     TheChain(j).Lagger = a
  284.     TheChain(j).Content = ReFormat$(b)
  285.     TheChain(a).Pointer = j
  286.     IF (ap <> EOC) THEN TheChain(ap).Lagger = j
  287.  
  288. SUB InsertRange (a AS LONG, b AS STRING)
  289.     ' Inserts a sub-chain anywhere.
  290.     FOR k = 1 TO LEN(b)
  291.         c$ = MID$(b, k, 1)
  292.         CALL InsertBefore(a, c$)
  293.     NEXT
  294.  
  295. SUB UnlinkCell (a AS LONG)
  296.     ' Remove single cell from chain and clear identity.
  297.     ap = TheChain(a).Pointer
  298.     al = TheChain(a).Lagger
  299.     IF ((ap = EOC) AND (al = BOC)) THEN
  300.         TheChain(a).Content = " "
  301.         'ID1 = a
  302.         'ID2 = ID1
  303.     ELSE
  304.         TheChain(a).Identity = 0
  305.         IF ((ap <> EOC) AND (al <> BOC)) THEN
  306.             TheChain(al).Pointer = ap
  307.             TheChain(ap).Lagger = al
  308.         END IF
  309.         IF (ap = EOC) THEN TheChain(al).Pointer = EOC
  310.         IF (al = BOC) THEN
  311.             StartIndex = ap
  312.             TheChain(ap).Lagger = BOC
  313.         END IF
  314.     END IF
  315.  
  316. SUB UnlinkRange (a AS LONG, b AS LONG)
  317.     ' Remove sub-chain and clear identity of each cell.
  318.     bp = TheChain(b).Pointer
  319.     al = TheChain(a).Lagger
  320.     IF ((al = BOC) AND (bp = EOC)) THEN
  321.         CALL UnlinkRange(NthP(a, 2), b)
  322.         TheChain(a).Content = " "
  323.         TheChain(a).Pointer = bp
  324.     ELSE
  325.         k = a
  326.         DO WHILE ((k <> b) AND (k <> EOC))
  327.             TheChain(k).Identity = 0
  328.             k = TheChain(k).Pointer
  329.         LOOP
  330.         TheChain(b).Identity = 0
  331.         TheChain(bp).Lagger = al
  332.         IF (al = BOC) THEN StartIndex = bp ELSE TheChain(al).Pointer = bp
  333.     END IF
  334.  
  335. FUNCTION LinearCount (a AS LONG, b AS LONG)
  336.     ' Returns number of links between two addresses.
  337.     i = a
  338.     k = 0
  339.     DO WHILE (i <> b)
  340.         k = k + 1
  341.         j = TheChain(i).Identity
  342.         i = TheChain(j).Pointer
  343.         IF (i = EOC) THEN EXIT DO
  344.     LOOP
  345.     LinearCount = k
  346.  
  347. FUNCTION Projection$ (a AS LONG, b AS LONG)
  348.     ' Returns the linear content for all address between a and b, inclusive.
  349.     DIM TheReturn AS STRING
  350.     TheReturn = ""
  351.     IF (a = b) THEN
  352.         TheReturn = TheChain(a).Content
  353.     ELSE
  354.         j = a
  355.         DO
  356.             c$ = TheChain(j).Content
  357.             TheReturn = TheReturn + c$
  358.             k = TheChain(j).Pointer
  359.             IF (j = b) THEN EXIT DO
  360.             IF (k = EOC) THEN EXIT DO
  361.             j = k
  362.         LOOP
  363.     END IF
  364.     Projection$ = TheReturn
  365.  
  366. SUB MapText
  367.     IF (TextFormatting = 1) THEN br$ = "~" ELSE br$ = " "
  368.     j = StartIndex
  369.     i = 1
  370.     q$ = ""
  371.     d$ = ""
  372.     DO ' Begin with any left-over text from previous iteration.
  373.         q$ = d$
  374.         d$ = ""
  375.         r = TextWidth - LEN(q$)
  376.         IF (TextWrapping = 2) THEN r = MemLimit + 1
  377.         k1 = NthP(j, r)
  378.         k2 = NthPC(j, CHR$(13))
  379.         c1 = LinearCount(j, k1)
  380.         c2 = LinearCount(j, k2)
  381.         IF (c2 = 0) THEN ' Line is blank-returned.
  382.             k = k2
  383.             q$ = q$ + br$
  384.             j = NthP(k, 2)
  385.         ELSE
  386.             IF (c1 = c2) THEN ' Possible end of chain.
  387.                 k = TheChain(k1).Lagger
  388.                 q$ = q$ + Projection$(j, k)
  389.                 j = NthP(k, 2)
  390.             END IF
  391.             IF (c1 < c2) THEN ' Width limit case (not always maximum).
  392.                 k = k1
  393.                 q$ = q$ + Projection$(j, k)
  394.                 j = NthP(k, 2)
  395.             END IF
  396.             IF (c1 > c2) THEN ' Break return somewhere in line (not first).
  397.                 k = k2
  398.                 q$ = q$ + Projection$(j, TheChain(k).Lagger) + br$
  399.                 n = TheChain(k).Pointer
  400.                 IF (n <> EOC) THEN j = n
  401.             END IF
  402.         END IF
  403.         IF (TextWrapping = 1) THEN ' Wrap text at first space from right, send remainder to next line.
  404.             IF (LEN(q$) >= TextWidth) THEN
  405.                 FOR m = LEN(q$) TO 1 STEP -1
  406.                     c$ = MID$(q$, m, 1)
  407.                     IF (c$ = " ") OR (c$ = "-") THEN
  408.                         q$ = LEFT$(q$, m)
  409.                         EXIT FOR
  410.                     END IF
  411.                     d$ = c$ + d$
  412.                     IF (m = 1) THEN ' Line is too long for allowed space and contains no wrapping characters.
  413.                         q$ = LEFT$(q$, TextWidth)
  414.                         d$ = ""
  415.                         EXIT FOR
  416.                     END IF
  417.                 NEXT
  418.             END IF
  419.         END IF
  420.         LineAsMapped(i) = q$
  421.         i = i + 1
  422.         IF (i >= TextHeight) THEN EXIT DO
  423.         IF (j = k) THEN EXIT DO
  424.     LOOP
  425.     VisibleLines = i - 1
  426.  
  427. FUNCTION StateChange
  428.     DIM TheReturn AS INTEGER
  429.     TheReturn = 0
  430.     MH = 0
  431.         MH1 = _MOUSEBUTTON(1)
  432.         MH2 = _MOUSEBUTTON(2)
  433.         MH3 = _MOUSEBUTTON(3)
  434.     LOOP
  435.     IF (MH1 = -1) THEN ' Move Cursor1.
  436.         MH = 1
  437.         IF (_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight + 1) THEN
  438.             Cursor1.X = _MOUSEX
  439.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent))
  440.             IF (Cursor1.X > q) THEN Cursor1.X = q
  441.             Cursor1.Y = _MOUSEY
  442.             CALL ReassignID1
  443.         END IF
  444.     END IF
  445.     IF (MH2 = -1) THEN ' Move Cursor2 and copy anything between Cursor1 and Cursor2 to clipboard.
  446.         MH = 1
  447.         IF (_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight + 1) THEN
  448.             Cursor2.X = _MOUSEX
  449.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent))
  450.             IF (Cursor2.X > q) THEN Cursor2.X = q
  451.             Cursor2.Y = _MOUSEY
  452.             CALL ReassignID2
  453.             IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN _CLIPBOARD$ = Projection$(ID1, ID2)
  454.         END IF
  455.     END IF
  456.     IF (MH3 = -1) THEN ' Paste at Cursor1 position.
  457.         MH = 1
  458.         IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN CALL InsertRange(ID1, _CLIPBOARD$)
  459.     END IF
  460.  
  461.     KH = 0
  462.     KH = _KEYHIT
  463.     ' Bksp
  464.     IF (KH = 8) THEN
  465.         r = TheChain(ID1).Pointer
  466.         q = TheChain(ID1).Lagger
  467.         CALL UnlinkCell(ID1)
  468.         IF ((r = EOC) AND (q = BOC)) THEN
  469.         ELSE
  470.             IF (q <> BOC) THEN ID1 = q ELSE ID1 = r
  471.             IF (r = EOC) THEN ID2 = ID1
  472.         END IF
  473.     END IF
  474.     ' Tab
  475.     IF (KH = 9) THEN CALL InsertRange(ID1, "    ")
  476.     ' Esc
  477.     IF (KH = 27) THEN
  478.         IF (ID2 <> ID1) THEN ID2 = ID1 ELSE ID2 = NthL(ID2, MemLimit + 1)
  479.     END IF
  480.     ' Enter, Alphanumerics
  481.     IF (KH = 13) OR ((KH >= 32) AND (KH <= 126)) THEN
  482.         IF (InsertKey = -1) THEN
  483.             IF (ID1 = ID2) THEN
  484.                 CALL InsertBefore(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  485.             ELSE
  486.                 CALL InsertAfter(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  487.                 ID1 = NthP(ID1, 2)
  488.             END IF
  489.         ELSE
  490.             TheChain(ID1).Content = LTRIM$(RTRIM$(CHR$(KH)))
  491.             IF (ID1 = ID2) THEN
  492.                 ID1 = NthP(ID1, 2)
  493.                 ID2 = ID1
  494.             ELSE
  495.                 ID1 = NthP(ID1, 2)
  496.             END IF
  497.         END IF
  498.     END IF
  499.     ' F1
  500.     IF (KH = 15104) THEN
  501.         IF (TextWrapping = 2) THEN
  502.             HScroll = HScroll - 1
  503.             IF (HScroll < 1) THEN HScroll = 1
  504.             CALL ReassignID1
  505.             CALL ReassignID2
  506.         END IF
  507.     END IF
  508.     ' F2
  509.     IF (KH = 15360) THEN
  510.         IF (TextWrapping = 2) THEN
  511.             HScroll = HScroll + 1
  512.             CALL ReassignID1
  513.             CALL ReassignID2
  514.         END IF
  515.     END IF
  516.     ' F5
  517.     IF (KH = 16128) THEN
  518.         q$ = Projection$(NthL(ID1, MemLimit + 1), NthP(ID1, MemLimit + 1))
  519.         Assimilate q$
  520.     END IF
  521.     ' F4
  522.     IF (KH = 15872) THEN
  523.         'CALL InsertRange(NthP(ID2, 2), "=" + SxriptEval$(Projection(ID1, ID2)))
  524.     END IF
  525.     ' F6
  526.     IF (KH = 16384) THEN
  527.         OPEN FileName$ FOR OUTPUT AS #1
  528.         q$ = Projection$(NthL(ID1, MemLimit + 1), NthP(ID1, MemLimit + 1))
  529.         PRINT #1, q$
  530.         CLOSE #1
  531.     END IF
  532.     ' F7
  533.     IF (KH = 16640) THEN
  534.         CALL ConvertToGrid
  535.         CALL GOL
  536.         CALL ConvertFromGrid
  537.     END IF
  538.     ' F8
  539.     IF (KH = 16896) THEN
  540.  
  541.     END IF
  542.     ' Home
  543.     IF (KH = 18176) THEN
  544.         IF (TextWrapping = 2) THEN HScroll = 1
  545.         Cursor1.X = LeftIndent + 1
  546.         CALL ReassignID1
  547.     END IF
  548.     ' UpArrow
  549.     IF (KH = 18432) THEN
  550.         IF (Cursor1.Y > TopIndent + 1) THEN
  551.             Cursor1.Y = Cursor1.Y - 1
  552.         ELSE
  553.             StartIndex = BackBreak(StartIndex)
  554.         END IF
  555.         q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) + 1
  556.         IF (Cursor1.X > q) THEN Cursor1.X = q
  557.         CALL ReassignID1
  558.     END IF
  559.     ' LeftArrow
  560.     IF (KH = 19200) THEN
  561.         ID1 = NthL(ID1, 2)
  562.         IF (TextWrapping = 2) THEN
  563.             IF (Cursor1.X = LeftIndent + 1) THEN
  564.                 IF (HScroll > 1) THEN
  565.                     HScroll = HScroll - 1
  566.                 ELSE
  567.                     j = Cursor1.Y - TopIndent - 1
  568.                     IF (j >= 1) THEN
  569.                         k = LEN(LineAsMapped(j)) - TextWidth + 1
  570.                         IF (k >= 1) THEN
  571.                             HScroll = k
  572.                         END IF
  573.                     END IF
  574.                 END IF
  575.             END IF
  576.         END IF
  577.     END IF
  578.     ' RightArrow
  579.     IF (KH = 19712) THEN
  580.         ID1 = NthP(ID1, 2)
  581.         IF (TextWrapping = 2) THEN
  582.             m = Cursor1.X - LeftIndent
  583.             n = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - HScroll + 1
  584.             IF (m >= TextWidth) THEN
  585.                 HScroll = HScroll + 1
  586.                 CALL ReassignID1
  587.             END IF
  588.             IF (m >= n) THEN
  589.                 j = Cursor1.Y - TopIndent + 1
  590.                 IF (j <= TextHeight) THEN
  591.                     HScroll = 1
  592.                 END IF
  593.             END IF
  594.         END IF
  595.     END IF
  596.     ' End
  597.     IF (KH = 20224) THEN
  598.         Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  599.         CALL ReassignID1
  600.         IF (TextWrapping = 2) THEN
  601.             q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - TextWidth + 1
  602.             IF (q >= 1) THEN HScroll = q
  603.         END IF
  604.     END IF
  605.     ' DownArrow
  606.     IF (KH = 20480) THEN
  607.         IF (Cursor1.Y = TopIndent + VisibleLines) THEN
  608.             IF (VisibleLines > 1) THEN
  609.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  610.                 CALL MapText
  611.             END IF
  612.         ELSE
  613.             Cursor1.Y = Cursor1.Y + 1
  614.         END IF
  615.         q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) + 1
  616.         IF (Cursor1.X > q) THEN
  617.             Cursor1.X = q
  618.         END IF
  619.         CALL ReassignID1
  620.     END IF
  621.     ' Insert
  622.     IF (KH = 20992) THEN
  623.         InsertKey = -InsertKey
  624.     END IF
  625.     ' Del
  626.     IF (KH = 21248) THEN
  627.         IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  628.             r = TheChain(ID2).Pointer
  629.             q = TheChain(ID1).Lagger
  630.             p = ID1
  631.             CALL UnlinkRange(ID1, ID2)
  632.             IF ((r = EOC) AND (q = BOC)) THEN
  633.                 ID1 = p
  634.                 ID2 = ID1
  635.                 StartIndex = p
  636.             ELSE
  637.                 IF (q <> BOC) THEN ID1 = q ELSE ID1 = r
  638.                 ID2 = NthP(ID1, 2)
  639.             END IF
  640.         END IF
  641.     END IF
  642.     ' F11
  643.     IF (KH = 34048) THEN TextFormatting = -TextFormatting
  644.     ' F12
  645.     IF (KH = 34304) THEN
  646.         TextWrapping = TextWrapping + 1
  647.         IF (TextWrapping > 2) THEN TextWrapping = 0
  648.         ID1 = StartIndex
  649.         ID2 = ID1
  650.         HScroll = 1
  651.     END IF
  652.     ' Cursor sync and autoscrolling.
  653.     IF ((MH <> 0) OR (KH > 0)) THEN
  654.         TheReturn = 1
  655.         CALL MapText
  656.         CALL CalibrateCursor(ID1)
  657.         CALL CalibrateCursor(ID2)
  658.         IF (Cursor1.Y > TopIndent + TextHeight - 1) THEN StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  659.     END IF
  660.     StateChange = TheReturn
  661.  
  662. SUB CalibrateCursor (a AS LONG)
  663.     ' Place Cursor under ID on rendered line.
  664.     s = StartIndex
  665.     IF ((TextWrapping = 2) AND (HScroll > 1)) THEN s = NthP(s, HScroll)
  666.     c = LinearCount(s, a)
  667.     k = 0
  668.     FOR j = 1 TO VisibleLines
  669.         n = LEN(LineAsMapped(j))
  670.         IF (k + n < c) THEN
  671.             k = k + n
  672.         ELSE
  673.             i = c - k + 1
  674.             EXIT FOR
  675.         END IF
  676.     NEXT
  677.     IF (i >= LeftIndent + LEN(LineAsMapped(j))) THEN
  678.         IF (j < VisibleLines) THEN
  679.             i = LeftIndent
  680.             j = j + 1
  681.         ELSE
  682.             i = LeftIndent + LEN(LineAsMapped(j))
  683.         END IF
  684.     END IF
  685.     IF (a = ID1) THEN
  686.         Cursor1.X = LeftIndent + i
  687.         Cursor1.Y = TopIndent + j
  688.     END IF
  689.     IF (a = ID2) THEN
  690.         Cursor2.X = LeftIndent + i
  691.         Cursor2.Y = TopIndent + j
  692.     END IF
  693.  
  694. FUNCTION FindID (a AS INTEGER, b AS LONG)
  695.     ' Find identity under a map location.
  696.     RelX = a - LeftIndent
  697.     RelY = b - TopIndent
  698.     FOR k = 1 TO RelY - 1
  699.         t = t + LEN(LineAsMapped(k))
  700.     NEXT
  701.     t = t + RelX
  702.     FindID = t
  703.  
  704. SUB ReassignID1
  705.     ' Reassign identity under Cursor1.
  706.     ID1 = NthP(StartIndex, FindID(Cursor1.X, Cursor1.Y) + (HScroll - 1))
  707.  
  708. SUB ReassignID2
  709.     ' Reassign identity under Cursor2.
  710.     ID2 = NthP(StartIndex, FindID(Cursor2.X, Cursor2.Y) + (HScroll - 1))
  711.  
  712. SUB ConvertToGrid
  713.     FOR j = 1 TO VisibleLines
  714.         c$ = LineAsMapped(j)
  715.         FOR i = 1 TO LEN(c$) - 1 ' BR offset to exclude break return at line end.
  716.             FunGrid(i, j, 1) = MID$(c$, i, 1)
  717.         NEXT
  718.     NEXT
  719.  
  720. SUB ConvertFromGrid
  721.     q$ = ""
  722.     FOR j = 1 TO VisibleLines
  723.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  724.             q$ = q$ + FunGrid(i, j, 1)
  725.         NEXT
  726.         q$ = q$ + CHR$(13) ' Undoes BR offset.
  727.     NEXT
  728.     Assimilate q$
  729.  
  730. SUB GOL
  731.     FOR j = 1 TO VisibleLines
  732.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  733.             c$ = FunGrid(i, j, 1)
  734.             IF (c$ = " ") THEN c$ = "0" ELSE c$ = "1"
  735.             FunGrid(i, j, 1) = c$
  736.             FunGrid(i, j, 2) = c$
  737.         NEXT
  738.     NEXT
  739.     FOR j = 2 TO VisibleLines - 2 ' BR offset.
  740.         FOR i = 2 TO LEN(LineAsMapped(j)) - 2 ' BR offset.
  741.             c$ = FunGrid(i, j, 1)
  742.             a1 = VAL(FunGrid(i - 1, j + 1, 1))
  743.             a2 = VAL(FunGrid(i, j + 1, 1))
  744.             a3 = VAL(FunGrid(i + 1, j + 1, 1))
  745.             a4 = VAL(FunGrid(i - 1, j, 1))
  746.             a6 = VAL(FunGrid(i + 1, j, 1))
  747.             a7 = VAL(FunGrid(i - 1, j - 1, 1))
  748.             a8 = VAL(FunGrid(i, j - 1, 1))
  749.             a9 = VAL(FunGrid(i + 1, j - 1, 1))
  750.             t = a1 + a2 + a3 + a4 + a6 + a7 + a8 + a9
  751.             IF (c$ = "1") THEN
  752.                 SELECT CASE t
  753.                     CASE IS < 2
  754.                         FunGrid(i, j, 2) = "0"
  755.                     CASE 2
  756.                         FunGrid(i, j, 2) = "1"
  757.                     CASE 3
  758.                         FunGrid(i, j, 2) = "1"
  759.                     CASE IS > 3
  760.                         FunGrid(i, j, 2) = "0"
  761.                 END SELECT
  762.             ELSE
  763.                 IF (t = 3) THEN FunGrid(i, j, 2) = "1"
  764.             END IF
  765.         NEXT
  766.     NEXT
  767.     FOR j = 1 TO VisibleLines
  768.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  769.             c$ = FunGrid(i, j, 2)
  770.             IF (c$ = "0") THEN c$ = " " ELSE c$ = CHR$(219)
  771.             FunGrid(i, j, 1) = c$
  772.             FunGrid(i, j, 2) = c$
  773.         NEXT
  774.     NEXT
  775.  
Title: Re: qXed: linked list text editor
Post by: bplus on September 30, 2018, 09:43:03 am
Well that is one very interesting way to go with an editor!
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on October 06, 2018, 02:08:25 pm
Heya all,

Made a pretty fat update that is best kept at the top. I direct you here for the latest: https://www.qb64.org/forum/index.php?topic=597 (https://www.qb64.org/forum/index.php?topic=597)
Title: Re: qXed: linked list text editor
Post by: STxAxTIC on October 08, 2018, 11:19:38 am
Heya folks,

I want to take some people back to a painful memory of 2014, following the IRC bot wars. In that struggle, I attempted to create an end-all SCREEN 0 program that would perform so many essential functions that a person would never want to close it. In certain ways I succeeded back then, as my go-to IRC client for over a year was an instance of the handmade program to which I'm alluding, the notorious MathBlab. You could do anything but make toast - go online, play poker, evaluate LISP expressions a la qbguy, yada yada - all that stuff. The thing still works, but I'm never pasting the code again under my own name. The guys at #qb64 on irc.freenode.net are still traumatized by the whole thing (mainly the ability to public test Eliza the therapist). See below for a screenshot.

Years later I find my projects at a similar collision point, but the pieces are not as crappy this time. The Sxript thing has gone through a total rewrite since then and is well-trusted, all essential web stuff (for my purposes) has been abstracted into Sprezzo. With a universal SCREEN 0 slate like qXed, I can see these coming together to form a QB64 approximation of emacs. See the second screenshot (my awesome paint skills). (There is one connection missing, and that is between Fellippe's InForm and a general way to handle text. That is being cooked behind the scenes - give it a few weeks or months.)

As for the connections that are marked, you can see why we try to make programs that interact nicely with other ones. Certain combinations give calculators, messaging apps, you see the idea. When three lines collide however, it's time for a new node. The square in the middle sounds suspiciously like the old MathBlab program. So... I have no choice. Expect a web-enabled does-lots-of-screen-zero-stuff program to emerge on this thread next.