Controlling windows and tabs in Script Editor

Hello,
does anyone know of a way to open Applescripts directly into a new tab of an already existing window in the Script Editor?
Or, perhaps merge two windows into a single multi-tabbed window?
I’m trying to open multiple scripts at once from an established projects-list via the menubar’s Script Menu.
That works nicely, but it would be better if they didn’t each open separately!
Even opening them all at once into a single window would be a step forward.

Here are two examples of my failed attempts to open or move scripts into tabs:

tell application "Script Editor"
	set oldDoc to first document
	set oldWindow to first window of oldDoc
	set newDoc to make new document
	set newWindow to first window of newDoc
	--tell oldWindow to make new document -->  	fails to make new tab
	--tell oldWindow to make new tab -->  			fails to make new tab
	--tell oldWindow to open (choose file) -->  	fails to make new tab
	--move newWindow to oldWindow -->  		fails to merge windows    
end tell
tell application "Script Editor"
	set oldDoc to first document
	set oldWindow to first window of oldDoc
	set newDoc to make new document
	set newWndw to first window of newDoc
end tell
tell application "System Events"
	tell process "Script Editor"
		#OK--> return properties of newWndw
		set theWndw to window (name of newWndw)
		#OK--> return UI elements of theWndw  
		set theTabGroup to tab group 1 of theWndw
		#OK--> return UI elements of theTabGroup
		--make new document at the end of theTabGroup-->  	fails to make new tab
		--tell theTabGroup to make new document-->  			fails to make new tab
		--make new document in theTabGroup-->  			fails  to make new tab
		--move theTabGroup to oldWindow-->  				fails to merge windows 
	end tell
end tell

Thanks in advance for your ideas!

Model: iMac Pro (2017)
AppleScript: 2.7
Browser: Safari 605.1.15
Operating System: macOS 10.14

Update: I dug around in some old code and found some partial solutions: I can break a tab away from the other tabs in a window by toggling it’s visibility. But not join it to an existing window…yet.


# BREAKS TAB FREE OF FELLOW TABS INTO IT's OWN WINDOW
set trgt to name of window 2 --whichever tab is focused among several tabs in a window.
set tabToMove to first window whose visible is true and name contains trgt
set index of tabToMove to 1
set visible of tabToMove to false
set visible of tabToMove to true

To glue multiple .applescript files selected by the user does not need to open multiple documents, windows or tabs. And then index, close, control or manage them special way, etc… One document, one window. See here:


set scriptsLocation to path to scripts folder from user domain
set appleScriptFiles to choose file of type "applescript" default location scriptsLocation with multiple selections allowed

set combinedScriptText to ""
repeat with |appleScriptFile| in appleScriptFiles
	tell application "Finder" to set theName to name of |appleScriptFile|
	set combinedScriptText to combinedScriptText & "-------------------" & theName & "-------------------" & return & return & (read (contents of |appleScriptFile|)) & return & return
end repeat

tell application "Script Editor"
	activate
	make new document with properties {name:"combinedScript", text:combinedScriptText}
end tell


To glue multiple .scpt files selected by the user:


set scriptsLocation to path to scripts folder from user domain
set appleScriptFiles to choose file of type "osas" default location scriptsLocation with multiple selections allowed

tell application "Script Editor"
	activate
	set oldDocument to open (get item 1 of appleScriptFiles)
	
	set text of oldDocument to return & "--------------- " & name of oldDocument & " ---------------" & return & return & text of oldDocument
	if (count appleScriptFiles) < 2 then return
	repeat with i from 2 to count appleScriptFiles
		set newDocument to open (get item i of appleScriptFiles)
		set text of oldDocument to text of oldDocument & return & "--------------- " & name of newDocument & " ---------------" & return & return & text of newDocument
		close newDocument saving no
	end repeat
	set name of oldDocument to "combinedScript"
end tell

Thanks KniazidisR, but while those work well to combine scripts. I am seeking to manipulate window tabs without making a new file and without changing an existing file.

