|
Post by Robert on Jun 16, 2024 12:45:11 GMT -5
George,
In Working with Tags, there is the following note:
"If you choose to use FIRST, LAST, PREV or NEXT on TAG, it would ordinarily involve manual cursor placement if you wanted to repeat your initial single-line TAG command. However, you can use the ITERATOR Technique to get around this. See the Application Note: The ITERATOR Technique in Command Macro Support for more information."
However, I cannot find this Application Note, going back as many versions as I have. The link in the current Help doesn't work.
Can you try to find this Application Note somewhere? If you have full backups of all the versions, maybe you can find it one of the old CHM files. I wonder if this got deleted because it didn't work any more, or maybe by accident?
R
|
|
|
Post by George on Jun 16, 2024 15:14:15 GMT -5
Robert: Wow! Is that an old reference. I had to go back to version 6 in 2013 to find it. It was before we had the current macro support.
Here it is (not that it's useful anymore, that support is long gone)
I Cut/Pasted this from the CHM file.
Application Note: The ITERATOR technique
Sometimes you may wish to execute a set of one or more commands repeatedly. An obvious way to do this is might be:
Place several commands in the primary command area, using the command separator character between them Press Enter to execute the set of commands once Press the key you have mapped to retrieve commands (usually, F12) Press Enter to execute the commands again Repeat steps 3 and 4 as many times as needed
If you had to do this many times, steps 3 and 4 could add up to a lot of typing. Here is an easier way to accomplish the same thing with a macro, which we are calling the ITERATOR technique - a way to "iterate over" a list of commands. The specific names and keys used are up to you, but for now, let's continue with the example.
You will define an "iterator name" based on the name of the key it is mapped to. hat way, if you need more than one of them, you'll always have a well-defined name for each key used. In this example, Ctrl-E is the key we will use. You are free to choose any available key.
Create a macro called CTRL-E.SPM.
In the CTRL-E.SPM, put a ;CTRL-E macro prototype header on line 1, with any default operands, as usual for a macro.
In the remaining lines, put the commands you wish to execute, one command per line.
Save the CTRL-E.SPM file.
To make it easy to access the CTRL-E.SPM file in the future, you can add it to the Favorites Files File List.
In KEYMAP, map macro name CTRL-E as the command name to keyboard key Ctrl-E, and ensure it is set to auto-repeat.
Navigate in your main edit file to the place where you want to apply the set of commands.
Finally, hold down the Ctrl-E as long as required to apply the commands where you need them.
When you use the ITERATOR Technique, you no longer have to repeatedly retrieve and execute commands. All you have to do is hold down your Ctrl-E key until the task is done. You'll find it's much faster, and a lot easier on the fingers!
You can apply the ITERATOR Technique to multiple macros and multiple keys, by giving the macro name the name of the key that launches it.
One thing to watch out for is that if you do this, you could accidentally run the ITERATOR macro if you pressed Ctrl-E by mistake. To avoid this, you can disable the macro by placing a \ backslash in column 1 of line 1. When SPFLite sees this, it immediately stops execution of the macro. If you have a macro that you frequently redefine, but only use occasionally, you can disable it with the backslash when you are done using it, and then re-enable it again when you have a (new) use for it.
ITERATOR example
Note: When this example was originally written, LOCATE ALL was not a supported feature, but it is now, in version 6.2. The discussion that follows may be considered for illustration purposes.
Are there any specific situations where an ITERATOR technique is valuable? One is to apply it to the LOCATE command. Compared to commands like FIND, LOCATE did not have an ALL option (prior to 6.2). The reason why is that LOCATE was designed to go to a single, particular line of some given type. So, it's impossible to go to ALL of them at the same time. However, a side effect of LOCATE is that it will unexclude any excluded line which it goes to. So, suppose you wanted to LOCATE all lines of some type and unexclude them. There is no simple command to do that. But this can be done with an iterator.
Let's say you want to unexclude all lines with labels of any kind. You'd like to be able to say LOCATE LABEL ALL, but that's not allowed. Instead, create a macro like this (we will continue to use CTRL-E):
;CTRL-E
LOCATE LABEL NEXT
Now, exclude all the lines in your file, and hold down the Ctrl-E key. The macro will repeatedly locate lines with labels until there are no more to be found, and when that happens, it will produce the message, "Bottom of data reached". Since all you are doing is locating lines, and not modifying them, no harm is done if you hold the Ctrl-E down longer than necessary. Once you see them message, just let go of the key; the exact timing of it isn't critical.
Note: To reiterate, starting with version 6.2, the command syntax LOCATE LABEL ALL is now supported, and you would not need a macro for that any more.
|
|
|
Post by Robert on Jun 16, 2024 17:30:15 GMT -5
Thanks for finding this. The TAG help makes mention of this. As you can tell, the blurb I wrote about this (x? years ago) is very low-tech, vintage SPFLite. I was thinking about making something appropriate to today's SPFLite. Here goes:
Consider a primary command called REPEAT (which is not a current command). The syntax would be:
REPEAT the text of any primary command here
What would happen next is:
1. "the text of any primary command here" would be written to the CMDPAD.CLIP file
2. The Primary command "the text of any primary command here" would be executed.
The key assigned to running the CMDPAD command (like, Keypad Enter) could then be used to repeat the initial command as many times as needed by holding the Keypad Enter key down.
The REPEAT command probably could be written as a macro. I haven't delved into the mechanics of it all, but I suspect it could be valuable as a general-purpose feature without needing too much work to implement.
Thoughts?
===> Couple more things:
(1) Instead of combining steps 1. and 2. above into one command, there could be a new primary command of the form CMDPAD the text of any primary command here
since there is no CMDPAD primary command now. What this would do is just copy the command in question to CMDPAD.CLIP but not execute it. This would make the storing of the command and running it separate, in case you had to defer running it the first time for some reason.
(2) Since this idea deals with the command pad, I went to look for the help on it. Only thing is, there is no help entry for (CmdPad) in the keyboeard-primitives help, and no entry for (CmdPad) in the KEYMAP GUI screen's drop-down box. The only place CMDPAD gets a mention is in the Working With section. Also, if I say HELP CMDPAD nothing is returned.
R
|
|
|
Post by Stefan on Jun 17, 2024 5:04:34 GMT -5
As you need to define a key to "running" the CMDPAD anyway, why wouldn't you just define a key (e.g. CTRL-F12) as !RETRIEVE(ENTER)? Having executed a command (or series of commands separated by the command-separator-char) once, you can then repeat it with CTRL-F12. If you keep the tick in the KEYMAP dialog 'repeat' column, just holding down that key will repeat the command until you release the key.
I appreciate this approach doesn't 'store' the command sequence for later, but I doubt anyone would remember what they stored a few days before. If they need to repeat the same sequence several times, why not define the sequence to a key and just hit that key?
As an aside, I notice a considerable delay when executong a RUN command, mostly due to the external file I/O overhead associated with that command.
Therefore I also suggest avoiding use of a .CLIP file, which entails I/O to save the command and to retrieve the command. I'd be enclined to implement the lot as a MACRO and 'store' the command(s) in a SET variable, from whence it can be accessed without file I/O - no need to bother Windows, Antivirus, et al :-).
|
|
|
Post by Robert on Jun 17, 2024 11:19:41 GMT -5
Stefan,
The reason why I wouldn't use something like !RETRIEVE(Enter) is that RETRIVE doesn't always work. It's behavior is unreliable, and has been for many years. Frequently, you can press F12 to do a RETRIEVE and nothing happens. I suspect the issue may be that there is no common code to store primary commands in the retrieve stack, but instead each command must handle it individually. So, if there were a bug, you'd have to check every command (a couple hundred of them, I believe) to find where it goes wrong. But then, sometimes the stored command can be obtained using a RETF command - which in itself doesn't make sense.
Because of this problem, I don't tend to use RETRIEVE all that much. And yes, I have told George about it before, but the problem is still here, years later. I must assume that finding the bug is hard to do.
As for the overhead of CMDPAD, I don't think it's as much as you believe if you are judging by how RUN behaves. Do a HELP RUN and see how much overhead is involved. It has to create a temporary copy of the current file, then do a Shell function to launch CMD.EXE and then the command interpreter, which could be for BAT, EXE, REXX, etc. When you just open a small file, the NTFS logic will store files < 512 bytes or so in directory entries, and it will keep recently-used ones in memory. The net result is that reading small files is very fast.
I wish RETRIEVE worked, just for its own sake, but if it's hard to find and harder to fix, George probably doesn't have a lot of incentive to tackle it.
I think you may be wrong about the Antivirus concerns. Windows may launch a virus check when a new file comes from "outside", like the internet or maybe an external drive, but not every time it's opened, otherwise Windows would be horribly slow, and it just isn't.
R
|
|
|
Post by George on Jun 17, 2024 12:41:15 GMT -5
Robert: You have complained about RETRIEVE before, so much so that Retrieve and all it's data is stored and accessed from an Object, so it can't be accidentally modified. Stuff is added to the stack by the command router, not each individual command. Same with retrieve, one place.
I rewrote the whole darned mess quite a while ago to try and fix this problem that you seem to encounter. I'm open to any further suggestions you might have as to how to address this.
Here's the code, I'd be overjoyed if you can find the bug.
CLASS cRtrv INSTANCE Rtr() AS STRING ' Hold area for Retrieve commands INSTANCE RtrIX AS LONG ' Pointer to next retrieve item
CLASS METHOD CREATE() ' Constructor - Initialize Class stuff REGISTER i AS LONG ' REGISTER k AS LONG ' '--------------------------------------------------------------------------------------------+ '- Load the table from the CFG file | '--------------------------------------------------------------------------------------------+ DIM Rtr(1 TO 50) AS INSTANCE STRING ' Retrievable Command line(s) gSQL.SelBegin("select * FROM " + gSQL.RtrName) ' Ask for everything IF gSQL.SelFirst() THEN ' Select the first DO WHILE gSQL.SelNext() ' Loop through them INCR i ' Count the line IF i > 50 THEN EXIT DO ' Excess records, ignore them Rtr(i) = gSQL.SelGet("DBData") ' Get and save the the record LOOP ' IF i < 50 THEN ' Less than specified number? FOR k = i + 1 TO 50 ' Fill in nulls Rtr(k) = "" ' NEXT k ' END IF ' END IF ' gSQL.SelEnd ' Close the SQL request RtrIX = 0 ' END METHOD '
INTERFACE iRtrv: INHERIT IUNKNOWN ' Define the interface
METHOD ADD(Cmd AS STRING) AS LONG '--------------------------------------------------------------------------------------------+ '- Add an item to the Retrieve stack | '--------------------------------------------------------------------------------------------+ LOCAL t, tCmdF AS STRING ' t = UUCASE(GetNextWord(Cmd, %NoStrip)) ' Get command name tCmdF = LSET$(t, 8) ' Make an 8 char version of it
IF LEN(Cmd) >= gENV.MinRetrieve THEN ' Long enough to save? '--------------------------------------------------------------------------------------+ '- Don't add certain commands | '--------------------------------------------------------------------------------------+ IF INSTR("RETRIEVECRETRIEVRETF ISCROLL TOP BOTTOM BOT UP DOWN LEFT RIGHT RECALL RC UNDO REDO ",tCmdF) = 0 THEN IF Rtr(1) <> Cmd THEN ' If not the same as the current top of stack RtrIX = 0 ' Reset ptr if adding a new entry ARRAY INSERT Rtr(1), Cmd ' Save new entry for Retrieve command END IF ' END IF ' END IF ' END METHOD '
METHOD GetNext() AS STRING '--------------------------------------------------------------------------------------------+ '- Retrieve next command from stack | '--------------------------------------------------------------------------------------------+ INCR RtrIX: IF RtrIX > UBOUND(Rtr()) THEN RtrIX = 1 ' Bump, wrap if needed TP.ErrMsgHigh = %eRetrieve ' METHOD = Rtr(RtrIX) ' Pass back the entry END METHOD '
METHOD GetPrev() AS STRING REGISTER j AS LONG '--------------------------------------------------------------------------------------------+ '- Retrieve previous command from stack | '--------------------------------------------------------------------------------------------+ IF RtrIX > 1 THEN ' Room to backup? DECR RtrIX ' Get previous entry METHOD = Rtr(RtrIX) ' ELSE ' No Backup room, wrap FOR j = 50 TO 1 STEP -1 ' Find the last non-null entry IF ISNOTNULL(Rtr(j)) THEN ' We have one RtrIX = j: METHOD = Rtr(j): TP.ErrMsgHigh = %eRetrieve: EXIT METHOD ' Use it END IF ' NEXT j ' METHOD = "" ' Return null if all empty END IF ' TP.ErrMsgHigh = %eRetrieve ' END METHOD '
METHOD RESET() ' RtrIX = 0 ' Back to the top END METHOD '
METHOD CLEAR() ' REGISTER i AS LONG ' RtrIX = 0 ' Back to the top FOR i = 1 TO 50 ' Clear the entries Rtr(i) = "" ' NEXT i ' END METHOD '
METHOD SAVE() ' REGISTER i AS LONG ' '-----------------------------------------------------------------------------------------+ '- Save the table to the CFG file | '-----------------------------------------------------------------------------------------+ gSQL.Execute("PRAGMA synchronous = OFF") ' To optimize things gSQL.TableClear(gSQL.RtrName) ' Clear the existing table gSQL.AddString("R", "R00000", "50") ' Write the Total # entries FOR i = 1 TO 50 ' Dump the Table gSQL.AddString("R", "R" + FORMAT$(i, "00000"), Rtr(i)) ' NEXT i ' gSQL.Execute("PRAGMA synchronous = ON") ' END METHOD '
END INTERFACE
END CLASS '
George
|
|
|
Post by Stefan on Jun 17, 2024 13:53:31 GMT -5
Robert,
If you say RETRIEVE is unreliable, I won't argue, although I've found it to be fine if we ignore the commands it deliberately doesn't capture (like UP, DOWN, etc). Might the issue perhaps be caused by the minimum length specified for RETRIEVE in OPTIONS GENERAL?
|
|
|
Post by Robert on Jun 17, 2024 15:03:18 GMT -5
I am not sure if this is "the" or "the only" bug, but there is a bug in GetNext().
You have the line that says,
INCR RtrIX: IF RtrIX > UBOUND(Rtr()) THEN RtrIX = 1 ' Bump, wrap if needed
But assume the retrieve stack is occupied by < 50 commands; call this N. Now, once RtrIX is > N then you simply return an entry that had been initialized with "" when the table was loaded. Once that happens, RETRIEVE will just keep saying "No (more) RETREIVE commands available". No wrap around happens, and there's no way to get out of this "error condition". I put that in quotes because IMHO, this shouldn't be an error in the first place.
But if RtrIX is at N and you do a RETF, it will wrap around, even if the stack is not completely filled.
So, the problem is that RETRIEVE and RETF are inconsistent; one wraps and one doesn't.
I recommend you rewrite this function as follows:
METHOD GetNext() AS STRING
'--------------------------------------------------------------------------------------------+ '- Retrieve next command from stack | '--------------------------------------------------------------------------------------------+
INCR RtrIX ' Point to "next (older) stack entry
IF RtrIX > UBOUND(Rtr()) THEN ' If end of stack passed RtrIX = 1 ' Wrap back to start of stack ELSEIF Rtr(RtrIX) = "" THEN ' At end of partially filled stack RtrIX = 1 ' Wrap back to start of stack END IF
TP.ErrMsgHigh = %eRetrieve ' METHOD = Rtr(RtrIX) ' Pass back the entry
END METHOD '
|
|
|
Post by George on Jun 18, 2024 14:20:24 GMT -5
Robert: It should only go wrong when the table is new, once fully populated, it's fine. Regardless, it should be fixed.
George
|
|