Author Topic: qXed: fairly-polished text editor  (Read 4944 times)

0 Members and 1 Guest are viewing this topic.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
qXed: fairly-polished text editor
« on: August 21, 2019, 10:56:04 am »
Hello all,

Some of you remember the qXed project from almost a year ago, and real veterans know what the X stands for - but we aren't digging up ancient history here. I did a small bit of streamlining on the text editor I started, and figure there's no better time to familiarize everyone with its quirks and thereby trigger a crusade where bplus and/or Steve venture to code up a Notepad clone - but I digress. Really this post is to prototype a Samples entry for this code.

So I bring you qXed without any extras - you can open, edit and save text. Right now the file size limits to 128000 characters for no good reason. The code box below needs no extras, you just compile and run. Drag your own file onto the compiled exe if you don't want to read Terence Mckenna.

The controls are not precisely trivial, but a far easier learning curve than vim or emacs. So here goes... In alphabetical order...

Copy/Paste
Text that is highlighted by right-clicking (See Cursors) is copied to the clipboard. Clicking Mouse 3 will paste the clipboard contents into the text before Cursor 1. Press F3 to highlight and copy the row occupied by Cursor 1.

Cursors
A pair of "block" cursors are used to navigate and influence text. Left-clicking places Cursor 1 (teal), and right-clicking places Cursor 2 (brown). Cursor 2 is only visible and functional when placed after Cursor 1 (otherwise it vanishes). The text between Cursor 1 and Cursor 2 is considered highlighted. Cursor 2 does not move while editing text or scrolling, while Cursor 1 is the main "pointer" that stays in view at all times.

Cursor Overlap / Mode
When Cursor 1 and Cursor 2 highlight the same character, the color turns to violet, and the insert mode inverts. That is, new keyboard input will appear to the left of violet cursor(s), otherwise new input appears to the right. The Esc key moves Cursor 2 alternatively between the Cursor 1 position and the first character in the file, and thus acts as a "mode" key.

Formatting
There are no hidden or implied text formatting options or symbols: only alphanumeric characters and break returns are allowed. Tabs are translated into spaces. By toggling the F11 key, break returns and whitespace are shown using the tilde (~) and underscore (_) symbols, respectively.

Navigation
Cursors are confined to locate within the body of the text - only the mouse pointer can land in the meaningless black space where no text yet exists. Cursor 1 can be moved respectively using arrow keys to move through rows and columns. To scroll larger distances, the PgUp and PgDn keys bring Cursor 1 up/down several rows. In a given row, the Home and End keys move Cursor 1 to the beginning or end of a row, respectively. The mouse wheel is presently coded to imitate the up/down arrow keys. For unwrapped text with a horizontal span great than the screen width, the F1 ad F2 keys scroll left and right, respectively.

Save/Load
The F6 key saves the present text body to disk using a unique filename. A text file can be loaded by dragging it onto the compiled executable.

Scrollbars
The bottom- and right-edges of the window contain bookmarks that track Cursor 1 within the text body. The horizontal bookmark's position in the window is proportional to the Cursor's horizontal position in the present row. (I call it Zeno scrolling.)

Wrapping
There are three text wrapping modes: square, fluid, and none. Pressing F12 toggles between each mode.

