Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Last user entered command needed as string variable

11 REPLIES 11
SOLVED
Reply
Message 1 of 12
jcourtne
1320 Views, 11 Replies

Last user entered command needed as string variable

Main question: I need the user to be able to type a command. Then use it or cancel it. Then i need them to type another lisp command. In the second command i need to know (have the string of) the previous command they typed in. How do i get that string?

 

What I have tried.

I have looked at "LASTPROMPT" though not extensively, and i believe it would just return "ineedhelp" if i tried that.

I have looked at "CMDNAMES" but that gives the command you're currently in. 

And I have found one page on this forum titled: Command Line History Macro, but there was no answer on that post.

I could create a global variable for that string, but then I would have to put the set code in every function I create. Nope, will not.

 

Backstory:

There's a person that is going to make how-to videos on how to use some of my lisp routines. (Yes, some people can't figure out that the command line can tell you what to type.)

Anyhow, I need the user to be able to type in "ineedhelp" (as help is an autocad command) and the lisp that runs must get the last command's string go to the shared drive and pull up the video file for that command.

I have made a lisp that opened a word file before, and if i could get the string of the previous command, i'm fairly certain that I can get the videos to work.

There will be approximately 60 lisp routine video's and i do NOT want to make a second lisp just for getting to the help video.

Can anyone please help with this. 

Thanks for your time

11 REPLIES 11
Message 2 of 12
alanjt_
in reply to: jcourtne

You could make a command and lisp command reactor to store each executed command to a global variable. 

Message 3 of 12
jcourtne
in reply to: alanjt_

I have very little experience with reactors. I have only tried twice and both times i wound up stuck in a reactor loop.

Face melting, and all that.

Anyhow, most of my lisp routines have several layers of subfunctions (to get/set object names, convert from ents, get set props.) Is there a reactor that works ONLY on user entered functions and not lisp routine called functions?

If so, a quick work up so I could muttle through it would help.  I do remember seeing some post about that and dismissed it. But i would love to be proved wrong. Thanks again 🙂

Message 4 of 12
dgorsman
in reply to: jcourtne

There is the vlr-command-reactor, which can process on:

 

  • unknown commands
  • command started
  • command ended
  • command cancelled
  • command failed

There is also the vlr-lisp-reactor, which can process on:

 

  • LISP evaluation started
  • LISP evaluation completed
  • LISP evaluation completed

(LISP can define commands as well using (defun c:MY_COMMAND_NAME ( / ) ...))

 

It should be fairly simple to hook a reactor to set a global variable (or even one of the USERSn system variables) with the command name.  Since it is constantly running, the value will alway reflect the last command name.  Then your "iNeedHelp" command can reference that value into a lookup table when launching the video.  You would have to watch the sequencing otherwise you could end up with the last command always being "iNeedHelp".

----------------------------------
If you are going to fly by the seat of your pants, expect friction burns.
"I don't know" is the beginning of knowledge, not the end.


Message 5 of 12
alanjt_
in reply to: jcourtne

I can do that. Here's a sample where it will store the active command and the last command to a global variable. You can then access the last executed command with: 

 

(cadr *Reactor:PreviousCommand:List*)

 

Reactor sample:

 

