Tapatalk

Smart/contextual unindent (for Python scripts)

Smart/contextual unindent (for Python scripts)

1
NewbieNewbie
1

PostSep 12, 2017#1

I downloaded the latest version of UltraEdit and when I enter the following into a Python file that is indented by 4 spaces:

Code: Select all

if blah == 1:
    x = 1<Enter><Backspace>
I would expect the cursor to return to column 1. Most code editors that are Python aware perform this way. In UltraEdit, it simply deletes one character and leaves the cursor on column 4, where no valid text can be entered (per Python's indentation rules).

Is there any way to change that behavior?

6,823625
Grand MasterGrand Master
6,823625

PostSep 13, 2017#2

UltraEdit is not designed as Python aware code editor. So it has built-in no feature for smart removing spaces on pressing key BACKSPACE in a Python script file.

But UltraEdit can be customized and extended by their users using UltraEdit macros or scripts.

Here is a quickly coded scripting solution for replacing built-in BACKSPACE behavior by a script designed for smart unindent Python indents with 4 spaces.

Code: Select all

function SmartUnindent()
{
   var nIndentSpaces = 4;

   // Execute deletion with key BACKSPACE under following conditions:

   // The active file is opened in hex edit mode.
   if (UltraEdit.activeDocument.hexMode)
   {
      return;  // Do nothing because UltraEdit also does nothing
   }           // on pressing key BACKSPACE in hex edit mode.

   // There is a selection in active file.
   if (UltraEdit.activeDocument.isSel())
   {
      UltraEdit.activeDocument.key("BACKSPACE"); return;
   }

   // The caret is at beginning of a line.
   if (UltraEdit.activeDocument.isColNum(1))
   {
      UltraEdit.activeDocument.key("BACKSPACE"); return;
   }

   // The file extension is whether PY nor PYW.
   if ((!UltraEdit.activeDocument.isExt("py")) && (!UltraEdit.activeDocument.isExt("pyw")))
   {
      UltraEdit.activeDocument.key("BACKSPACE"); return;
   }

   // The character left to caret position is not a space character.
   UltraEdit.activeDocument.key("LEFT ARROW");
   if (!UltraEdit.activeDocument.isChar(" "))
   {
      UltraEdit.activeDocument.deleteText(); return;
   }

   // Smart delete 1 to 4 spaces.
   var nStartColumn = UltraEdit.activeDocument.currentColumnNum + 1;

   var nFirstColumnNumber = 1;
   // The next block is needed only for UltraEdit for Windows < v16.00 and
   // UEStudio < v10.00 on which property currentColumnNum has value 0 for
   // the first column in a line, 1 for second column, and so on.
   if (typeof(UltraEdit.activeDocumentIdx) == "undefined")
   {
      nStartColumn++;
      nFirstColumnNumber = 0;
   }

   var nDeleteSpaces = nIndentSpaces - 1;  // One space is already counted.

   // The loop below is exited before defined number of spaces being counted
   // if caret is already at first column in current line or the character
   // at current position in file is not a space character. In second case
   // are deleted just the spaces up to first non space character. The loop
   // is also exited if the current position of caret in file is at a tab
   // stop value / indent spaces position independent on how many spaces
   // have been already processed for being deleted next.
   do
   {
      if (UltraEdit.activeDocument.isColNum(1)) break;
      if (((UltraEdit.activeDocument.currentColumnNum - nFirstColumnNumber) % nIndentSpaces) == 0) break;
      UltraEdit.activeDocument.key("LEFT ARROW");
      if (!UltraEdit.activeDocument.isChar(" "))
      {
         UltraEdit.activeDocument.key("RIGHT ARROW");
         break;
      }
   }
   while(--nDeleteSpaces);

   UltraEdit.activeDocument.gotoLineSelect(0,nStartColumn);
   UltraEdit.activeDocument.deleteText();
}

if (UltraEdit.document.length > 0)  // Is any file opened?
{
   SmartUnindent();
}
Copy and paste the script code into a new ANSI encoded file with DOS line terminators and save it for example with file name Python Smart Unindent.js in directory %APPDATA%\IDMComp\UltraEdit\Scripts whereby directory Scripts must be first created on saving the file.

Then add this script to Script List. You have not posted which version of UltraEdit your are using in which GUI mode. Therefore I can't post a step by step instruction how to add a script to Script List suitable for your version of UltraEdit in used GUI mode.

Assign Shift + BACKSPACE as hotkey to the script. It is not possible to assign just BACKSPACE directly to the script as this key is used also in the dialog window to delete existing hotkey assignment.

You can already test now on an opened *.py or *.pyw file the script behavior on pressing Shift + BACKSPACE.

Exit UltraEdit and open %APPDATA%\IDMComp\UltraEdit\uedit*.ini in Windows Notepad. The exact file name depends on version of Windows which I don't know as not posted by you. Search in INI file of UltraEdit for Shift + BACKSPACE and replace the single occurrence by just BACKSPACE. Save the INI file and exit Windows Notepad. Now the script is executed by UltraEdit on pressing key BACKSPACE.

For this script it is definitely useful to open in UltraEdit for Windows Advanced - Settings or Configuration - Scripting and uncheck Show status information in output window and Show cancel dialog. But please note that these two options are global settings for all UltraEdit scripts. When you ever develop an UltraEdit script in future by yourself, you should enable both settings again for the duration of development and testing your script.

I wrote first a scripting solution as I was not sure if the task could be done also with an UltraEdit macro because variables are not supported in macros. But after finishing script coding task I think this smart unindent could be also coded as UltraEdit macro with a different approach. A macro solution would be faster on execution than the scripting solution. But I decided to first post the already completed scripting solution and wait on your reply before coding a macro solution.

Edit on 2017-09-17: The script was enhanced to stop at an indent spaces position of 4 for unindent when the number of spaces in current line is not a multiple of 4.

41
NewbieNewbie
41

PostDec 27, 2024#3

Mofi, I just discovered this script and it is brilliant. Thank you. It is working on UltraEdit 2024.2 on Windows 11. The performance is acceptable, however it would be great to have a macro version if that will be faster. If you find the time to write a macro version it will be most appreciated. Cheers.

6,823625
Grand MasterGrand Master
6,823625

PostDec 31, 2024#4

A macro version for deleting 1 to 4 spaces left the current position of the caret in a *.py or *.pyw file is:

Code: Select all

InsertMode
ColumnModeOff
IfSel
Key BACKSPACE
ExitMacro
EndIf
IfColNum 1
Key BACKSPACE
ExitMacro
EndIf
IfExtIs "py"
Else
IfExtIs "pyw"
Else
Key BACKSPACE
ExitMacro
EndIf
EndIf
Key LEFT ARROW
IfCharIs 32
Delete
Else
Delete
ExitMacro
EndIf
Loop 3
IfColNum 1
ExitLoop
EndIf
IfColNum 5
ExitLoop
EndIf
IfColNum 9
ExitLoop
EndIf
IfColNum 13
ExitLoop
EndIf
IfColNum 17
ExitLoop
EndIf
IfColNum 21
ExitLoop
EndIf
IfColNum 25
ExitLoop
EndIf
IfColNum 29
ExitLoop
EndIf
IfColNum 33
ExitLoop
EndIf
IfColNum 37
ExitLoop
EndIf
IfColNum 41
ExitLoop
EndIf
IfColNum 45
ExitLoop
EndIf
IfColNum 49
ExitLoop
EndIf
Key LEFT ARROW
IfCharIs 32
Delete
Else
Key RIGHT ARROW
ExitLoop
EndIf
EndLoop
The macro with indents for better understanding:

Code: Select all

InsertMode
ColumnModeOff
IfSel
    Key BACKSPACE
    ExitMacro
EndIf
IfColNum 1
    Key BACKSPACE
    ExitMacro
EndIf
IfExtIs "py"
Else
    IfExtIs "pyw"
    Else
        Key BACKSPACE
        ExitMacro
    EndIf
EndIf
Key LEFT ARROW
IfCharIs 32
    Delete
Else
    Delete
    ExitMacro
EndIf
Loop 3
    IfColNum 1
        ExitLoop
    EndIf
    IfColNum 5
        ExitLoop
    EndIf
    IfColNum 9
        ExitLoop
    EndIf
    IfColNum 13
        ExitLoop
    EndIf
    IfColNum 17
        ExitLoop
    EndIf
    IfColNum 21
        ExitLoop
    EndIf
    IfColNum 25
        ExitLoop
    EndIf
    IfColNum 29
        ExitLoop
    EndIf
    IfColNum 33
        ExitLoop
    EndIf
    IfColNum 37
        ExitLoop
    EndIf
    IfColNum 41
        ExitLoop
    EndIf
    IfColNum 45
        ExitLoop
    EndIf
    IfColNum 49
        ExitLoop
    EndIf
    Key LEFT ARROW
    IfCharIs 32
        Delete
    Else
        Key RIGHT ARROW
        ExitLoop
    EndIf
EndLoop
There is no macro command to find out if the active file is opened in hex edit mode. Do not run this macro on a file in hex edit mode although with UE/UES v2024.2.0.39 nothing happens on running the macro on a file opened in hex edit mode which does not have the file extension .py or .pyw according to my tests. For Python files opened in hex edit mode the behavior is similar to behavior on Python file opened in text edit mode depending on caret is in hex area (always just one byte deleted) or in text area (one to four bytes deleted depending on byte values).

UltraEdit macros do not support variables. That is the reason for the sequence of IfColNum number with ExitLoop and EndIf. If the macro should support even more indent levels, there must be added some more of these command sequences. The numbers and the Loop number must be edited if something other than four spaces is used for indents in Python files.

The macro can be saved together with other often needed macros in a macro file being configured for automatic load on startup of UltraEdit. The binary code of the macro saved with UltraEdit/UEStudio v2024.2.0.39 is:

Code: Select all

0000h: 55 45 2D 4D 61 63 72 6F 31 00 88 00 50 00 79 00 ; UE-Macro1.ˆ.P.y.
0010h: 74 00 68 00 6F 00 6E 00 55 00 6E 00 69 00 6E 00 ; t.h.o.n.U.n.i.n.
0020h: 64 00 65 00 6E 00 74 00 00 00 00 00 00 00 00 00 ; d.e.n.t.........
0030h: 22 00 00 00 E0 00 00 00 04 08 00 00 20 49 20 6D ; "...à....... I m
0040h: 20 0E 20 43 20 38 20 30 20 15 20 06 20 14 20 31 ;  . C 8 0 . . . 1
0050h: 20 43 20 38 20 30 20 15 20 06 20 8E 20 32 20 70 ;  C 8 0 . . Ž 2 p
0060h: 79 20 17 20 8E 20 33 20 70 79 77 20 17 20 43 20 ; y . Ž 3 pyw . C 
0070h: 38 20 30 20 15 20 06 20 06 20 4B 20 33 37 20 30 ; 8 0 . . . K 37 0
0080h: 20 10 20 31 20 20 20 30 20 2D 31 20 30 20 17 20 ;  . 1   0 -1 0 . 
0090h: 30 20 15 20 06 20 12 20 33 20 14 20 31 20 16 20 ; 0 . . . 3 . 1 . 
00A0h: 06 20 14 20 35 20 16 20 06 20 14 20 39 20 16 20 ; . . 5 . . . 9 . 
00B0h: 06 20 14 20 31 33 20 16 20 06 20 14 20 31 37 20 ; . . 13 . . . 17 
00C0h: 16 20 06 20 14 20 32 31 20 16 20 06 20 14 20 32 ; . . . 21 . . . 2
00D0h: 35 20 16 20 06 20 14 20 32 39 20 16 20 06 20 14 ; 5 . . . 29 . . .
00E0h: 20 33 33 20 16 20 06 20 14 20 33 37 20 16 20 06 ;  33 . . . 37 . .
00F0h: 20 14 20 34 31 20 16 20 06 20 14 20 34 35 20 16 ;  . 41 . . . 45 .
0100h: 20 06 20 4B 20 33 37 20 30 20 10 20 31 20 20 20 ;  . K 37 0 . 1   
0110h: 30 20 2D 31 20 30 20 17 20 4B 20 33 39 20 30 20 ; 0 -1 0 . K 39 0 
0120h: 16 20 06 20 13                                  ; . . .
The macro is named here PythonUnindent and has Shift + BACKSPACE as hotkey. The macro properties are:
  • Show cancel dialog for this macro … not checked
  • Continue if search string not found … checked
  • Disable screen refresh during macro playback not … checked
The two bytes 04 08 at byte offset 0038h define the hotkey. The first byte 04 must be modified to 00 for BACKSPACE by opening the macro file in UltraEdit in hex edit mode and changing this byte. 08 is the byte value of the control character respectively key BACKSPACE. The byte 04 before is a bit mask value for Ctrl (bit 0 … 01), (left) Alt (bit 1 … 02), (left) Shift (bit 2 … 04).

I tested the macro with modification of hotkey from Shift + BACKSPACE to BACKSPACE using the manual edit of the modifier byte in binary macro file on a *.py file opened in text edit mode with UE/UES v2024.2.0.39. The macro makes the deletion as usually done on pressing key BACKSPACE on being executed on files with a different extension than .py or .pyw or does nothing on active file is opened in hex edit mode and does not have the extension .py or .pyw.

41
NewbieNewbie
41

PostDec 31, 2024#5

Thanks Mofi!  The macro works perfectly and the performance improvement is noticeable, it seems instant.  Your clear instructions made it very easy to install.  I made one refinement, as shown below, so it will perform a single backspace whenever the character to the right of the caret is a space.  This change works well for my use but may not be needed for everyone.  

Thanks again for your great work, it is appreciated.  Now I need to retrain myself to hit the backspace key once instead of four times :).