Specifically, I wish to place an existing script as a second tab in an existing window.

And, if possible, move that tab to another existing window.

Currently, I can either close an existing tab or dismount it from it’s parent window into a new single-tabbed-window. But I cannot join tabs together into one parent window.

Your ideas would make an excellent component-sourced script-builder though!

I think you will not be able to do this, since the Script Editor dictionary has no division into windows and tabs. There are only independent windows that seem like Safari-like tabs to you.

In this case, we must forget about manipulating tabs, since there are none. As there are no parent windows, but there are just childless windows (no tabs).

KniazidisR wrote:

I am able to find many references to the window components as well as their tabs by perusing the UI elements. I was able to extract such things as filename, tab quantity, script contents, etc. But not the tab’s filepath which I think would be a good clue.

Since the windows and their tabs exist, so too must their code. Here are some pieces I dug up…

tell application "System Events"
	tell application process "Script Editor"
		
		set w to window 2
		#tell w to return properties of UI element 1 --
		
		tell w to set theAXSplitGroup1 to splitter group 1 of splitter group 1
		#tell w to return UI elements of theAXSplitGroup1
		#tell w to return properties of theAXSplitGroup1
		
		tell w to set theScrollArea1 to scroll area 1 of splitter group 1 of splitter group 1
		#tell w to return UI elements of theScrollArea1
		#tell w to return properties of theScrollArea1
		#tell w to return properties of text area 1 of theScrollArea1 --"script source"
		#tell w to return properties of scroll bar 1 of theScrollArea1--"scroll bar"
		
		tell w to set theSplitter1 to splitter 1 of splitter group 1 of splitter group 1
		#tell w to return UI elements of theSplitter1--{}
		#tell w to return properties of theSplitter1--"splitter"
		
		tell w to set theGroup1 to group 1 of splitter group 1 of splitter group 1
		#tell w to return UI elements of theGroup1
		#tell w to return properties of theGroup1--{}
		#tell w to return properties of static text "Result" of theGroup1 --"Result"
		#tell w to return properties of scroll area 1 of theGroup1
		#tell w to return UI elements of scroll area 1 of theGroup1
		#tell w to return properties of text area 1 of scroll area 1 of theGroup1--description:"script result"
		#tell w to return properties of scroll bar 1 of scroll area 1 of theGroup1
		
		tell w to return properties of splitter group 1 of splitter group 1
		tell w to return splitter 1 of splitter group 1
		tell w to return UI element of UI element 7 --{pop up button 1 of group 1 of window "Untitled 225.scpt" of application process "Script Editor" of application "System Events", pop up button 2 of group 1 of window "Untitled 225.scpt" of application process "Script Editor" of application "System Events"}
		
		tell w to set languageAXPopUpButton to pop up button 1 of group 1 --help:"Script language", 
		tell w to set elementsAXPopUpButton to pop up button 2 of group 1 --"Script navigation"
		tell w to set theAXGroup to UI element 1 --
		tell w to set theDescriptionButton to UI element 2 --"Show or Hide the Description"
		tell w to set theResultButton to UI element 3 --"Show or Hide the Result"
		tell w to set theLogButton to UI element 4 --"Show or Hide the Log"
		tell w to set theStatusButton to UI element 5 --"Reveal Status Source"
		tell w to set theAXStaticText to UI element 6 --"User canceled."
		tell w to set theAXSplitGroup to UI element 7 --"AXSplitGroup"
		tell w to set theAXToolbar to UI element 8 --"toolbar"
		tell w to set theAXTabGroup to UI element 9 --"Tab bar, 2 tabs"
		tell w to set theAXStaticText to UI element 10 -- "text"
		tell w to set theAXCloseButton to UI element 11 -- "close button"
		tell w to set theAXFullScreenButton to UI element 12 -- "this button also has an action to zoom the window"
		tell w to set theAXMinimizeButton to UI element 13 -- "minimize button"
		tell w to set theEditedButton to UI element 14 -- "document actions"
		tell w to set theAXImage to UI element 15 -- name:"Untitled 225.scpt", description:"image"
		tell w to set theAXStaticText2 to UI element 16 -- name:"Untitled 225.scpt", description:"text
		tell w to return properties of UI element 1 --
		
		tell w to return properties of UI element -2 --{minimum value:missing value, orientation:missing value, position:{-1075, 26}, class:image, accessibility description:missing value, role description:"image", focused:false, title:"Untitled 225.scpt", size:{16, 16}, help:missing value, entire contents:{}, enabled:true, maximum value:missing value, role:"AXImage", value:missing value, subrole:missing value, selected:missing value, name:"Untitled 225.scpt", description:"image"}
		
		tell w to return properties of UI element -1 --{minimum value:missing value, orientation:missing value, position:{-1057, 26}, class:static text, accessibility description:missing value, role description:"text", focused:false, title:missing value, size:{109, 16}, help:missing value, entire contents:{}, enabled:true, maximum value:missing value, role:"AXStaticText", value:"Untitled 225.scpt", subrole:missing value, selected:missing value, name:"Untitled 225.scpt", description:"text"}
		
		tell w to return name of every UI element --{missing value, missing value, missing value, missing value, missing value, "User canceled.", missing value, missing value, "tab bar", "—", missing value, missing value, missing value, "Edited", "Untitled 225.scpt", "Untitled 225.scpt"}
		
		tell w to set tb to UI element "tab bar"
		
		#tell tb to return its properties -- {minimum value:missing value, orientation:missing value, position:{-1333, 90}, class:tab, accessibility description:"Tab bar, 1 tab", role description:"tab group", focused:false, title:"tab bar", size:{700, 26}, help:missing value, entire contents:{}, enabled:missing value, maximum value:missing value, role:"AXTabGroup", value:radio button "Untitled 225.scpt" of tab group "tab bar" of window "Untitled 225.scpt" of application process "Script Editor" of application "System Events", subrole:missing value, selected:missing value, name:"tab bar", description:"Tab bar, 1 tab"}
		
		#tell tb to return every radio button --{radio button "Untitled 225.scpt" of tab group "tab bar" of window "Untitled 225.scpt" of application process "Script Editor" of application "System Events"}
		
		#tell tb's first radio button to return its properties --{radio button "Untitled 225.scpt" of tab group "tab bar" of window "Untitled 225.scpt" of application process "Script Editor" of application "System Events"}
		
		--tell tb to make new radio button --error "System Events got an error: AppleEvent handler failed." number -10000
		
	end tell
