Adding a date to the History section of the Command Window

Hi all,

I have been on and off analysing some data for the last 15 years. Because I can't remember clearly how I did certain calculations I often search the history in the Command Window to remind myself. The 'find' feature helps a lot but I was wondering if the command window could do more passively to aid me.

I think looking back at my past work would be even easier if the date that I did what commands were displayed by the command history window. Example:- I did these 50 lines 5 years ago, then I added another 50 lines 2 years later.

I would like to see it work like Photos on the mac, such that when I drag the window slider on the right hand side and move it up and down to scroll the command history, the date I wrote those commands pops up. Some auto inserted date headers might also be nice as an option in the preferences. 

Just my two cents.


This would definitely be a good addition. Perhaps there is a hidden way to extract the information from previous experiments. In the meantime, I have this code in a "JJW Core Functions" procedure in my Igor Procedures.

Menu "Macros"
    "Print Now to History",/Q, print "TIME STAMP --> " + jjwcf#DateTimeStamp(dsep="-",tsep=":",sep="/")
    "Do Independent Development", setigoroption independentmoduledev=1
    "Save Experiment SnapShot",/Q, jjwcf#SaveExpSnapShot()

// date/time stamp in various ways
// dsep - date separator string (default "")
// tsep - time separator string (default "")
// sep - date + time separator string (default "")
// dtonly - 0: all, 1: date only, 2: time only (default 0)
Function/S DateTimeStamp([dsep,tsep,sep,dtonly])
    string dsep, tsep, sep
    variable dtonly
    string a, b, c, dstr, tstr, rtstr, gExp
    if (ParamIsDefault(sep))
        sep = ""
    if (ParamIsDefault(dsep))
        dsep = ""
    if (ParamIsDefault(tsep))
        tsep = ""
    if (ParamIsDefault(dtonly))
        dtonly = 0
    gExp = "([0-9]+)/([0-9]+)/([0-9]+)"
    SplitString/E=(gExp) secs2date(datetime,-1), a, b, c
    dstr = c[2,3] + dsep + b + dsep + a
    gExp = "([0-9]+):([0-9]+)" 
    SplitString/E=(gExp) secs2time(datetime,2), a, b
    tstr = a + tsep + b

        case 0: // all
            rtstr = dstr + sep + tstr
        case 1: // date only
            rtstr = dstr
        case 2: // time only
            rtstr = tstr
    return rtstr


When it is important for you to document what you do at time t in a manner that would be understood at t+dt you may want to consider using an Igor notebook for your work.  You include in the notebook all the details.  You can insert timestamps as jjweimer suggested above and make sure to execute all your commands from the same notebook.  Remember, to execute commands you need to select them in the notebook and type Ctrl-Enter.

Following on the suggestion to run commands from a notebook, the LogBook package allows you to type executable commands into a note field. It runs those commands and assembles the results into a notebook with a date+time stamp. It can also assemble the history log (cumulative or snapshot).

What might be nice is to have a hook function(s) Before/AfterPrinttoHistoryHook. Users could then create their own date+time stamp output (or create one reporting function) using such a hook function.

A not very elegant but workable way to achieve this with the available hook functions could be to use AfterFileOpenHook() and a persistent global variable. The global variable holds the date of the last update. After the experiment was opened, check the global and if the date has changed print a line with the current date, then update the global. This way, the current date would be saved whenever one works on the file.

You can probably do this already with a modification of HistoryCarbonCopy... which I've almost achieved but Hooks/UI aren't my forte so there is a bug. Someone more skilled can probably spot my blunder in the window hook function - the rest is cribbed straight from the manual I think: 

EDIT: I figured out a quick and dirty kluge and fixed where the history would be killed. Unfotunately it won't do anyting for old experiments but you can  use it going forward if it is useful to you. You could also have an IgorStartOrNewHook that just prints the date everytime you open an experiment. The history carbon copy you can at least search and save as text if required.

// Function to creat a copy of the history area as a log
Function CreateHistoryCarbonCopy()
    // bring to front if it exists
    if (ItemsInList(WinList("HistoryCarbonCopy",";","WIN:16")))
        DoWindow/F HistoryCarbonCopy
        return 0
    NewNotebook /F=1/K=3 /N=HistoryCarbonCopy /W=(5,0,800,515)/OPTS=3 as "Command Log"
    Notebook HistoryCarbonCopy writeBOM=0
    Notebook HistoryCarbonCopy backRGB=(0,0,0)  // Set background to black

    Notebook HistoryCarbonCopy showRuler=0

    // Define ruler to govern commands.
    // Igor will automatically apply this to commands sent to history carbon copy.
    Notebook HistoryCarbonCopy newRuler=Command, rulerDefaults={"Arial",10,0,(65535,65535,0)},margins={0,0,800}

    // Define ruler to govern results.
    // Igor will automatically apply this to results sent to history carbon copy.
    Notebook HistoryCarbonCopy newRuler=Result, rulerDefaults={"Arial",10,0,(0,65535,0)},margins={0,0,800}

    // Define ruler to govern user-generated error messages.
    // We will apply this ruler to error messages that we send to history carbon copy via Print commands.
    Notebook HistoryCarbonCopy newRuler=Error, rulerDefaults={"Arial",10,0,(65535,0,0)},margins={0,0,800}
    SetWindow HistoryCarbonCopy, hook(MyHook) = HistoryWindowHook   // Install window hook
    return 0
