Resize Width of Front BBEdit Window According to Longest Line and then Center

Hey Folks,

I use plain text files in BBEdit for many things and frequently want to resize their windows according to the maximum line length of the content.

I finally got fed up with doing this manually and dug into AppleScriptObjC enough to script it.

I’ve subtracted the gutter width back out of the center equation so the text content is centered.

It looks like it’s not possible to directly detect whether BBEdit’s sidebar is open or not – much less detect its width – so I’ve got a feature suggestion in to the guys at Bare Bones.

I realize it’s possible to pole View > Window Appearance > Show/Hide Sidebar for its status, but this is crude and cannot reveal the width of the sidebar.)

If the guys at Bare Bones won’t expose the sidebar to AppleScript then I’ll probably get peeved enough to use Keyboard Maestro’s Find Image on Screen action to find unique elements on each side of it to calculate its width.

In the meantime the workaround is to close the sidebar before running the script if necessary.


Best Regards,
Chris

----------------------------------------------------------------
# Auth: Christopher Stone { Kudos to Shane Stanley for his generous help with ASObjC }
# dCre: 2018/09/16 22:24
# dMod: 2018/09/17 00:24
# Appl: BBEdit
# Task: Resize width of front BBEdit window according to longest line and center.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @BBEdit, @Resize, @Width, @Front, @Window, @Longest, @Line, @Center
----------------------------------------------------------------
use AppleScript version "2.4" -- macOS 10.10 or later
use framework "Foundation"
use framework "AppKit"
use scripting additions
----------------------------------------------------------------
property fontName : "Menlo-Regular"
property fontSize : "14.0"
----------------------------------------------------------------

set screenWithMenu to current application's NSScreen's screens()'s objectAtIndex:0
set screenWithMenuFrameSize to (screenWithMenu's frame()'s |size|()) as list
set screenWidth to (item 1 of screenWithMenuFrameSize) as integer

set maxLineLength to 0
set maxLineContent to missing value

tell application "BBEdit"
   tell front text window
      repeat with theLine in its lines
         if (length of theLine) > maxLineLength then
            set maxLineLength to (length of theLine)
            set maxLineContent to (contents of theLine)'s contents
         end if
      end repeat
   end tell
end tell

set theFont to current application's NSFont's fontWithName:fontName |size|:fontSize
set theAtts to current application's NSMutableDictionary's ¬
   dictionaryWithObject:theFont forKey:(current application's NSFontAttributeName)

set atrString to (current application's NSAttributedString's alloc()'s initWithString:maxLineContent attributes:theAtts)
set theSize to atrString's |size|()
set lineWidth to round (width of theSize)

if lineWidth < 600 then
   error "Text content is too narrow to resize the window!"
end if

set gutterWidth to 64
set gutterPad to 18
set scrollbarWidth to 16
set newWindowWidth to lineWidth + gutterWidth + gutterPad + scrollbarWidth

tell application "BBEdit"
   tell front window
      
      if newWindowWidth > screenWidth then
         set {null, y1, null, y2} to (get its bounds)
         set its bounds to {0, y1, screenWidth, y2}
      else
         
         set {x1, y1, null, y2} to (get its bounds)
         set newBounds to {x1, y1, x1 + newWindowWidth, y2}
         set its bounds to newBounds
         
         tell newBounds
            set winWidth to (its item 3) - (its item 1)
         end tell
         
         set winPos to its position
         set newXPosition to round ((screenWidth - winWidth) / 2)
         set item 1 of winPos to (newXPosition - 64) -- I like a left offset.
         set its position to winPos
      end if
      
   end tell
end tell

----------------------------------------------------------------
1 Like

Probably missing something, but here getting an error in SD on line 2 of the main script:

set screenWithMenuFrameSize to (screenWithMenu's frame()'s |size|()) as list

{{0.0, 0.0}, {1440.0, 900.0}} doesn’t understand the “size” message.

FWIW on this (High Sierra) system we seem to get a pair of lists:

use AppleScript version "2.4"
use framework "Foundation"
use framework "AppKit"
use scripting additions

-- screenXYWH :: Bool -> Bool -> 
--               {x :: Int, y :: Int, w :: Int, h :: Int}
on screenXYWH(blnActiveScreen, blnVisibleOnly)
    set scr to current application's NSScreen
    if blnActiveScreen then
        set d to scr's mainScreen()
    else
        set d to scr's screens()'s objectAtIndex:(0)
    end if
    if blnVisibleOnly then
        set {{x, y}, {w, h}} to d's visibleFrame()
    else
        set {{x, y}, {w, h}} to d's frame()
    end if
    return {x:x, y:y, w:w, h:h}
end screenXYWH

on run
    w of screenXYWH(true, false)
end run

You can do:

set screenWithMenuFrameSize to screenWithMenu's frame()
set screenWidth to current application's NSWidth(screenWithMenuFrameSize) as integer
2 Likes

See here for workarounds:

2 Likes

I suspect your script is doing more work than it needs to. Instead of getting the longest line from BBEdit and then calculating its size, get the full contents f the window and calculate its size. It will probably be much quicker.

1 Like

Perhaps practical to read these directly from the BB front window’s
display font and display font size ?

use AppleScript version "2.4" -- macOS 10.10 or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

on run
    tell application "BBEdit"
        tell front text window
            set recWH to my ¬
                stringSizeInFontAtPointSize(display font, display font size, contents)
        end tell
    end tell
    
    
end run

-- stringSizeInFontAtPointSize :: String -> Num -> String
--                      -> {width:Num, height:Num}
on stringSizeInFontAtPointSize(fontName, nPoints, str)
    set ca to current application
    if 0 < (length of str) then
        ((ca's NSAttributedString's alloc()'s ¬
            initWithString:str attributes:(ca's NSMutableDictionary's ¬
                dictionaryWithObject:(ca's NSFont's fontWithName:fontName |size|:nPoints) ¬
                    forKey:(ca's NSFontAttributeName))))'s |size|()
    else
        {width:0, height:0}
    end if
end stringSizeInFontAtPointSize

1 Like

Hey Shane,

You were right indeed.

I had a long document recently that choked, so I rewrote the script as you suggested.

Is there a significantly faster method to get the longest line than than the canon?

set AppleScript's text item delimiters to linefeed
set testList to text 2 thru -2 of "
one
two
three
four
five
six
seven
eight
nine
ten
"
set testList to text items of testList
set longestLineLength to 0
set longestLineText to null

repeat with i in testList
   set lineLength to length of (contents of i)
   if lineLength > longestLineLength then
      set longestLineLength to lineLength
      set longestLineText to contents of i
      ""
   end if
end repeat

{longestLineLength, longestLineText}

-Chris

Your original script was on the right track, but instead of passing the longest line, you should pass the document’s whole contents. So initWithString:maxLineContent would become initWithString:docContents, where docContents is all the text of the document.