end tell

This is a slightly cleaner way to hunt for clues I suppose.
I found these possible clues using the script below :

AXChildrenInNavigationOrder

«class radB» "Untitled 227" of «class tabg» "tab bar" of window "Untitled 225.scpt"

set rprt to return
# BIG PRINTOUT OF ELEMENTS & VALUES...
tell application "System Events"
	tell application process "Script Editor"
		set w to window 2
		tell w to repeat with UIE in every UI element
			tell UIE to repeat with EA in every attribute
				try
					tell EA to return its properties as text
				on error err
					set errSTRNG to text (offset of "{" in err) thru (offset of "} into" in err) of err
					set rprt to rprt & errSTRNG & return
				end try
			end repeat
		end repeat
	end tell
end tell
return rprt

After modifying the above script to label the elements it finds,
this was the only UI element I found which simultaneously mentioned both test tabs
(Untitled 225.scpt, Untitled 227.scpt).

version 2

# BIG PRINTOUT OF ELEMENTS & VALUES...v2
set rprt to return
tell application "System Events"
	tell application process "Script Editor"
		set w to window 2
		tell w to repeat with UIE in every UI element
			set rprt to rprt & return & name of UIE & return
			tell UIE to repeat with EA in every attribute
				try
					tell EA to return its properties as text
				on error err
					set errSTRNG to text (offset of "{" in err) thru (offset of "} into" in err) of err
					set rprt to rprt & errSTRNG & return
				end try
			end repeat
		end repeat
	end tell
