Making the IDE My Own – Part 1

Sometimes I want additional functionality in the IDE. Over the years I have collected a small collection of “tweaks”.

I have a single tidy folder of small functions, each doing one thing. The session simply picks it up and applies it every time it starts. No need to save the session file any more! Let me show you some of what I’ve built and, more interestingly, how it works.

This is going to be a mini-series of blog posts. This first post will deal with the infrastructure and things that apply cross-platform. The next post will extend the graphical user interface (GUI) of the Windows IDE.

I’m assuming Dyalog v20.0 here. The principles hold in Dyalog v19.0, but you’d have to substitute any new features used, in particular, array notation, behind (), and ⎕VGET.

The StartupSession Folder

In my 2018 and 2020 posts on function keys, I kept this kind of thing in SALT’s Setup mechanism by using a Setup function in MyUCMDs\setup.dyalog that SALT runs at startup. That still works, but I have since migrated to the new session initialisation, which is altogether more versatile; it doesn’t only run code, it can also leave functions (and more) resident in ⎕SE afterwards.

The way it works is that, at startup, Dyalog populates the session namespace (⎕SE) from StartupSession folders within your documents folder, and, once everything has loaded, it automatically calls the Run function in each sub-folder of a StartupSession folder. There is one StartupSession folder pertaining to all versions of Dyalog plus a separate one for each of the versions installed.

For example, under Microsoft Windows, I’d place all the functions listed in this blog post into a folder I’ve named C:\Users\adam\Documents\Dyalog APL Files\StartupSession\seext (for session extensions). If I wanted it just for a specific version, such as Dyalog v20.0, I could put it into C:\Users\adam\Documents\Dyalog APL-64 20.0 Unicode Files\StartupSession\seext instead. If you also have older versions installed you might want to do exactly that, since the code as listed requires Dyalog v20.0 or later.

On other platforms, the corresponding folders would be /home/adam/dyalog.files/StartupSession/seext and /home/adam/dyalog.200U64.files/StartupSession/seext, for all versions and specific versions respectively.

The functions live in ⎕SE, so they are available even if the workspace is cleared or another one is loaded. They also do not interfere with the workspace content. I keep two types of functions side by side:

Feature
Begins with a lowercase letter. Enables a specific behaviour.
Utility
Begins with an uppercase letter. Supports the functionality of a feature.

In this post I’ll only be dealing with the first type, but I’ll prepare the special Run function (technically a utility) so it is ready for next time. Run is the bootstrapping function that calls the feature functions:

∇ Run args
 ⍝ Call niladic fns with lowercase initial except mentioned in exclude.config
  ;path;exclude
  :If ⎕NEXISTS path←args⊃⍛,'/exclude.config'
      exclude←⎕NGET path 1
  :Else
      exclude←0⍴⊂''
  :EndIf
  ⍎¨exclude~⍨{0=11 ⎕ATX ⍵}⍛/⎕A ⎕C⍛⎕NL ¯3
∇

The session initialisation code hands it a vector argument in which the first element specifies the directory from where it was loaded. The exclude-list’s filename is appended, and, if it exists, the exclude-list is read in as a vector of character vectors; otherwise, we just set a null list. Then comes a somewhat involved line, that benefits from being broken into chunks – reading these from right to left:

  1. ⎕A ⎕C⍛⎕NL ¯3 asks for the names. ⎕C is Case Convert. The behind operator modifies ⎕NL so that it pre-processes its left argument to be lowercased (actually case-folded, but it doesn’t matter for the basic Latin alphabet). So, we are really evaluating 'abcdefghijklmnopqrstuvwxyz' ⎕NL ¯3, that is, “give me the names of all functions (3) beginning with a lowercase Latin letter” as a nested vector of names.
  2. {0=11 ⎕ATX ⍵}⍛/ then filters that list. ⎕ATX reports extended attributes of a name; attribute 11 is the function’s valence, so 0= keeps only the niladic ones. The ⍛/ construct is my precious derived monadic filtering operator: Predicate⍛/names is Predicate filtering names, or (Predicate names)/names pre-behind.
  3. exclude~⍨ removes the features that have been manually excluded, if any.
  4. ⍎¨ calls each survivor of the culling.

The lowercase requirement isn’t arbitrary. In accordance with my personal naming convention, since a niladic function has the syntactic role of an array, its name needs a lowercase initial. This means that either test (lowercase initial or niladic valence) would already pick out exactly the features. Run checks both anyway; I like to err on the side of caution. Adding a feature or utility then simply involves dropping it into the folder; there is nothing to register, and as long as I adhere to my naming convention, it all just works. If you want to install the features, but want some disabled, create an exclude.config file with one feature function name on each line.

Now to the features themselves.

Output Settings

Let’s begin by tidying up appearances. setOutput configures how results are displayed:

∇ setOutput
 ⍝ Set ]Box and ]Rows
  ⎕SE.UCMD¨(
      '←OUTPUT.Box on -f=on -t=tree'
      '←OUTPUT.Rows on -s=long -fold=3'
  )
∇