(if (not *Reactor:PreviousCommand*)
  (setq *Reactor:PreviousCommand*
         (list (vlr-lisp-reactor nil '((:vlr-lispWillStart . Reactor:PreviousCommand)))
               (vlr-command-reactor nil '((:vlr-commandwillstart . Reactor:PreviousCommand)))
         )
  )
)


(defun Reactor:PreviousCommand (call info)
  (if (or (wcmatch (setq info (strcase (car info))) "(C:*") ; LISP command
          (getcname info) ; core command
      )
    (setq info                           (vl-string-subst "" "(C:" (vl-string-right-trim ")" info))
          *Reactor:PreviousCommand:List*
                                         (if (vl-consp *Reactor:PreviousCommand:List*)
                                           (list info (car *Reactor:PreviousCommand:List*))
                                           (list info)
                                         )
    )
  )
  (princ)
)

 

Message 6 of 12
jcourtne
in reply to: alanjt_

1. Does the Reactor object variable require the *'s?

2. Is the variable Reactor:..:List and object or just a list?

3. Does this function make the dugger jump into this code when stepping through another function call?

(I'm going to test this myself but decided it would be good information to have on the forums.)

4. After seeing this and somewhat understanding it, I plan on wrapping the if statement in a defun and calling it in the lisp loading sequence. Because I will be able to comment out a single line and not have the reactor going off during debugging/ testing.

Do you see any problems with that?

5. The defun you have has following it (call info).  In my code i always have (argument1 arg2 etc  / localvariable1 2 etc)

You do not have a slash, and when i tried to add one, i started to get errors. Is this not a normal function call? I don't see call as a local variable, is it just being ignored?

Message 7 of 12
dgorsman
in reply to: jcourtne

I'll adress the last point, about the "/" and arguments.  The function Reactor:PreviousCommand is what gets called by the reactor.  From the LISP help, the vlr-command-reactor must call a function with two arguments, no more no less.  One isfor the command name and one is for generic information the reactor can pass along.  The "/" is omitted because there aren't any localized variables inside that function.

----------------------------------
If you are going to fly by the seat of your pants, expect friction burns.
"I don't know" is the beginning of knowledge, not the end.


Message 8 of 12
jcourtne
in reply to: dgorsman

I will now answer the questions (except the first) of my own. then post my code with comments and much more code bloat for the others just getting into reactors. I have accepted that this is the only way to do what I want, though I retain my reservations.

1) I do not think it does, but I'm not gonig to figure out how to test that.

2) The "...:List"  was indeed just a list. Since it is not in the localvars section of the function header it becomes global.

3) Yes. I will be putting this code in a separate load file to make the vlide debugger jump over it.

4) I haven't run into any problems, YET.

5) The above poster was correct. I had placed the / before the arguments making them local vars, not agurments.

 

Note: These two commands work together to make a global variable which returns the last two commands ran.

I will be using this and continue to edit it to make it do what I want. Thanks to the two above posters, alanjt and dgorsman. Special thanks to alan for original code.

 

globalListVariableCommandNames

 