end tell
return rprt

:cool: And this is actually makes a new tab…

tell application "System Events"
	click button 1 of tab group "tab bar" of window 1 of application process "Script Editor"
end tell

And this script allows you to toggle between two tabs in another window!
Apparently the tabs are considered “radio buttons” in “Script Editor”.

property theOtherTab : 1
tell application "System Events"
	tell tab group "tab bar" of window 2 of application process "Script Editor"
		set {rb1, rb2} to {radio button 1, radio button 2}
		if theOtherTab = 1 then
			set {theOtherTab, theTabsRAdioButton} to {2, rb1}
		else if theOtherTab = 2 then
			set {theOtherTab, theTabsRAdioButton} to {1, rb2}
		end if
		click theTabsRAdioButton
	end tell
end tell

update: the role description of the radio button is clearly labeled as being a “tab”.

FWIW, scripting of tabs is pretty simple in Script Debugger 8. To combine two windows, you use the parent window property:

tell application id "com.latenightsw.ScriptDebugger8" -- Script Debugger.app
	set parent window of script window 1 to script window 2
end tell

To move a tab out of a tab group, you set its tabbed property to false:

tell application id "com.latenightsw.ScriptDebugger8" -- Script Debugger.app
	set tabbed of script window tab 2 of script window 1 to false
end tell

That is beautiful and intuitive Shane!

I thought I’d give Fredrik71’s suggestion a try and it works quite well. On a reasonably quick computer, the delay is unnecessary and the GUI scripting is almost imperceptible.

-- create a script on the desktop with the name "New Script.scpt"
-- then open this script in Script Editor and run

tell application "Script Editor"
	open file ((path to desktop as text) & "New Script.scpt")
end tell

delay 0.2 -- test different values or use a repeat loop

tell application "System Events" to tell process "Script Editor"
	click menu item "Merge All Windows" of menu "Window" of menu bar 1
end tell

BTW, I tested this on macOS 10.15.7–I don’t know if it works on macOS 10.14.

Thanks Fredrik71 and peavine,

Does merge allow omission of certain scripts or is it all-or-nothing? I have multiple monitors with scripts positioned side by side on each monitor. Merging EVERY window into one would not work for what I’m doing. The best solution so far seems to be the discreet control method suggested by Shane, where I can individually move any tab to any other window.

I already have a nice monitor specific window arranger and other handy window positioning scripts in my menubar, but sorting and arranging tabbed groups would be very helpful fo my current project which is migrating thousands of old scripts to a new machine.

FYI, other scripts in the project search the cloud, networked machines, and several old drives for applescripts, droplets, or applets, then score them based on contents, and finally open selected items into window-groups for review. Controlling tabs would tidy up that part of the process.

So far I can generate a new blank tab, close any specific tab, but not open a script directly into a tab in a specified window, nor move an existing tab to another window.

Thanks Fredrik71, I think of couple of those lines will definitely be useful in the final script!

Fredrik71 wrote:

I’ve find that the “every window” command usually includes hidden or preference windows. (it did when i tested your idea.) I typically begin calling windows by their document first to ensure they have a file associated with them.

You can see the difference here where some windows are referenced with negative numbers…

set everyWindow to every document's first window -->{window id 698, window id 410, window id 574, window id 168, window id 438, window id 517, window id 178, window id 179, window id 258}

set everyWindow to every window -->{window id 698, window id 410, window id 574, window id 168, window id 438, window id 517, window id 178, window id 179, window id 258, window id -1, window id 534, window id -1}

Fredrik71 wrote:

Sadly, my new machine is not remembering window positions well. Sometimes when I restart, all the windows are jumbled onto one monitor. I’ve even had the desktop completely rearranged when one machine in iCloud changed all the others. So I’ve had to be very careful to control layouts on machines and monitors with my own scripts instead of relying on Apple. Catalina on an iMacPro with 3 monitors =buggiest ScriptEditor ever.