These are the ]Box and ]Rows user commands. A leading on a user command invocation silences it, so the session comes up without two lines of Was OFF confirmations. The modifiers are where the behaviour lives (note that you can abbreviate any modifier as long as it stays unambiguous):

]Box
-t=tree
Short for -trains=tree, draws tacit functions as trees rather than boxes.
-f=on
Short for -fns=on, makes all of this apply to output produced inside functions (implicitly or using ⎕←), in addition to results and outputs that were requested at the Session level.
]Rows
-s=long
Short for -style=long, lets the session scroll horizontally instead of hard-wrapping long lines
-fold=3
Prevents a single tall result from flooding the screen; when output won’t fit vertically, the middle lines are replaced with leader dots and only the last 3 rows are shown.

Traditionally, you’d save your session file to preserve these output settings, but since our code runs at every startup, there’s no need for that.

One Log for Each Interpreter

109⌶ controls the file to which Dyalog writes its log of deprecated-feature usage. I run several interpreters – combinations of different versions, Unicode and Classic editions, 32- and 64-bit widths – and I don’t want them all writing to one file, so setLog gives each its own, named after the active interpreter and parked in the temporary directory:

∇ setLog
 ⍝ Set log file for usage of deprecated features
  ;tmp;Log;file;target;version;platform;type
  tmp←739⌶0
  Log←109⌶
  file←tmp,'/'
  (target version platform type)←# ⎕WG'APLVersion'
  file,←  3↑version∩⎕D
  file,←⊃'CU'⌽⍨80=⎕DR''
  file,←¯2↑'32',target∩⎕D
  file,←'.log'
  file Log 0
∇

(I rather enjoy the mnemonics of the two I-beams: 109 reads as the letters log, and 739 as TMP — a slanted T, a sideways M, a mirrored P). From APLVersion we keep the first three version digits, then choose 'U' or 'C' (80=⎕DR'' asks whether the empty character vector has a Data Representation of 80, meaning 8-bit Unicode, as opposed to 82 for 8-bit Classic, and 'CU'⌽⍨ rotates the pair so the correct letter falls first), then append '64' or '32' from the target platform. Under a Unicode-edition 64-bit Dyalog v20.0, this gives 200U64.log.

A Name and a Home

When I open a fresh interpreter (I do this a lot, and often have multiple running at the same time) to try something out, I often forget to save my noodling before I close the interpreter (or it crashes…). In addition, due to old habits, I might end up saving a workspace rather than using Link. autoLink solves these issues:

∇ {msg}←autoLink
 ⍝ If CLEAR WS, Link # to timestamped dir and set ⎕WSID
  ;tmp;∆DT;path
  tmp←739⌶0
  ∆DT←1200⌶
  path←⎕SE.Dyalog.Utils.Config'LOAD'
  :If ''≡path
  :AndIf 'CLEAR WS'≡⎕WSID
  :AndIf (⊂⍕#)~⍤∊(⎕VGET⊂'⎕SE.Link.Links'(ns:0)).ns
      path←tmp,⊃'/YYYYMMDD.hhmmss'∆DT 1 ⎕DT'J'
      ⎕←msg←⎕SE.Link.Create # path
      ⎕WSID←path,'/'
  :EndIf
∇

This function does three things:

  1. First, it checks whether there is anything already loaded in or linked to the root namespace (#).
  2. Then, if there is nothing, it creates a link between # and a new directory named with the current local timestamp.
  3. Finally, it sets the Workspace Identification (⎕WSID) to the path of the newly-created linked directory.

As you can see, I lean heavily on Dyalog v20.0 features here: ⎕VGET reads ⎕SE.Link.Links (Link’s memory of active links), but supplies the array-notation namespace (ns:0) as a default, so the line can’t fall over with a VALUE ERROR (if the list hasn’t even been created) or a NONCE ERROR (because there are no currently active links).

Now, every function I create or modify using the Editor is written straight to disk as source (a throwaway scratchpad that nonetheless survives an early shut-down or a crash).

Setting ⎕WSID has two positive effects:

  • Both Ride and the Windows IDE display ⎕WSID in the titlebar, this helps me with situational awareness when I have many interpreter instances running in parallel.
  • The trailing slash in ⎕WSID makes it an invalid filename, preventing me from accidentally saving the workspace (which would create a workspace with the same name as the directory, but with the file extension **.dws**, and with active links, both of which could lead to all sorts of unpleasantness), and further increases my situational awareness by telling me that the current workspace has a directory as source, rather than a binary blob workspace file.

To Be Continued…

So far, I have looked at the setup, making output readable, and ensuring code is saved. In my next post I’ll look at customising the GUI. In the meantime, the easiest way to try out anything from this post is to paste all the function definitions into a clear workspace and enter (with the directory that’s appropriate for you):

]Link.export # C:\Users\adam\Documents\Dyalog APL-64 20.0 Unicode Files\StartupSession\seext

If any of this is useful to you, take it. If it doesn’t quite do what you want, adapt it. If you have an idea for additional cross-platform Session extensions, let me know!

Leave a Reply

Your email address will not be published. Required fields are marked *