Code: QB64: [Select]
  1. '$EXEICON:'qXedlogo.ico'
  2. '$RESIZE:ON
  3.  
  4. 'REM $Include: 'sxmath.bi'
  5. 'REM $Include: 'sxript.bi'
  6.  
  7. scrHand& = _NEWIMAGE(90, 30, 0)
  8. SCREEN scrHand&
  9. '_FREEIMAGE scrHand&
  10.  
  11. ' Define fundamental structures.
  12. TYPE Vector
  13.     X AS INTEGER
  14.     Y AS INTEGER
  15.  
  16. TYPE Cell
  17.     Identity AS LONG
  18.     Pointer AS LONG
  19.     Lagger AS LONG
  20.     Content AS STRING * 1
  21.  
  22. DIM SHARED ChainLimit AS LONG
  23. DIM SHARED BOC AS LONG ' Beginning of chain.
  24. DIM SHARED EOC AS LONG ' End of chain.
  25. ChainLimit = 128000
  26. BOC = -1
  27. EOC = ChainLimit
  28.  
  29. ' Define text window properties.
  30. DIM SHARED WindowHeight
  31. DIM SHARED WindowWidth
  32. DIM SHARED VisibleLines
  33. DIM SHARED TopIndent
  34. DIM SHARED LeftIndent
  35. DIM SHARED TextHeight
  36. DIM SHARED TextWidth
  37. DIM SHARED HScroll
  38. DIM SHARED TextWrapping
  39. DIM SHARED TextFormatting
  40. DIM SHARED InsertKey
  41. TopIndent = 1
  42. LeftIndent = 1
  43. WindowWidth = _WIDTH
  44. WindowHeight = _HEIGHT
  45. TextHeight = WindowHeight - 2 * TopIndent
  46. TextWidth = WindowWidth - 2 * LeftIndent
  47. HScroll = 1
  48. TextWrapping = 1
  49. TextFormatting = -1
  50. InsertKey = -1
  51.  
  52. ' Initiate text inside window.
  53. DIM SHARED StartIndex
  54. DIM SHARED LineAsMapped(TextHeight) AS STRING
  55. DIM SHARED Cursor1 AS Vector
  56. DIM SHARED Cursor2 AS Vector
  57.  
  58. ' Auxiliary 2D text grid.
  59. DIM SHARED GOLSwitch
  60. DIM SHARED AuxGrid(TextWidth, TextHeight, 2) AS STRING
  61. GOLSwitch = -1
  62.  
  63. ' Load text file into memory if applicable, use example string if not.
  64. DIM SHARED FileName$
  65. IF (c$ <> "") THEN
  66.     q$ = ""
  67.     OPEN c$ FOR INPUT AS #1
  68.     DO WHILE NOT EOF(1)
  69.         LINE INPUT #1, r$
  70.         q$ = q$ + r$ + CHR$(13)
  71.     LOOP
  72.     CLOSE #1
  73.     i = INSTR(c$, ".")
  74.     IF (i <> 0) THEN j = i - 1 ELSE j = LEN(c$)
  75.     FileName$ = LEFT$(c$, j) + "-" + LTRIM$(RTRIM$(STR$(INT(TIMER)))) + ".txt"
  76.     _TITLE "qXed" + " (" + FileName$ + ")"
  77.     FileName$ = "Newfile" + "-" + DATE$ + "-" + LTRIM$(RTRIM$(STR$(INT(TIMER)))) + ".txt"
  78.     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."
  79.     'q$ = " "
  80.     _TITLE "qXed"
  81.  
  82. ' Create memory space for string.
  83. DIM SHARED TheChain(ChainLimit) AS Cell
  84.  
  85. ' Create character list.
  86. CALL Assimilate(q$)
  87.  
  88. ' Prime main loop.
  89. CALL MapText
  90. CALL CalibrateCursor(ID1)
  91. CALL CalibrateCursor(ID2)
  92. CALL PrintEverything
  93.  
  94. DIM SHARED DEBUG$
  95.  
  96. ' Main loop.
  97.     IF (StateChange = 1) THEN
  98.         CALL PrintEverything
  99.     END IF
  100.  
  101.     IF (GOLSwitch = 1) THEN
  102.         CALL ConvertToGrid
  103.         CALL GOL
  104.         CALL ConvertFromGrid
  105.         CALL MapText
  106.         CALL PrintEverything
  107.     END IF
  108.  
  109.     'IF _RESIZE = -1 THEN
  110.     '    _DELAY .1
  111.     '    oldimage& = scrHand&
  112.     '    scrHand& = _NEWIMAGE(_RESIZEWIDTH / 8, _RESIZEHEIGHT / 16, 0)
  113.     '    SCREEN scrHand&
  114.     '    _FREEIMAGE oldimage&
  115.     '    WindowWidth = INT(_RESIZEWIDTH / 8)
  116.     '    WindowHeight = INT(_RESIZEHEIGHT / 16)
  117.     '    TextHeight = WindowHeight - 2 * TopIndent
  118.     '    TextWidth = WindowWidth - 2 * LeftIndent
  119.     '    REDIM LineAsMapped(TextHeight)
  120.     '    REDIM AuxGrid(TextWidth, TextHeight, 2)
  121.     '    CALL MapText
  122.     '    CALL CalibrateCursor(ID1)
  123.     '    CALL CalibrateCursor(ID2)
  124.     '    CALL PrintEverything
  125.     'END IF
  126.  
  127.     _DISPLAY
  128.     _LIMIT 120
  129.  
  130. SUB PrintEverything
  131.     CLS
  132.  
  133.     ' Main text
  134.     FOR i = 1 TO VisibleLines
  135.         c$ = LineAsMapped(i)
  136.         'IF ((TextFormatting = 1) AND (TextWrapping <> 2)) THEN
  137.         IF (TextFormatting = 1) THEN
  138.             FOR j = 1 TO TextWidth - LEN(c$)
  139.                 c$ = c$ + "_"
  140.             NEXT
  141.         END IF
  142.         d$ = MID$(c$, HScroll, TextWidth)
  143.         CALL DisplayText(LeftIndent + 1, TopIndent + i, 7, 1, d$)
  144.     NEXT
  145.  
  146.     ' Horizontal scrollbar
  147.     p = LinearCount(NthP(StartIndex, FindID(LeftIndent + 1, Cursor1.Y)), ID1)
  148.     q = LEN(LineAsMapped(Cursor1.Y - TopIndent))
  149.     r = p / (q + 1)
  150.     IF r > 1 THEN r = 1
  151.     IF r < 0 THEN r = 0
  152.     i = 2 + INT(r * (WindowWidth - 1))
  153.     IF i < 1 THEN i = 1
  154.     IF i >= WindowWidth THEN i = WindowWidth
  155.     CALL DisplayText(i, WindowHeight - 1, 8, 7, "^")
  156.  
  157.     ' Vertical scrollbar
  158.     p = LinearCount(ID1, NthP(ID1, ChainLimit + 1))
  159.     q = LinearCount(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  160.     IF (q = 0) THEN r = 1 ELSE r = 1 - p / q
  161.     CALL DisplayText(WindowWidth, 1 + INT(r * (WindowHeight - 1)), 8, 7, "<")
  162.  
  163.     ' Mouse pointer
  164.     IF ((_MOUSEX >= 1) AND (_MOUSEX <= WindowWidth) AND (_MOUSEY >= 1) AND (_MOUSEY <= WindowHeight)) THEN
  165.         a$ = CHR$(SCREEN(_MOUSEY, _MOUSEX))
  166.         CALL DisplayText(_MOUSEX, _MOUSEY, 0, 15, a$)
  167.     END IF
  168.  
  169.     ' Cursor2
  170.     IF ((Cursor2.X > 0 AND Cursor2.X < WindowWidth) AND ((Cursor2.Y > 0) AND (Cursor2.Y < WindowHeight))) THEN
  171.         p1 = LinearCount(StartIndex, ID1)
  172.         p2 = LinearCount(StartIndex, ID2)
  173.         pe = LinearCount(StartIndex, EOC)
  174.         IF ((p2 > p1) AND (p2 < pe)) THEN
  175.             c$ = TheChain(ID2).Content
  176.             IF (c$ = " ") THEN c$ = "_"
  177.             IF (c$ = CHR$(13)) THEN c$ = "~"
  178.             CALL DisplayText(Cursor2.X, Cursor2.Y, 0, 6, c$)
  179.         END IF
  180.     END IF
  181.  
  182.     ' Cursor1
  183.     IF ((Cursor1.X > 0 AND Cursor1.X < WindowWidth) AND ((Cursor1.Y > 0) AND (Cursor1.Y < WindowHeight))) THEN
  184.         c$ = TheChain(ID1).Content
  185.         IF (c$ = " ") THEN c$ = "_"
  186.         IF (c$ = CHR$(13)) THEN c$ = "~"
  187.         IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN
  188.             a = 16: b = 5
  189.         ELSE
  190.             a = 16: b = 3
  191.         END IF
  192.         CALL DisplayText(Cursor1.X, Cursor1.Y, a, b, c$)
  193.     END IF
  194.  
  195.     ' Guide
  196.     SELECT CASE TextWrapping
  197.         CASE 0: d$ = "Square"
  198.         CASE 1: d$ = "Fluid"
  199.         CASE 2: d$ = "None"
  200.     END SELECT
  201.     c$ = "[F3=Highlight Row] [F6=Save] [F11=Format] [F12=Wrap: " + d$ + "]"
  202.     IF (TextWrapping = 2) THEN c$ = "[F1/2=HScroll] " + c$
  203.     c$ = c$ + STR$(INT(100 * r)) + "%"
  204.     IF (LEN(c$) >= TextWidth) THEN c$ = LEFT$(c$, TextWidth)
  205.     CALL DisplayText(WindowWidth - LEN(c$), 1, 15, 0, c$)
  206.     c$ = "[Esc=Mode] [M1=Cur1] [M2=Cur2/Copy] [M3=Paste] [Del=Erase Cur1-2]"
  207.     IF (InsertKey = 1) THEN c$ = "[Ins] " + c$
  208.     IF (LEN(c$) >= TextWidth) THEN c$ = LEFT$(c$, TextWidth)
  209.     CALL DisplayText(WindowWidth - LEN(c$), WindowHeight, 15, 0, c$)
  210.  
  211.     ' Cursor status
  212.     d$ = TheChain(ID1).Content
  213.     e$ = TheChain(ID2).Content
  214.     IF ((ASC(d$) = 10) OR (ASC(d$) = 13)) THEN d$ = "~"
  215.     IF ((ASC(e$) = 10) OR (ASC(e$) = 13)) THEN e$ = "~"
  216.     IF (ASC(d$) = 32) THEN d$ = "_"
  217.     IF (ASC(e$) = 32) THEN e$ = "_"
  218.  
  219.     c$ = "(" + LTRIM$(RTRIM$(STR$(Cursor1.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor1.Y - TopIndent))) + ")" ':" + " " + LTRIM$(RTRIM$(d$)) + " " + LTRIM$(RTRIM$(STR$(ID1))) + ")"
  220.     IF ((Cursor1.X = Cursor2.X) AND (Cursor1.Y = Cursor2.Y)) THEN
  221.         CALL DisplayText(2, WindowHeight, 15, 5, c$)
  222.         CALL DisplayText(3 + LEN(c$) + LEN(g$), WindowHeight, 15, 5, "<" + CHR$(177))
  223.     ELSE
  224.         CALL DisplayText(2, WindowHeight, 0, 3, c$)
  225.         CALL DisplayText(3 + LEN(c$) + LEN(g$), WindowHeight, 0, 3, CHR$(177) + ">")
  226.     END IF
  227.     IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  228.         g$ = "(" + LTRIM$(RTRIM$(STR$(Cursor2.X - LeftIndent))) + "," + LTRIM$(RTRIM$(STR$(Cursor2.Y - TopIndent))) + ")" ':" + " " + LTRIM$(RTRIM$(e$)) + " " + LTRIM$(RTRIM$(STR$(ID2))) + ")"
  229.         CALL DisplayText(6 + LEN(c$), WindowHeight, 0, 6, g$)
  230.     END IF
  231.  
  232.     DEBUG$ = STR$(ID1) + STR$(ID2)
  233.     c$ = "qXed" + " " + DEBUG$
  234.     CALL DisplayText(1, 1, 11, 0, c$)
  235.  
  236.     COLOR 15, 0
  237.  
  238.  
  239. SUB Assimilate (a AS STRING)
  240.     ' Load a string to initialize chain.
  241.     FOR k = 1 TO ChainLimit
  242.         TheChain(k).Identity = 0
  243.     NEXT
  244.     StartIndex = 1
  245.     PreviousIdentity = BOC
  246.     NextIdentity = NextOpenIdentity(StartIndex)
  247.     FOR k = 1 TO LEN(a)
  248.         j = NextIdentity
  249.         TheChain(j).Identity = j
  250.         TheChain(j).Content = ReFormat$(MID$(a, k, 1))
  251.         TheChain(j).Lagger = PreviousIdentity
  252.         PreviousIdentity = j
  253.         IF (k < LEN(a)) THEN
  254.             NextIdentity = NextOpenIdentity(j)
  255.             TheChain(j).Pointer = NextIdentity
  256.         ELSE
  257.             TheChain(j).Pointer = EOC
  258.         END IF
  259.         PRINT TheChain(j).Content
  260.     NEXT
  261.     ID1 = StartIndex
  262.     ID2 = ID1 'NthP(ID1, ChainLimit + 1)
  263.  
  264. FUNCTION ReFormat$ (a AS STRING)
  265.     c$ = a
  266.     IF c$ = CHR$(10) THEN c$ = CHR$(13)
  267.     IF c$ = CHR$(9) THEN c$ = "    "
  268.     ReFormat = c$
  269.  
  270. FUNCTION NthP (a AS LONG, b AS LONG)
  271.     ' Returns the address that is b jumps ahead of address a.
  272.     i = a
  273.     IF (i <> EOC) THEN
  274.         k = 0
  275.         j = 0
  276.         DO WHILE (k < b)
  277.             k = k + 1
  278.             j = TheChain(i).Identity
  279.             i = TheChain(j).Pointer
  280.             IF (i = EOC) THEN EXIT DO
  281.         LOOP
  282.     END IF
  283.     NthP = j
  284.  
  285. FUNCTION NthPC (a AS LONG, b AS STRING)
  286.     ' Returns the address holding b first enLinearCountered from a.
  287.     i = a
  288.     DO
  289.         j = TheChain(i).Identity
  290.         i = TheChain(j).Pointer
  291.         IF (TheChain(j).Content = b) THEN EXIT DO
  292.         IF (i = EOC) THEN
  293.             j = BOC
  294.             EXIT DO
  295.         END IF
  296.     LOOP
  297.     NthPC = j
  298.  
  299. FUNCTION NthL (a AS LONG, b AS LONG)
  300.     ' Returns the address that is b jumps behind address a.
  301.     i = a
  302.     k = 0
  303.     DO WHILE k < b
  304.         k = k + 1
  305.         j = TheChain(i).Identity
  306.         i = TheChain(j).Lagger
  307.         IF (i = BOC) THEN EXIT DO
  308.     LOOP
  309.     NthL = j
  310.  
  311. FUNCTION NextOpenIdentity (a AS LONG)
  312.     ' Returns first nonzero identity.
  313.     FOR j = a TO ChainLimit
  314.         IF (TheChain(j).Identity = 0) THEN EXIT FOR
  315.     NEXT
  316.     IF (j > ChainLimit) THEN
  317.         PRINT "Out of memory: "; ChainLimit
  318.         SLEEP
  319.         SYSTEM
  320.     END IF
  321.     NextOpenIdentity = j
  322.  
  323. FUNCTION BackBreak (a AS LONG)
  324.     ' Function for scrolling up.
  325.     j = a
  326.     lastbreak = 0
  327.     c$ = ""
  328.     DO
  329.         IF (j = BOC) THEN EXIT DO
  330.         k = TheChain(j).Lagger
  331.         IF (k = BOC) THEN
  332.             lastbreak = j
  333.             EXIT DO
  334.         END IF
  335.         j = k
  336.         d$ = TheChain(j).Content
  337.         c$ = d$ + c$
  338.         IF ((TextWrapping = 1) AND (d$ = " ")) THEN lastbreak = j
  339.         IF (TextWrapping <> 2) AND (LEN(c$) = TextWidth) THEN EXIT DO
  340.         IF (d$ = CHR$(13)) THEN EXIT DO
  341.     LOOP
  342.     IF (lastbreak <> 0) THEN j = TheChain(lastbreak).Identity
  343.     BackBreak = j
  344.  
  345. SUB InsertBefore (a AS LONG, b AS STRING)
  346.     ' Inserts a single cell before address a in the chain.
  347.     j = NextOpenIdentity(a)
  348.     al = TheChain(a).Lagger
  349.     TheChain(j).Identity = j
  350.     TheChain(j).Pointer = a
  351.     TheChain(j).Lagger = al
  352.     TheChain(j).Content = ReFormat$(b)
  353.     TheChain(a).Lagger = j
  354.     IF (al = BOC) THEN StartIndex = j ELSE TheChain(al).Pointer = j
  355.  
  356. SUB InsertAfter (a AS LONG, b AS STRING)
  357.     ' Inserts a single cell after address a in the chain.
  358.     j = NextOpenIdentity(a)
  359.     ap = TheChain(a).Pointer
  360.     TheChain(j).Identity = j
  361.     TheChain(j).Pointer = ap
  362.     TheChain(j).Lagger = a
  363.     TheChain(j).Content = ReFormat$(b)
  364.     TheChain(a).Pointer = j
  365.     IF (ap <> EOC) THEN TheChain(ap).Lagger = j
  366.  
  367. SUB InsertRange (a AS LONG, b AS STRING)
  368.     ' Inserts a sub-chain anywhere.
  369.     FOR k = 1 TO LEN(b)
  370.         c$ = MID$(b, k, 1)
  371.         CALL InsertBefore(a, c$)
  372.     NEXT
  373.  
  374. SUB UnlinkCell (a AS LONG)
  375.     ' Remove single cell from chain and clear identity.
  376.     ap = TheChain(a).Pointer
  377.     al = TheChain(a).Lagger
  378.     IF ((ap = EOC) AND (al = BOC)) THEN
  379.         TheChain(a).Content = " "
  380.         'ID1 = a
  381.         'ID2 = ID1
  382.     ELSE
  383.         TheChain(a).Identity = 0
  384.         IF ((ap <> EOC) AND (al <> BOC)) THEN
  385.             TheChain(al).Pointer = ap
  386.             TheChain(ap).Lagger = al
  387.         END IF
  388.         IF (ap = EOC) THEN TheChain(al).Pointer = EOC
  389.         IF (al = BOC) THEN
  390.             StartIndex = ap
  391.             TheChain(ap).Lagger = BOC
  392.         END IF
  393.     END IF
  394.  
  395. SUB UnlinkRange (a AS LONG, b AS LONG)
  396.     ' Remove sub-chain and clear identity of each cell.
  397.     bp = TheChain(b).Pointer
  398.     al = TheChain(a).Lagger
  399.     IF ((al = BOC) AND (bp = EOC)) THEN
  400.         CALL UnlinkRange(NthP(a, 2), b)
  401.         TheChain(a).Content = " "
  402.         TheChain(a).Pointer = bp
  403.     ELSE
  404.         k = a
  405.         DO WHILE ((k <> b) AND (k <> EOC))
  406.             TheChain(k).Identity = 0
  407.             k = TheChain(k).Pointer
  408.         LOOP
  409.         TheChain(b).Identity = 0
  410.         TheChain(bp).Lagger = al
  411.         IF (al = BOC) THEN StartIndex = bp ELSE TheChain(al).Pointer = bp
  412.     END IF
  413.  
  414. FUNCTION LinearCount (a AS LONG, b AS LONG)
  415.     ' Returns number of links between two addresses.
  416.     i = a
  417.     k = 0
  418.     DO WHILE (i <> b)
  419.         k = k + 1
  420.         j = TheChain(i).Identity
  421.         i = TheChain(j).Pointer
  422.         IF (i = EOC) THEN EXIT DO
  423.     LOOP
  424.     LinearCount = k
  425.  
  426. FUNCTION LinearCount2 (a AS LONG, b AS LONG, c AS LONG)
  427.     ' Returns number of links between two addresses, with exit condition.
  428.     i = a
  429.     k = 0
  430.     DO WHILE (i <> b)
  431.         k = k + 1
  432.         j = TheChain(i).Identity
  433.         i = TheChain(j).Pointer
  434.         IF (i = EOC) THEN EXIT DO
  435.         IF (k = c) THEN EXIT DO
  436.     LOOP
  437.     LinearCount2 = k
  438.  
  439. FUNCTION Projection$ (a AS LONG, b AS LONG)
  440.     ' Returns the linear content for all address between a and b, inclusive.
  441.     DIM TheReturn AS STRING
  442.     TheReturn = ""
  443.     IF (a = b) THEN
  444.         TheReturn = TheChain(a).Content
  445.     ELSE
  446.         j = a
  447.         DO
  448.             c$ = TheChain(j).Content
  449.             TheReturn = TheReturn + c$
  450.             k = TheChain(j).Pointer
  451.             IF (j = b) THEN EXIT DO
  452.             IF (k = EOC) THEN EXIT DO
  453.             j = k
  454.         LOOP
  455.     END IF
  456.     Projection$ = TheReturn
  457.  
  458. SUB MapText
  459.     IF (TextFormatting = 1) THEN br$ = "~" ELSE br$ = " "
  460.     j = StartIndex
  461.     i = 1
  462.     q$ = ""
  463.     d$ = ""
  464.     DO ' Begin with any left-over text from previous iteration.
  465.         q$ = d$
  466.         d$ = ""
  467.         r = TextWidth - LEN(q$)
  468.         IF (TextWrapping <> 2) THEN k1 = NthP(j, r) ELSE k1 = EOC
  469.         k2 = NthPC(j, CHR$(13))
  470.         IF (TextWrapping <> 2) THEN c1 = LinearCount(j, k1) ELSE c1 = LinearCount2(j, k1, TextWidth * TextHeight)
  471.         c2 = LinearCount(j, k2)
  472.         IF (c2 = 0) THEN ' Line is blank-returned.
  473.             k = k2
  474.             q$ = q$ + br$
  475.             j = NthP(k, 2)
  476.         ELSE
  477.             IF (c1 = c2) THEN ' Possible end of chain.
  478.                 k = TheChain(k1).Lagger
  479.                 q$ = q$ + Projection$(j, k)
  480.                 j = NthP(k, 2)
  481.             END IF
  482.             IF (c1 < c2) THEN ' Width limit case (not always maximum).
  483.                 k = k1
  484.                 q$ = q$ + Projection$(j, k)
  485.                 j = NthP(k, 2)
  486.             END IF
  487.             IF (c1 > c2) THEN ' Break return somewhere in line (not first).
  488.                 k = k2
  489.                 q$ = q$ + Projection$(j, TheChain(k).Lagger) + br$
  490.                 n = TheChain(k).Pointer
  491.                 IF (n <> EOC) THEN j = n
  492.             END IF
  493.         END IF
  494.         IF (TextWrapping = 1) THEN ' Wrap text at first space from right, send remainder to next line.
  495.             IF (LEN(q$) >= TextWidth) THEN
  496.                 FOR m = LEN(q$) TO 1 STEP -1
  497.                     c$ = MID$(q$, m, 1)
  498.                     IF (c$ = " ") OR (c$ = "-") THEN
  499.                         q$ = LEFT$(q$, m)
  500.                         EXIT FOR
  501.                     END IF
  502.                     d$ = c$ + d$
  503.                     IF (m = 1) THEN ' Line is too long for allowed space and contains no wrapping characters.
  504.                         q$ = LEFT$(q$, TextWidth)
  505.                         d$ = ""
  506.                         EXIT FOR
  507.                     END IF
  508.                 NEXT
  509.             END IF
  510.         END IF
  511.         LineAsMapped(i) = q$
  512.         i = i + 1
  513.         IF n = EOC THEN EXIT DO
  514.         IF (i >= TextHeight) THEN EXIT DO
  515.         IF (j = k) THEN EXIT DO
  516.     LOOP
  517.     VisibleLines = i - 1
  518.  
  519. FUNCTION StateChange
  520.     DIM TheReturn
  521.     MH = 0
  522.     MW = 0
  523.     MT = 0
  524.         MH1 = _MOUSEBUTTON(1)
  525.         MH2 = _MOUSEBUTTON(2)
  526.         MH3 = _MOUSEBUTTON(3)
  527.         MW = _MOUSEWHEEL
  528.         IF (MW <> 0) THEN MT = MW
  529.     LOOP
  530.     MW = MT
  531.  
  532.     IF (MH1 = -1) THEN
  533.         ' Move Cursor1 among text.
  534.         MH = 1
  535.         IF ((_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight)) THEN
  536.             Cursor1.X = _MOUSEX
  537.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent)) - (HScroll - 1)
  538.             IF (Cursor1.X > q) THEN
  539.                 IF (q < 0) THEN
  540.                     IF (LEN(LineAsMapped(_MOUSEY - TopIndent)) > TextWidth) THEN
  541.                         HScroll = 1 + LEN(LineAsMapped(_MOUSEY - TopIndent)) - (Cursor1.X - LeftIndent)
  542.                     ELSE
  543.                         HScroll = 1
  544.                         Cursor1.X = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent))
  545.                     END IF
  546.                 ELSE
  547.                     Cursor1.X = q
  548.                 END IF
  549.             END IF
  550.             Cursor1.Y = _MOUSEY
  551.             CALL ReassignID1
  552.         END IF
  553.         ' Move by vertical scrollbar.
  554.         IF (_MOUSEX = WindowWidth) THEN
  555.             i = NthL(ID1, ChainLimit + 1)
  556.             j = NthP(ID1, ChainLimit + 1)
  557.             IF (_MOUSEY = WindowHeight) THEN i = j
  558.             IF (_MOUSEY > 1) AND (_MOUSEY < WindowHeight) THEN
  559.                 t = LinearCount(i, j)
  560.                 f = _MOUSEY / WindowHeight
  561.                 FOR k = 1 TO t
  562.                     IF (k / t >= f) THEN EXIT FOR
  563.                     i = TheChain(i).Pointer
  564.                 NEXT
  565.             END IF
  566.             StartIndex = i
  567.             ID1 = i
  568.         END IF
  569.         ' Move by horizontal scrollbar.
  570.         IF (_MOUSEY = WindowHeight - 1) THEN
  571.             j = ID1
  572.             i = NthP(StartIndex, FindID(LeftIndent + 1, Cursor1.Y))
  573.             IF (_MOUSEX > 1) AND (_MOUSEX < WindowWidth) THEN
  574.                 t = LEN(LineAsMapped(Cursor1.Y - TopIndent))
  575.                 f = _MOUSEX / WindowWidth
  576.                 FOR k = 1 TO t
  577.                     IF (k / t >= f) THEN EXIT FOR
  578.                     i = TheChain(i).Pointer
  579.                 NEXT
  580.             END IF
  581.             ID1 = i
  582.             d = LinearCount(StartIndex, i) - LinearCount(StartIndex, j)
  583.             IF (TextWrapping = 2) THEN HScroll = HScroll + d
  584.             IF HScroll < 1 THEN HScroll = 1
  585.         END IF
  586.     END IF
  587.     IF (MH2 = -1) THEN
  588.         ' Move Cursor2 and copy anything between Cursor1 and Cursor2 to clipboard.
  589.         MH = 1
  590.         IF (_MOUSEX > LeftIndent) AND (_MOUSEX < TextWidth + LeftIndent + 1) AND (_MOUSEY > TopIndent) AND (_MOUSEY < TopIndent + TextHeight + 1) THEN
  591.             Cursor2.X = _MOUSEX
  592.             q = LeftIndent + LEN(LineAsMapped(_MOUSEY - TopIndent)) - (HScroll - 1)
  593.             IF (Cursor2.X > q) THEN
  594.                 IF (q < 0) THEN
  595.                 ELSE
  596.                     Cursor2.X = q
  597.                 END IF
  598.             END IF
  599.             Cursor2.Y = _MOUSEY
  600.             CALL ReassignID2
  601.             IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN _CLIPBOARD$ = Projection$(ID1, ID2)
  602.         END IF
  603.     END IF
  604.     IF (MH3 = -1) THEN
  605.         ' Paste at Cursor1 position.
  606.         MH = 1
  607.         'IF (LinearCount(StartIndex, ID2) >= LinearCount(StartIndex, ID1)) THEN
  608.         CALL InsertRange(ID1, _CLIPBOARD$)
  609.     END IF
  610.     IF (MW = -1) THEN
  611.         ' Wheel up
  612.         MH = 1
  613.  
  614.  
  615.         'StartIndex = BackBreak(StartIndex)
  616.         'CALL ReassignID1
  617.  
  618.         ' Borrowed from uparrow.
  619.         IF (Cursor1.Y > TopIndent + 1) THEN
  620.             Cursor1.Y = Cursor1.Y - 1
  621.         ELSE
  622.             StartIndex = BackBreak(StartIndex)
  623.         END IF
  624.         q = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (HScroll - 1)
  625.         IF (Cursor1.X > q) THEN
  626.             IF (q < 0) THEN
  627.                 IF (LEN(LineAsMapped(Cursor1.Y - TopIndent)) > TextWidth) THEN
  628.                     HScroll = 1 + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (Cursor1.X - LeftIndent)
  629.                 ELSE
  630.                     HScroll = 1
  631.                     Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  632.                 END IF
  633.             ELSE
  634.                 Cursor1.X = q
  635.             END IF
  636.         END IF
  637.         CALL ReassignID1
  638.  
  639.  
  640.     END IF
  641.     IF (MW = 1) THEN
  642.         ' Wheel down
  643.         MH = 1
  644.  
  645.         'IF (VisibleLines > 1) THEN
  646.         '    StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  647.         '    CALL MapText
  648.         'END IF
  649.         'CALL ReassignID1
  650.  
  651.         ' Borrowed from downarrow.
  652.         IF (Cursor1.Y = TopIndent + VisibleLines) THEN
  653.             IF (VisibleLines > 1) THEN
  654.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  655.                 CALL MapText
  656.             END IF
  657.         ELSE
  658.             Cursor1.Y = Cursor1.Y + 1
  659.         END IF
  660.         q = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (HScroll - 1)
  661.         IF (Cursor1.X > q) THEN
  662.             IF (q < 0) THEN
  663.                 IF (LEN(LineAsMapped(Cursor1.Y - TopIndent)) > TextWidth) THEN
  664.                     HScroll = 1 + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (Cursor1.X - LeftIndent)
  665.                 ELSE
  666.                     HScroll = 1
  667.                     Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  668.                 END IF
  669.             ELSE
  670.                 Cursor1.X = q
  671.             END IF
  672.         END IF
  673.         CALL ReassignID1
  674.  
  675.     END IF
  676.  
  677.     KH = 0
  678.     '''k$ = ""
  679.     '''k$ = INKEY$
  680.     '''IF k$ <> "" THEN KH = ASC(k$)
  681.     KH = _KEYHIT
  682.  
  683.     ' Bksp
  684.     IF (KH = 8) THEN
  685.         IF ID1 = StartIndex THEN StartIndex = BackBreak(StartIndex) 'BEEP
  686.         r = TheChain(ID1).Pointer
  687.         q = TheChain(ID1).Lagger
  688.         CALL UnlinkCell(ID1)
  689.         IF ((r = EOC) AND (q = BOC)) THEN
  690.             ' Never erase the last character.
  691.         ELSE
  692.             IF (q <> BOC) THEN ID1 = q ELSE ID1 = r
  693.             IF (r = EOC) THEN ID2 = ID1
  694.         END IF
  695.     END IF
  696.     ' Tab
  697.     IF (KH = 9) THEN CALL InsertRange(ID1, "    ")
  698.     ' Esc
  699.     IF (KH = 27) THEN
  700.         IF (ID1 = StartIndex) AND (ID2 = StartIndex) THEN
  701.             ID2 = TheChain(ID1).Pointer
  702.         ELSE
  703.             IF (ID2 <> ID1) THEN ID2 = ID1 ELSE ID2 = StartIndex
  704.         END IF
  705.     END IF
  706.     ' Enter, Alphanumerics
  707.     IF (KH = 13) OR ((KH >= 32) AND (KH <= 126)) THEN
  708.         IF (InsertKey = -1) THEN
  709.             IF (ID1 = ID2) THEN
  710.                 CALL InsertBefore(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  711.             ELSE
  712.                 CALL InsertAfter(ID1, LTRIM$(RTRIM$(CHR$(KH))))
  713.                 ID1 = NthP(ID1, 2)
  714.             END IF
  715.         ELSE
  716.             TheChain(ID1).Content = LTRIM$(RTRIM$(CHR$(KH)))
  717.             IF (ID1 = ID2) THEN
  718.                 ID1 = NthP(ID1, 2)
  719.                 ID2 = ID1
  720.             ELSE
  721.                 ID1 = NthP(ID1, 2)
  722.             END IF
  723.         END IF
  724.         IF ((TextWrapping = 2) AND (Cursor1.X - LeftIndent = TextWidth)) THEN HScroll = HScroll + 1
  725.     END IF
  726.     ' F1
  727.     IF (KH = 15104) THEN
  728.         IF (TextWrapping = 2) THEN
  729.             HScroll = HScroll - 1
  730.             IF (HScroll < 1) THEN HScroll = 1
  731.             CALL ReassignID1
  732.             CALL ReassignID2
  733.         END IF
  734.     END IF
  735.     ' F2
  736.     IF (KH = 15360) THEN
  737.         IF (TextWrapping = 2) THEN
  738.             HScroll = HScroll + 1
  739.             CALL ReassignID1
  740.             CALL ReassignID2
  741.         END IF
  742.     END IF
  743.     ' F3
  744.     IF (KH = 15616) THEN
  745.         Cursor1.X = LeftIndent + 1
  746.         CALL ReassignID1
  747.         Cursor2.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  748.         Cursor2.Y = Cursor1.Y
  749.         CALL ReassignID2
  750.         IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN _CLIPBOARD$ = Projection$(ID1, ID2)
  751.     END IF
  752.     ' F5
  753.     IF (KH = 16128) THEN
  754.         q$ = Projection$(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  755.         Assimilate q$
  756.     END IF
  757.     ' F4
  758.     'IF (KH = 15872) THEN
  759.     '    'CALL InsertRange(NthP(ID2, 2), CHR$(10) + "=" + CoreProcess$(Projection(ID1, ID2)))
  760.     '    CALL InsertRange(NthP(ID2, 2), CHR$(10) + "=" + SxriptEval$(Projection(ID1, ID2)))
  761.     '    ID2 = StartIndex
  762.     'END IF
  763.     ' F6
  764.     IF (KH = 16384) THEN
  765.         OPEN FileName$ FOR OUTPUT AS #1
  766.         q$ = Projection$(NthL(ID1, ChainLimit + 1), NthP(ID1, ChainLimit + 1))
  767.         PRINT #1, q$
  768.         CLOSE #1
  769.     END IF
  770.     ' F7
  771.     IF (KH = 16640) THEN
  772.         GOLSwitch = -GOLSwitch
  773.     END IF
  774.     ' F8
  775.     'IF (KH = 16896) THEN
  776.     '    Cursor1.X = LeftIndent + 1
  777.     '    CALL ReassignID1
  778.     '    Cursor2.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  779.     '    Cursor2.Y = Cursor1.Y
  780.     '    CALL ReassignID2
  781.     'END IF
  782.     ' Home
  783.     IF (KH = 18176) THEN
  784.         IF (TextWrapping = 2) THEN HScroll = 1
  785.         Cursor1.X = LeftIndent + 1
  786.         CALL ReassignID1
  787.     END IF
  788.     ' UpArrow
  789.     IF (KH = 18432) THEN
  790.         IF (Cursor1.Y > TopIndent + 1) THEN
  791.             Cursor1.Y = Cursor1.Y - 1
  792.         ELSE
  793.             StartIndex = BackBreak(StartIndex)
  794.         END IF
  795.         q = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (HScroll - 1)
  796.         IF (Cursor1.X > q) THEN
  797.             IF (q < 0) THEN
  798.                 IF (LEN(LineAsMapped(Cursor1.Y - TopIndent)) > TextWidth) THEN
  799.                     HScroll = 1 + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (Cursor1.X - LeftIndent)
  800.                 ELSE
  801.                     HScroll = 1
  802.                     Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  803.                 END IF
  804.             ELSE
  805.                 Cursor1.X = q
  806.             END IF
  807.         END IF
  808.         CALL ReassignID1
  809.     END IF
  810.     ' PgUp
  811.     IF (KH = 18688) THEN
  812.         FOR k = 1 TO INT(TextHeight / 2)
  813.             StartIndex = BackBreak(StartIndex)
  814.         NEXT
  815.         CALL ReassignID1
  816.     END IF
  817.     ' LeftArrow
  818.     IF (KH = 19200) THEN
  819.         ID1 = NthL(ID1, 2)
  820.         IF (TextWrapping = 2) THEN
  821.             IF (Cursor1.X = LeftIndent + 1) THEN
  822.                 IF (HScroll > 1) THEN
  823.                     HScroll = HScroll - 1
  824.                 ELSE
  825.                     j = Cursor1.Y - TopIndent - 1
  826.                     IF (j >= 1) THEN
  827.                         k = LEN(LineAsMapped(j)) - TextWidth + 1
  828.                         IF (k >= 1) THEN
  829.                             HScroll = k
  830.                         END IF
  831.                     END IF
  832.                 END IF
  833.             END IF
  834.         ELSE
  835.             IF ((Cursor1.X - LeftIndent = 1) AND Cursor1.Y - TopIndent = 1) THEN
  836.                 StartIndex = BackBreak(StartIndex)
  837.             END IF
  838.         END IF
  839.     END IF
  840.     ' RightArrow
  841.     IF (KH = 19712) THEN
  842.         ID1 = NthP(ID1, 2)
  843.         m = Cursor1.X - LeftIndent
  844.         n = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - HScroll + 1
  845.         IF (TextWrapping = 2) THEN
  846.             IF (m >= TextWidth) THEN
  847.                 HScroll = HScroll + 1
  848.                 CALL ReassignID1
  849.             END IF
  850.             IF (m >= n) THEN
  851.                 j = Cursor1.Y - TopIndent + 1
  852.                 IF ((j <= TextHeight) AND (VisibleLines > 1)) THEN HScroll = 1
  853.             END IF
  854.         ELSE
  855.             IF ((m >= n) AND (Cursor1.Y - TopIndent = VisibleLines)) THEN
  856.                 IF (VisibleLines > 1) THEN StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  857.             END IF
  858.         END IF
  859.     END IF
  860.     ' End
  861.     IF (KH = 20224) THEN
  862.         Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  863.         CALL ReassignID1
  864.         IF (TextWrapping = 2) THEN
  865.             q = LEN(LineAsMapped(Cursor1.Y - TopIndent)) - TextWidth + 1
  866.             IF (q >= 1) THEN HScroll = q
  867.         END IF
  868.     END IF
  869.     ' DownArrow
  870.     IF (KH = 20480) THEN
  871.         IF (Cursor1.Y = TopIndent + VisibleLines) THEN
  872.             IF (VisibleLines > 1) THEN
  873.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  874.                 CALL MapText
  875.             END IF
  876.         ELSE
  877.             Cursor1.Y = Cursor1.Y + 1
  878.         END IF
  879.         q = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (HScroll - 1)
  880.         IF (Cursor1.X > q) THEN
  881.             IF (q < 0) THEN
  882.                 IF (LEN(LineAsMapped(Cursor1.Y - TopIndent)) > TextWidth) THEN
  883.                     HScroll = 1 + LEN(LineAsMapped(Cursor1.Y - TopIndent)) - (Cursor1.X - LeftIndent)
  884.                 ELSE
  885.                     HScroll = 1
  886.                     Cursor1.X = LeftIndent + LEN(LineAsMapped(Cursor1.Y - TopIndent))
  887.                 END IF
  888.             ELSE
  889.                 Cursor1.X = q
  890.             END IF
  891.         END IF
  892.         CALL ReassignID1
  893.     END IF
  894.     ' PgDn
  895.     IF (KH = 20736) THEN
  896.         FOR k = 1 TO INT(TextHeight / 2)
  897.             IF (VisibleLines > 1) THEN
  898.                 StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  899.                 CALL MapText
  900.             END IF
  901.         NEXT
  902.         CALL ReassignID1
  903.     END IF
  904.     ' Insert
  905.     IF (KH = 20992) THEN
  906.         InsertKey = -InsertKey
  907.     END IF
  908.     ' Del
  909.     IF (KH = 21248) THEN
  910.         IF (LinearCount(StartIndex, ID2) > LinearCount(StartIndex, ID1)) THEN
  911.             r = TheChain(ID2).Pointer
  912.             q = TheChain(ID1).Lagger
  913.             p = ID1
  914.             CALL UnlinkRange(ID1, ID2)
  915.             IF ((r = EOC) AND (q = BOC)) THEN
  916.                 ID1 = p
  917.                 ID2 = ID1
  918.                 StartIndex = p
  919.             ELSE
  920.                 IF (q = BOC) THEN
  921.                     ID1 = r
  922.                 ELSE
  923.                     ID1 = q
  924.                     IF (p = StartIndex) THEN StartIndex = q
  925.                 END IF
  926.                 ID2 = NthP(ID1, 2)
  927.             END IF
  928.         END IF
  929.     END IF
  930.     ' F11
  931.     IF (KH = 34048) THEN TextFormatting = -TextFormatting
  932.     ' F12
  933.     IF (KH = 34304) THEN
  934.         TextWrapping = TextWrapping + 1
  935.         IF (TextWrapping > 2) THEN TextWrapping = 0
  936.         ID1 = StartIndex
  937.         ID2 = ID1
  938.         HScroll = 1
  939.     END IF
  940.     ' Exit sequence
  941.     TheReturn = 0
  942.     IF ((MH <> 0) OR (KH > 0)) THEN
  943.         TheReturn = 1
  944.         CALL MapText
  945.         CALL CalibrateCursor(ID1)
  946.         CALL CalibrateCursor(ID2)
  947.         ' Cursor sync and autoscrolling.
  948.         IF (Cursor1.Y > TopIndent + TextHeight - 1) THEN StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  949.     END IF
  950.     _KEYCLEAR
  951.     StateChange = TheReturn
  952.  
  953. SUB CalibrateCursor (a AS LONG)
  954.     ' Place Cursor under ID on rendered line.
  955.     s = StartIndex
  956.     IF ((TextWrapping = 2) AND (HScroll > 1)) THEN s = NthP(s, HScroll)
  957.     c = LinearCount(s, a)
  958.     k = 0
  959.     i = -1
  960.     FOR j = 1 TO VisibleLines
  961.         n = LEN(LineAsMapped(j))
  962.         IF (k + n < c) THEN
  963.             k = k + n
  964.         ELSE
  965.             i = c - k + 1
  966.             EXIT FOR
  967.         END IF
  968.     NEXT
  969.     IF (i >= LeftIndent + LEN(LineAsMapped(j))) THEN
  970.         IF (j <= VisibleLines) THEN
  971.             i = 1
  972.             j = j + 1
  973.         END IF
  974.     END IF
  975.     IF (a = ID1) THEN
  976.         Cursor1.X = LeftIndent + i
  977.         Cursor1.Y = TopIndent + j
  978.     END IF
  979.     IF (a = ID2) THEN
  980.         Cursor2.X = LeftIndent + i
  981.         Cursor2.Y = TopIndent + j
  982.     END IF
  983.  
  984. FUNCTION FindID (a AS INTEGER, b AS LONG)
  985.     ' Find identity under a map location.
  986.     RelX = a - LeftIndent
  987.     RelY = b - TopIndent
  988.     FOR k = 1 TO RelY - 1
  989.         t = t + LEN(LineAsMapped(k))
  990.     NEXT
  991.     t = t + RelX
  992.     FindID = t
  993.  
  994. SUB ReassignID1
  995.     ' Reassign identity under Cursor1.
  996.     ID1 = NthP(StartIndex, FindID(Cursor1.X, Cursor1.Y) + (HScroll - 1))
  997.  
  998. SUB ReassignID2
  999.     ' Reassign identity under Cursor2.
  1000.     ID2 = NthP(StartIndex, FindID(Cursor2.X, Cursor2.Y) + (HScroll - 1))
  1001.  
  1002. SUB ConvertToGrid
  1003.     FOR j = 1 TO VisibleLines
  1004.         c$ = LineAsMapped(j)
  1005.         FOR i = 1 TO LEN(c$) - 1 ' BR offset to exclude break return at line end.
  1006.             AuxGrid(i, j, 1) = MID$(c$, i, 1)
  1007.         NEXT
  1008.     NEXT
  1009.  
  1010. SUB ConvertFromGrid
  1011.     q$ = ""
  1012.     FOR j = 1 TO VisibleLines
  1013.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  1014.             q$ = q$ + AuxGrid(i, j, 1)
  1015.         NEXT
  1016.         q$ = q$ + CHR$(13) ' Undoes BR offset.
  1017.     NEXT
  1018.     Assimilate q$
  1019.  
  1020. SUB GOL
  1021.     FOR j = 1 TO VisibleLines
  1022.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  1023.             c$ = AuxGrid(i, j, 1)
  1024.             IF (c$ = " ") THEN c$ = "0" ELSE c$ = "1"
  1025.             AuxGrid(i, j, 1) = c$
  1026.             AuxGrid(i, j, 2) = c$
  1027.         NEXT
  1028.     NEXT
  1029.     FOR j = 2 TO VisibleLines - 2 ' BR offset.
  1030.         FOR i = 2 TO LEN(LineAsMapped(j)) - 2 ' BR offset.
  1031.             c$ = AuxGrid(i, j, 1)
  1032.             a1 = VAL(AuxGrid(i - 1, j + 1, 1))
  1033.             a2 = VAL(AuxGrid(i, j + 1, 1))
  1034.             a3 = VAL(AuxGrid(i + 1, j + 1, 1))
  1035.             a4 = VAL(AuxGrid(i - 1, j, 1))
  1036.             a6 = VAL(AuxGrid(i + 1, j, 1))
  1037.             a7 = VAL(AuxGrid(i - 1, j - 1, 1))
  1038.             a8 = VAL(AuxGrid(i, j - 1, 1))
  1039.             a9 = VAL(AuxGrid(i + 1, j - 1, 1))
  1040.             t = a1 + a2 + a3 + a4 + a6 + a7 + a8 + a9
  1041.             IF (c$ = "1") THEN
  1042.                 SELECT CASE t
  1043.                     CASE IS < 2
  1044.                         AuxGrid(i, j, 2) = "0"
  1045.                     CASE 2
  1046.                         AuxGrid(i, j, 2) = "1"
  1047.                     CASE 3
  1048.                         AuxGrid(i, j, 2) = "1"
  1049.                     CASE IS > 3
  1050.                         AuxGrid(i, j, 2) = "0"
  1051.                 END SELECT
  1052.             ELSE
  1053.                 IF (t = 3) THEN AuxGrid(i, j, 2) = "1"
  1054.             END IF
  1055.         NEXT
  1056.     NEXT
  1057.     FOR j = 1 TO VisibleLines
  1058.         FOR i = 1 TO LEN(LineAsMapped(j)) - 1
  1059.             c$ = AuxGrid(i, j, 2)
  1060.             IF (c$ = "0") THEN c$ = " " ELSE c$ = CHR$(219)
  1061.             AuxGrid(i, j, 1) = c$
  1062.             AuxGrid(i, j, 2) = c$
  1063.         NEXT
  1064.     NEXT
  1065.  
  1066. SUB ThrowOutput
  1067.     'i = NthP(ID1, ChainLimit + 1)
  1068.     'FOR k = 1 TO LogTextCount
  1069.     '    CALL InsertRange(i, LogText(k) + CHR$(13))
  1070.     'NEXT
  1071.     LogTextCount = 0
  1072.     CALL MapText
  1073.     ID1 = NthP(ID1, ChainLimit + 1)
  1074.     CALL CalibrateCursor(ID1)
  1075.     CALL CalibrateCursor(ID2)
  1076.     DO WHILE (Cursor1.Y > TopIndent + TextHeight - 1)
  1077.         StartIndex = NthP(StartIndex, LEN(LineAsMapped(1)) + 1)
  1078.         CALL MapText
  1079.         CALL CalibrateCursor(ID1)
  1080.         CALL CalibrateCursor(ID2)
  1081.     LOOP
  1082.     ID1 = NthP(ID1, ChainLimit + 1)
  1083.     CALL PrintEverything
  1084.  
  1085. SUB DisplayText (Col AS INTEGER, Row AS INTEGER, Shade1 AS INTEGER, Shade2 AS INTEGER, Text AS STRING)
  1086.     COLOR Shade1, Shade2: _PRINTSTRING (Col, Row), Text
  1087.  
  1088. 'REM $Include: 'sxmath.bm'
  1089. 'REM $Include: 'sxript.bm'
« Last Edit: August 21, 2019, 11:01:58 am by STxAxTIC »
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #1 on: August 21, 2019, 11:29:28 am »
:) For the record, why would I clone Notepad or any editor when I have it with one Shell line? (I wouldn't.)

Q: When I save to file, do file lines end with CHR$(10) or CHR$(13) + CHR$(10) or something eXotic?

Q2, 3, 4?: What is fluid wrapping? Can I assume with Square wrapping I can set line Width? or are we completely misaligned in our thinking?
« Last Edit: August 21, 2019, 11:34:39 am by bplus »

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #2 on: August 21, 2019, 11:32:09 am »
Quote
Q: When I save to file, do file lines end with CHR$(10) or CHR$(13) + CHR$(10) or something eXotic?

Pretty sure I just use 13, but I haven't thought much about why or why not something else.
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #3 on: August 21, 2019, 11:36:29 am »
I ask, because I will likely want to open the file in QB64 code and work with text if it is saved like a .txt file.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #4 on: August 21, 2019, 11:40:55 am »
If it gives you more courage, this thing never saves over any existing file. The saved filename includes a timestamp so you'd have to be(plus) unlucky as hell to lose anything.
« Last Edit: August 21, 2019, 11:44:57 am by STxAxTIC »
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #5 on: August 21, 2019, 11:52:10 am »
Yeah, I see the timestamps after a test of F6 (no feedback if file saved, so I had to check directory.)

So far the only way to load a file is through command line?

update: Oh drag and drop too, nice!
« Last Edit: August 21, 2019, 11:53:24 am by bplus »

Offline petoro

  • Newbie
  • Posts: 27
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #6 on: August 21, 2019, 01:43:43 pm »
If it gives you more courage, this thing never saves over any existing file. The saved filename includes a timestamp so you'd have to be(plus) unlucky as hell to lose anything.

I love that feature, and also the default text :)

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: qXed: fairly-polished text editor
« Reply #7 on: August 21, 2019, 09:46:48 pm »
Hey bplus,

Didn't see your other questions - so fluid wrapping is a way to not cut words at the right edge, so it breaks on returns, spaces, and hyphens. Square wrapping is automatically fit to the window, so the size for now is fixed. You can uncomment all the "resize" code to adjust that, but there is some (probably shallow) error that QB64 kicks up when resizing sometimes. I never really looked into it, but might...
You're not done when it works, you're done when it's right.