Code: Select all

InsertMode
ColumnModeOff
IfSel
Key BACKSPACE
ExitMacro
EndIf
IfColNum 1
Key BACKSPACE
ExitMacro
EndIf
IfExtIs "py"
Else
IfExtIs "pyw"
Else
Key BACKSPACE
ExitMacro
EndIf
EndIf
IfCharIs 32
Key LEFT ARROW
Delete
ExitMacro
EndIf
Key LEFT ARROW
IfCharIs 32
Delete
Else
Delete
ExitMacro
EndIf
Loop 3
IfColNum 1
ExitLoop
EndIf
IfColNum 5
ExitLoop
EndIf
IfColNum 9
ExitLoop
EndIf
IfColNum 13
ExitLoop
EndIf
IfColNum 17
ExitLoop
EndIf
IfColNum 21
ExitLoop
EndIf
IfColNum 25
ExitLoop
EndIf
IfColNum 29
ExitLoop
EndIf
IfColNum 33
ExitLoop
EndIf
IfColNum 37
ExitLoop
EndIf
IfColNum 41
ExitLoop
EndIf
IfColNum 45
ExitLoop
EndIf
IfColNum 49
ExitLoop
EndIf
Key LEFT ARROW
IfCharIs 32
Delete
Else
Key RIGHT ARROW
ExitLoop
EndIf
EndLoop