;;; Set up temporary reactor command
(defun ReactorSetup (/)
	;; The *'s are left because this variable is an actual object variable in autocad
	(if (not *Reactor:PreviousCommand*)
		(setq *Reactor:PreviousCommand*
			(list (vlr-lisp-reactor nil '((:vlr-lispWillStart . Reactor:PreviousCommand)))
				(vlr-command-reactor nil '((:vlr-commandwillstart . Reactor:PreviousCommand)))
			)
		)
	)
) ;_ End of function ReactorSetup
;;;****************************************************************************


(defun Reactor:PreviousCommand (argDataNil argListCommandString / sCommandName)
	;; Inside the if condition, we remove the list wrapper and capitalize the command name
	;; We test to see if it has a c: because those are functions the user types in
	;; We test to see if it is a native autocad command
	;; if EITHER of those is true we proceed. ( I will change this later to perform only on c: functions)
	(if	(or	(wcmatch
				(setq sCommandName (strcase (car argListCommandString)))
				"(C:*"
			)
		(getcname sCommandName)
		)
		;; This is the setq command that exicutes givin a true in the if condition there are two setq's in this statement.
		;; The first takes the command string from above and chops the ()'s and the c: off
		;; The second saves a growing list of all the commands run. And makes this list if it doesn't exist yet.
		(setq sCommandName(vl-string-subst "" "(C:" (vl-string-right-trim ")" sCommandName))
			globalListVariableCommandNames
										(if (vl-consp globalListVariableCommandNames)
											(list sCommandName (car globalListVariableCommandNames))
											(list sCommandName)
										)
		)
	)
	;; if you add a string in the Princ statement, you'll find out just how often this reactor runs.
	(princ)
) ;_ End of function Reactor:PreviousCommand
;;;****************************************************************************

 

Message 9 of 12
Kent1Cooper
in reply to: jcourtne

If you want such a thing to call up instructional videos of your custom Lisp routines, maybe you don't want to keep track of every command that's entered [presumably you don't have an instructional video about the Line command], but only of those Lisp routines.  If that's the case, you could add a line at the top of all your Lisp-defined commands for which videos exist, such as:

 

(setq LastRoutine "NameOfThisRoutine")

 

with LastRoutine being a global variable, its value thereby updated any time one of those commands is called for.  If it's right at the beginning of a given command definition, it would be recorded even if they cancel the command or it fails for some reason.  It wouldn't record the names of sub-routines unless you also build it into their definitions, which you could just as easily do in any for which you think it would be useful.

 

Then the INeedHelp command could simply get the value of the LastRoutine variable, and go from there.  If you only put that line into the definitions of routines that have videos, adding it to each only as its video becomes available, you don't have to worry about its trying to find a video for a routine for which there isn't one yet.

 

However, if someone uses a command for which there is a video, and later uses one for which there isn't one, and has trouble with the latter, then INeedHelp would call up the video for the one they used before, not the one they're asking for help with.  You could avoid that potential drawback by adding a similar line at the beginning of every command definition for which there isn't yet a video, such as:

 

(setq LastRoutine "NotYet")

 

and when INeedHelp finds that value, it can express its regret to the User that there's no help of that kind available yet.

Kent Cooper, AIA
Message 10 of 12
jcourtne
in reply to: Kent1Cooper

I have been mulling that over also. There are definate advatages for that system.

SIMPLICITY. Alot to be said for that.

I also agree that I would not have to write error checking code for missing video files.

 

However, I currently have 12 files, just one has 80 in it. No biggie, but I'm not sure when he'll get around to which ones, and I haven't convinced him to mokey with the code and add that line himself yet. (i'm lazy (like the people i mention  below) and don't want to keep up with which videos have/ haven't been created.)

 

About the reactor solution:

I am worried about a performance hit. My larger more complicated 3D tray checking and flipping routines tend to have several function calls deep, like 8. And have many loops and lists. I and do realize that all those function calls will be making that reactor whirl. All that impact just for the POSSIBILITY of someone needing to see the help on one function one day, and they are too lazy to go to the sharepoint site to click the d@edit#   button. 

So I may wind up implementing that much simpler idea, yet.

Message 11 of 12
BlackBox_
in reply to: jcourtne

Not sure what version you're using, but Autoloader may actually be of use here... It has an XML Attribute that you use to specify the help file associated with a given command, and does all of the help file registration for you. Also, this would make it simple for you to deploy a single .bundle throughout your entire enterprise, and makes upgrading components easy as well with the inherent upgrade code, etc... very powerful, when used well. It's what we do here for 2012-2014.

Regarding the reactor calls, they all fire now in other APIs such as ObjectARX, and .NET. The events are raised, it's what you do then, and how many subscriptions to said event(s) that cause any potential performance hit... That, and the efficiency of your callback/ event handler.


"How we think determines what we do, and what we do determines what we get."

Message 12 of 12
alanjt_
in reply to: jcourtne


@jcourtne wrote:

1. Does the Reactor object variable require the *'s?

2. Is the variable Reactor:..:List and object or just a list?

3. Does this function make the dugger jump into this code when stepping through another function call?

(I'm going to test this myself but decided it would be good information to have on the forums.)

4. After seeing this and somewhat understanding it, I plan on wrapping the if statement in a defun and calling it in the lisp loading sequence. Because I will be able to comment out a single line and not have the reactor going off during debugging/ testing.

Do you see any problems with that?

5. The defun you have has following it (call info).  In my code i always have (argument1 arg2 etc  / localvariable1 2 etc)

You do not have a slash, and when i tried to add one, i started to get errors. Is this not a normal function call? I don't see call as a local variable, is it just being ignored?


 

 

I'll do my best to answer your questions...

 

1. Does the Reactor object variable require the *'s?
No. I prefer to prefix and suffix all global variables with “*”. It’s actually pretty common practice among lispers

2. Is the variable Reactor:..:List and object or just a list?

Just a list where the the last two or one if first command executed for session (counting the one just executed) commands are stored.

3. Does this function make the dugger jump into this code when stepping through another function call?
I’m not sure I understand the question, but the reactor will fire for every lisp and core command executed. My checks with wcmatch for (C:* is what filters out the subroutines.

4. After seeing this and somewhat understanding it, I plan on wrapping the if statement in a defun and calling it in the lisp loading sequence. Because I will be able to comment out a single line and not have the reactor going off during debugging/ testing.
Do you see any problems with that?

Go for it. It’s a complete code, but it can be altered as much as you like.

5. The defun you have has following it (call info).  In my code i always have (argument1 arg2 etc  / localvariable1 2 etc)

You do not have a slash, and when i tried to add one, i started to get errors. Is this not a normal function call? I don't see call as a local variable, is it just being ignored?
I didn’t localize any variables because I don’t have any. I only have the two variables I’m applying to the function (call info) and my global variable. I know I setq the info variable again, but it’s technically localized by being applied to the function.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report

”Boost