// hook to write the time and date at the start of each line
Function HistoryWindowHook(s)
    STRUCT WMWinHookStruct &s

    Variable hookResult = 0

        case 8:             // window modified

            NoteBook HistoryCarbonCopy findText = {"•",2^4} // seach backwards
            if (V_flag)
                Notebook HistoryCarbonCopy setData = Secs2Time(datetime,3,1) + "\t" + Secs2Date(datetime,3) + "\t" // insert date and time


    return hookResult       // 0 if nothing done, else 1


In reply to by cpr

The HistoryWindowHook function causes Igor to crash because it is modifying the notebook while Igor is in the process of modifying it. We can reproduce the crash by typing in the HistoryCarbonCopy.

I am investigating this, but it looks like, to prevent crashing, we will have to make it an error to call the Notebook operation from a hook modified event (s.eventCode==8).


That is unfortunate, I don't see a crash, hang or anything untoward FYI.

Even more reason to have a datetime option in the command history - maybe if you hover over a line with your cursor a help bubble could have the information.

For the record: I also did not encounter any crashes while playing around with cpr's code (Win 10, Igor 9 beta). Would it be a solution to push the update into the operation queue? This way the notebook is not updated during the execution of the hook function.

Function HistoryWindowHook(s)
    STRUCT WMWinHookStruct &s
    if (s.eventCode == 8)
        NoteBook HistoryCarbonCopy findText = {"•",2^4}
        if (V_flag)
            String dateStr = "\""+Secs2Time(datetime,3,1) + "\t" + Secs2Date(datetime,3) + "\t"+"\""
            Execute/P/Q "NoteBook HistoryCarbonCopy findText = {\"\",2^4};Notebook HistoryCarbonCopy setData="+dateStr
    return 0



I'm also using IP9 under Windows - though 32-bit, for reasons...

Your solution neatly sidesteps the issue hrodstein is worried about without having to ban the call for window modified events entirely - though WM probably wouldn't like to leave any possibility for crashes open as I always seem to find them :-D . In my playing around I investigated using GetRTStackInfo() as I was worried about the call being self refferential, but that yeilded zip.

What I posted was hastily knocked together example of what might be possible now, though I think the original feature request is a good one even if I wouldn't be the target not having saved an experiment that wasn't sent to support since about 2015 due to how I use (abuse) Igor, though I use HistoryCarbonCopy a lot, just not with that hook. 

In my defence I got my excuses in ahead of time!


It crashes for me every time in Igor Pro 9.00B08 on Macintosh and Windows 32-bit and 64-bit. To make it crash, I create the HistoryCarbonCopy window, click at the end of the first line of text, and press return.

I think, even if this didn't crash, the use of the modify event to modify the notebook should create another modify event and so on ad infinitum (cascading modified events).

Would it be a solution to push the update into the operation queue?


However, the cascading modified events may still be a problem.

Hooks are useful but can also open up all kinds of bizarre scenarios (e.g., a window hook that kills the window) so it is best to keep it simple.


@hrodstein - you must have the extra special build as I cannot get a crash in 37767 using my original kluge. I will however differe to others undoubtedly better knowledge of hook functions etc.

@cpr I will attach my test experiment. Please let me know if it crashes for you.

1. Open the experiment.

2. Execute "CreateHistoryCarbonCopy()".

3. Click at the end of the first line of text in the notebook.

4. Press Return.

Also please confirm that you are using 9.00B08 on Windows 32-bit.


@hrodstein - Now that does crash nicely...  however that is not the code in my example which explains the discrepency. I changed it to fix a bug... 

// my edited post
    case 8:             // window modified
        NoteBook HistoryCarbonCopy findText = {"•",2^4}
        if (V_flag)
            Notebook HistoryCarbonCopy setData = Secs2Time(datetime,3,1) + "\t" + Secs2Date(datetime,3) + "\t"

// your example experiment
    case 8:             // window modified
        NoteBook HistoryCarbonCopy selection={startOfPrevParagraph,startOfPrevParagraph},findText={"\r\n",2^0  + 2^4}
        Notebook HistoryCarbonCopy setData = Secs2Time(datetime,3,1)+"\t"+Secs2Date(datetime,3)+"\t"

We're both right, ballance is restored!

You have demonstrated that it is possible to use the Notebook operation from a modified event without crashing and that it is possible to make Igor crash using the Notebook operation from a modified event.

I'm not sure if there is a solution for the crash short of making it illegal to call the Notebook operation from a modified event. I will continue investigating.


For Igor Pro 9 only, I have change the way notebook modified messages are sent. The change is in the Igor Pro 9 nightly build now available.

Previously notebook modified messages were sent from the internal Igor code that triggered the modification. This could cause a crash if a window hook function modified the notebook because the message was sent before the original modification was complete.

Now (build 37772 or later), the notebook modified event is sent from Igor's outer loop when it is idling (i.e., after procedure execution finishes). This prevents the crash described above. It also means that, if you modify a notebook programmatically, the modified event is not sent to hook functions until Igor returns to idling. Also, modifying a notebook from a notebook modified event does not cause recursion to the window hook.