A ConsoleIO Library for AppleScript.

Thanks to Kel for his participation in this. :slight_smile:

Output

Introduction

Output provides a channel to funnel output through from an AppleScript other than the log pane of your favourite AppleScript Editor, or the Console.app.

Output is a library, that gives you access to a kind of console output like you find in Pascal and C. But since we are using Mac’s we’d rather have the output printed into a text document, that we may have in the foreground if we so wishes, and watch the updating. Or have it in the background and optionally come to front when our AppleScript has stopped printing its output.

This library is designed in order to let you continue to actually use TextEdit, by entering text and move windows around while “something” is outputing “something” to one of the documents that are open.

The library support two types of output situations, the “one off” where you don’t bother to save the resulting output, and the one where we have a setup with files, we provide a scheme where the files are numbered sequentially ascending, like duplicates in the finder, so it is easy to open the two latest files with FileMerge and see the differences for instance. Every file, that gets to be saved or moved with a backup extension will open in Finder when double clicked, to have as easy access as possible.

The original idea came to me from Nigel Garvey. I have elaborated and decorated, until I made this library, which are intended to serve multiple purposes. The main motivation is to have a “printing environment” for as diverse purposes as to see test results from unit tests, to gathering of output of shell script that are exectuted from TextEdit, to have an environment to see results of calculations, something AppleScript really provides a very good environment for. It only lacked a decent uniform way to get results out, like console IO. Here the console IO is programmatically saveable, and can be incorporated into workflows that involves seeing differences between files.

Restrictions

One inclusion of the library can only handle writing and saving to one document, if you want to simulate standard output, and standard error, then you must use two instances of the library, for instance like this:

use AppleScript version "2.3"
use scripting additions
use std_out : script  "Output"
use err_out : script  "Output"

The names you use for reference doesn’t matter. You should however use named documents, when writing to several files simultaneously!

Usage

You start by saving the library into folder ~/Library/Script Libraries folder preferably under the name “Output.scpt”.

Then you must start a script with the following lines in order to use it.

use AppleScript version "2.3"
use scripting additions
use output : script  "Output"

Before you really start to use the interesting productive handlers, you should include the call below:

output's reset()

This assures that you are allowed to initialize new files during every run, like resetting a Pascal console IO device, before writing output to it.

The decision to make is whether you are going to use unsaved output, or if you are going to save the output. You must initalize a document in order to write to it, if you are going to save it, then you must initialize it with a name.

The intializers

The handlers initWithName and initWithTextAndName lets you initialize (create) a new document with a name.

The handlers init and initWithText initializes unnamed documents, perfect for getting rid off again.

All initializers lets you specify a header for the document with a font different from the regular font ( both configurable), and lets you set the inital window width, and height. There is also a handler positionFrontWindow(x, y) that lets you move the window to a preferred area of your screen real-estate.

Configuring output

There are handlers for setting font, (setHeaderdFont and setRegFont and fontsize (setHeaderFontSize and setRegFontSize for the Header font, and the Regular font. I personally really think monospaced fonts are best.

Writing output

The handlers append and appendNL lets you write text to named documents. If you want to fill the document with text in one go, as a coherent step to creating the document, then use the handler initWithTextAndName.

The handlers printit and writeLn lets you write text to unnamed documents. If you want to fill the document with text in one go, as a coherent step to creating the document, then use the handler initWithText.

Saving documents

Saving documents can be done after several schemes, you can plainly overwrite any previous document with the same name. (As long as it isn’t write protected.) Or the previous document can get a new extension, like ‘bak’ or ‘~’ for that matter, or the documents can be numbered from 1 up to 999 subsequentially. When saving documents, then we try as best as we can to make them be opened by TextEdit again. But if the extension you have chosen is an extension known to Launch Services, and there is another default app for that extension, then the likelyhood of it being opened by TextEdit when you later on double click on it, is zero.

The folder you specify for storing the document can be either hfs, posix, or an alias to a folder for the greatest flexibility, if you don’t give up any by either specifying “”, null, or missing value, then your file will be saved to the desktop.

Seeing Results

There are two handlers for bringing the documents to front, one for bringing it to front at some intermediate point of execution time: bringToFront and a handler for assuring that the document are brought to front when a script has finished running: leaveAtFront (that handler takes a parameter of decimal seconds, to leave the document in front at an appropriate moment, when the script has really finished.

Detailed specification of the handlers

reset()

reset does: Reset the output system by setting theDoc to missing value so that initDoc works when your script starts to run.

  • Returns: Nothing.

  • Parameters:

None.

setRegFontSize(fsz)

setRegFontSize does: Set the regular font size of the document

  • Returns: Nothing

  • Parameters:

  • fsz: Fontsize in points.

setHeaderFontSize(fsz)

setHeaderFontSize does: Set the header font size of the document

  • Returns: Nothing.

  • Parameters:

  • fsz: Fontsize in points.

setRegFont(fonName)

setRegFont does: Sets the name of the font that is to be used for
regular output.

  • Returns: Nothing.

  • Parameters

  • fonName: Fontname as string.

setHeaderdFont(fonName)

setHeaderFont does: Sets the name of the font that is to be used for
header of the output.

  • Returns:

  • Parameters:

  • fonName: Fontname as string.

bringToFront()

bringToFront does: Brings the TextEdit document that receives the output (henceforth the receiver), to the front

  • Returns: Nothing

  • Parameters: None.

leaveAtFront(secondsToWait)

leaveAtFront does: Bring the receiver to front after an interval of time has passed. It is inteded to be run as the last operation of a script that uses the library.

  • Returns: Nothing

  • Parameters:

  • secondsToWait: The number of seconds to wait for bringing the doc to front, after it has been called. (This is intended for letting the script be finished, so the receiver window, really gets to stay on top.

– > Maybe check if we have the same document?

printit(txt)

printit does: Allow you to output text to unnamed documents, without an ending linefeed.

  • Returns: Nothing.

  • Parameters:

  • txt: The text to be output.

writeLn(txt)

writeLn does: Allow you to output text to unnamed documents, with an ending linefeed.

  • Returns:

  • Parameters: Nothing

  • txt: The text to be output.

append(txt)

append does: Allow you to output text to unnamed documents, without an ending linefeed.

  • Returns:

  • Parameters: Nothing

  • txt: The text to be output.

appendWithLn(txt)

appendWithLn does: Allow you to output text to named documents, with an ending linefeed.

  • Returns:

  • Parameters: Nothing

  • txt: The text to be output.

init(heading, WindowWidth, WindowHeight)

init does: Initiates a new unnamed document for output. Reset should have been called first, unless the script is guarranteed to run only once.

  • Returns: Nothing.

  • Parameters:

  • heading: The title of the document.

  • WindowWidth: The window width, 0 is an illegal value.

  • WindowHeight: The window heigth, 0 is an illegal value.

initWithText(heading, WindowWidth, WindowHeight, theText)

initWithText does: init does: Initiates a new unnamed document for output with text. Reset should have been called first, unless the script is guarranteed to run only once. The intended usage, is to gather all the output from say a do shell script, and then just init the document with all of the text and die after maybe having brought the document to front, as there is nothing more to be done. (Unless of course you have more output to print later in your script, that you want to go to the same file.)

  • Returns: Nothing.

  • Parameters:

  • heading: The title of the document.

  • WindowWidth: The window width, 0 is an illegal value.

  • WindowHeight: The window heigth, 0 is an illegal value.

  • theText: The text to be “printed” in the unnamed document.

initWithName(heading, WindowWidth, WindowHeight, docName, askToSave)

initWithName does: Initiates a new named document for output, so we can save it to disk later. Reset should have been called first, unless the script is guarranteed to run only once.

  • Returns: Nothing.

  • Parameters:

  • heading: The title of the document.

  • WindowWidth: The window width, 0 is an illegal value.

  • WindowHeight: The window heigth, 0 is an illegal value.

  • docName: The docname as text, not starting nor ending its name with “.”.

  • askToSave If we are to ask about saving when we initiate by closing any other TextEdit documents with the same name.

initWithTextAndName(heading, WindowWidth, WindowHeight, theText, docName, askToSave)

initWithTextAndName does: Initiates a new named document for output, so we can save it to disk later. Reset should have been called first, unless the script is guarranteed to run only once. The intended usage, is to gather all the output from say a do shell script, and then just init the document with all of the text and die after maybe having brought the document to front, as there is nothing more to be done. (Unless of course you have more output to print later in your script, that you want to go to the same file.)

  • Returns: Nothing.

  • Parameters:

  • heading: The title of the document.

  • WindowWidth: The window width, 0 is an illegal value.

  • WindowHeight: The window heigth, 0 is an illegal value.

  • theText: The text to be “printed” in the named document.

  • docName: The docname as text, not starting nor ending its name with “.”.

  • askToSave If we are to ask about saving when we initiate by closing any other TextEdit documents with the same name.

saveDoc(saveFolder, fileName, theScheme)

saveDoc does: Saves an already named document to disk, allowing for several different schemes to be used when a file with that name already exists.

  • Returns: Nothing.

  • Parameters:

  • saveFolder: Can be the posix path, hfs path, or an alias to an already existing folder. If the value of the parameter is null, missing value, or “”, then the file will be saved to Desktop.

  • fileName: Must be present, and can’t start with, nor end with “.”

  • theScheme: The “overwrite” scheme to be used. A value of null, or missing value, means that we will overwrite the file if it already exists. A value that is a string, and that doesn’t start with a “.”, is taken as a valid extension, and is used for renaming the old file that already resides on disk. If the parameter is number then the new file will get assigned the next higher number to it, up to 999.

positionFrontWindow(x, y)

positionFrontWindow does: Position the window according to the users wishes.

  • Returns:

  • Parameters:

  • x: the xpos in points (screen resolution).

  • y: the ypos in points (screen resolution).


Read the code for further documentation/ more details.


-- Copyright © 2013  McUsr and put in public domain.
-- An output library, to simulate an Output device, like in Pascal, or stdout in C.
-- To allow us to write and leverage upon "Console" programs in AppleScript.

-- rev 1:
-- Removed bug,when performing AXRaise.
-- 
-- Mark IV
-- null reinstated as a valid value for overwrite as the scheme parameter to saveDoc.
-- Handlers are documented.
-- printit, and writeLn are now moved back to their original intention.


-- We now keep the id for the document handy, it is an error to try to create another
-- document, and intersperse output to two documents, by using the same library.
-- include the library for a secon time, with another library name to reference it
-- to achieve that functionality.

-- It is also an error to try to print or Append to a document that isn' alread open.

-- What's new:
-- Restrictions:
-- 1 doc per library at a time, we now use a reference to the doc to be more bulletprof.
-- Conventions
-- We must now specify alwaysExtension to whether or not we'll keep a singular text extension,
-- or add it if no extension at all is present, the parameter (true/false) is added to reset()

-- Handlers
-- saveDoc, saves a doc, according to some scheme.

-- setRegFontSize
-- setHeaderFontSize
-- setRegFont
-- setHeaderFont

-- ResetOutput:
-- Reinitiates output, so we can open a new document for output.
-- What's next:
-- Ability to set a reference to an already existing doc.

use AppleScript version "2.3"
use scripting additions

property regFonSz : 13
property hedFonSz : 15
property regFont : "Andale Mono"
property hedFont : "Andale Mono"
property theDoc : missing value
property alwaysExtension : true

-- ******* Should always start by using the handler below, to initialize lib.*****
on reset()
	set theDoc to missing value
end reset
(* ======== Handlers for configuration =================*)

on setRegFontSize(fsz)
	set my regFonSz to fsz
end setRegFontSize

on setHeaderFontSize(fsz)
	set my hedFonSz to fsz
end setHeaderFontSize

on setRegFont(fonName)
	set my regFont to fonName
end setRegFont

on setHeaderFont(fonName)
	set my hedFon to fonName
end setHeaderFont

(* ============== HANDLERS FOR BRINGING A DOCUMENT TO FRONT ============ *)

on bringToFront()
	-- brings the front TextEdit document to the front
	-- without activating all its windows.
	try
		toFrontInTextEdit()
	on error e
		showErrorAndDie("I couldn't access the window I should have been able to access! " & e & " (bringToFront:toFrontInTextEdit)")
	end try
	
end bringToFront

-- Brings a document to front after a script has finished, after a delay..
on leaveAtFront(secondsToWait)
	-- displays the output window after it has run.
	-- (Sometimes it dissappears when a script is finished...)
	-- may need to adjust the interval
	try
		toFrontInTextEdit()
	on error e
		showErrorAndDie("I couldn't access the window I should have been able to access! " & e & " (leaveAtFront:toFrontInTextEdit)")
	end try
	do shell script "(sleep " & secondsToWait ¬
		& "; open -b com.apple.textedit) &>/dev/null &"
end leaveAtFront

(* ============= HELPER FOR ASSURING OUR TEXT WINDOW IS THE FRONTMOST WHEN WE NEED THAT! ==========*)

on toFrontInTextEdit()
	local wn_name
	tell application id "ttxt" to set wn_name to name of theDoc
	try
		tell application id "ttxt" to set idx_win to index of (item 1 of (get every window whose name is (name of theDoc)))
		
		tell application id "sevs" to ¬
			tell application process "TextEdit" to ¬
				tell window idx_win to ¬
					perform action "AXRaise"
	on error e number n
	end try
end toFrontInTextEdit

(* ====================== Positioning of window ======================*)
-- TODO: our window, not front window!
to positionFrontWindow(x, y)
	tell application id "sevs" to tell ¬
		application process "TextEdit" to set position of its front window to {x, y}
end positionFrontWindow

(* ========== HANDLERS FOR WRITING TO A DOCUMENT WITHOUT A NAME =============*)
on printit(txt)
	-- Prints a paragraph to the frontmost
	-- unsaved TextEdit document, creates one if needed.
	-- Nigel Garvey made the original version.
	-- Now optimized by using app id of TextEdit
	-- Doesn't care anymore if frontmost or what not.
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (printit)")
	tell application id "ttxt"
		try
			local probe, probe2
			set probe to path of theDoc
			set probe2 to probe
			--		if probe is not missing value then
			my showErrorAndDie("The handler can only be used with unnamed documents.(printit)")
			error number -128
			--	end if
		end try
		make new paragraph at end of text of my theDoc with data ¬
			txt ¬
				with properties {font:regFont, size:regFonSz}
	end tell
end printit

on writeLn(txt)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (writeLn)")
	tell application id "ttxt"
		try
			local probe, probe2
			set probe to path of theDoc
			set probe2 to probe
			--			if probe is not missing value then
			my showErrorAndDie("The handler can only be used with unnamed documents.(writeLn)")
			error number -128
			--		end if
		end try
		set txt to txt & linefeed
		
		make new paragraph at end of text of my theDoc with data ¬
			txt ¬
				with properties {font:regFont, size:regFonSz}
	end tell
end writeLn


(* ========== HANDLERS FOR WRITING TO A DOCUMENT WITH A NAME =============*)

on append(txt)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (append)")
	tell application id "ttxt"
		try
			local probe
			set probe to path of theDoc
		on error
			my showErrorAndDie("The handler can only be used with named documents.(append)")
		end try
		make new paragraph at end of text of my theDoc with data ¬
			txt ¬
				with properties {font:regFont, size:regFonSz}
	end tell
end append

on appendWithLn(txt)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (appendWithLn)")
	tell application id "ttxt"
		try
			local probe
			set probe to path of theDoc
		on error
			my showErrorAndDie("The handler can only be used with named documents.(appendWithLn)")
		end try
		set txt to txt & linefeed
		make new paragraph at end of text of my theDoc with data ¬
			txt ¬
				with properties {font:regFont, size:regFonSz}
	end tell
end appendWithLn


(* ========== HANDLERS FOR CREATING  NEW DOCUMENTS  =============*)
-- Makes a new unnamed document without any ado.

on init(heading, WindowWidth, WindowHeight)
	-- Creates a new TextEdit document for output,
	-- of a certain size, and puts it onto the screen.
	if theDoc is not missing value then ¬
		showErrorAndDie("The library isn't reset before trying to opening a new file for output.(init)")
	tell application id "ttxt" to set my theDoc to a reference to (make new document at front)
	initFrontDoc(heading, WindowWidth, WindowHeight)
end init

-- new unnamed document with text

on initWithText(heading, WindowWidth, WindowHeight, theText)
	script o
		property thl : {}
	end script
	my init(heading, WindowWidth, WindowHeight)
	my fillWithParagraphs(theText)
end initWithText


-- Creates new empty document With a name , you can specify to be asked if there
-- exists an old one, that has unsaved changes. (askToSave)

on initWithName(heading, WindowWidth, WindowHeight, docName, askToSave)
	if theDoc is not missing value then ¬
		showErrorAndDie("The library isn't reset before trying to opening a new file for output. (initWithName)")
	closeNamedDoc(docName, askToSave)
	tell application id "ttxt" to set my theDoc to a reference to (make new document at front with properties {name:docName})
	initFrontDoc(heading, WindowWidth, WindowHeight)
end initWithName

-- new document with text, asktosave (bool) makes you confirm closing any
-- unsaved documents with the same name.

on initWithTextAndName(heading, WindowWidth, WindowHeight, theText, docName, askToSave)
	if theDoc is not missing value then ¬
		showErrorAndDie("The library isn't reset before trying to opening a new file for output. (initWithTextAndName)")
	my initWithName(heading, WindowWidth, WindowHeight, docName, askToSave)
	my fillWithParagraphs(theText)
end initWithTextAndName



(* ========== HELPERS FOR CREATING NEW DOCUMENTS  =============*)
on initFrontDoc(heading, WindowWidth, WindowHeight)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (initFrontDoc)")
	if WindowWidth = 0 or WindowHeight = 0 then showErrorAndDie("You can't set either windowheight or window size to 0. (initFrontDoc)")
	tell application id "ttxt"
		tell contents of theDoc
			set text of it to heading & linefeed
			set font of every character of its text to hedFont
			set size of every character of its text to hedFonSz
		end tell
		local corrWin
		set corrWin to (first window whose name is (name of contents of theDoc))
		tell corrWin
			set _bounds to its bounds
			set item 3 of _bounds to ¬
				(item 1 of _bounds) + WindowWidth
			set item 4 of _bounds to ¬
				(item 2 of _bounds) + WindowHeight
			set its bounds to _bounds
		end tell
		-- activate
	end tell
end initFrontDoc

on closeNamedDoc(docName, askToSave)
	if askToSave then
		tell application id "ttxt" to ¬
			close (every document whose name is docName)
	else
		tell application id "ttxt" to ¬
			close (every document whose name is docName) saving no
	end if
end closeNamedDoc


on fillWithParagraphs(theText)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handle was executed. (fillWithParagraphs)")
	
	script o
		property thl : {}
	end script
	if theText ≠ "" then
		tell (a reference to text item delimiters)
			local tids
			set {tids, contents of it} to ¬
				{contents of it, {linefeed, return}}
			set o's thl to text items of theText
			set contents of it to tids
		end tell
		tell application id "ttxt" to tell my theDoc
			repeat with i from 1 to (length of o's thl)
				make new paragraph at end of its text ¬
					with data ¬
					((item i of o's thl) & linefeed) ¬
						with properties {font:regFont, size:regFonSz}
				
			end repeat
		end tell
	end if
	
end fillWithParagraphs

(* ========== HANDLER FOR  SAVING OUTPUT-DOCUMENTS  =============*)

-- saveDoc:
-- SaveFolder can be a posix path, hfs path, or an alias, if it is empty ("" or missing value) then
-- we use the path to desktop as our folder path.

-- It really needs a filename, and it can not start with, nor end with "."

-- theScheme has several values
-- null or missing value  --> just overwrite the original file!
-- "bak", "~" or  anything else, but it mustn't start with "."!
-- any former files  with the same filename at this path, gets moved to a file. with this extension.
-- if there was a file with the backup extension, then it is overwritten!

-- number, (class number) the filename gets a suffix, consisting of the next higher number from 1 upwards
-- (like in the finder).

-- We also need the document to be initiated with a name, in order to be able to save the Doc.


on saveDoc(saveFolder, fileName, theScheme)
	if theDoc is missing value then ¬
		showErrorAndDie("A document wasn't set to be the output document before this handler was executed. (savedoc)")
	
	-- Initial qualification of parameters.
	-- qualification of filename
	if fileName is "" then showErrorAndDie("You haven't specified a filename (savedoc)")
	local fnProbe
	tell application id "ttxt" to set fnProbe to name of theDoc
	if fnProbe is not fileName then showErrorAndDie("Error: You have to make a document with a *similiar* filename  in order to save it. withname(savedoc)")
	if text 1 of fileName is "." then showErrorAndDie("Illegal filename, no dotfiles (it starts with \".\")! (savedoc)")
	if text -1 of fileName is "." then showErrorAndDie("Illegal filename, (it ends with \".\")! (savedoc)")
	
	-- qualificaiton of scheme pertains only to when it is of the class  text
	if class of theScheme is text then
		if theScheme is "" then showErrorAndDie("You have to specify an extension for the backup file nanme. (savedoc)")
		if text 1 of theScheme is "." then showErrorAndDie("Illegal extension, (it starts with \".\")! (savedoc)")
	end if
	
	-- We normalize the filepath into an alias
	-- had it been posix path or hfs path, (an empty path resolves to Desktop).
	
	local posixFolderPath
	-- We must determine once and for all the save folder path and its posix folder path.
	if saveFolder is "" or saveFolder is null or saveFolder is missing value then
		set saveFolder to path to desktop folder as alias
		set posixFolderPath to POSIX path of saveFolder
	else if class of saveFolder is alias then
		set posixFolderPath to POSIX path of saveFolder
	else
		try
			if ":" is in saveFolder then
				set saveFolder to saveFolder as alias
			else if "/" is in saveFolder then
				set saveFolder to (POSIX file thePosixPath) as alias
			else
				-- it just contains a directory name which we take to be on the desktop.
				set saveFolder to ((path to desktop folder as text) & saveFolder) as alias
			end if
		on error e
			showErrorAndDie("The path to which the file was to be saved doesn't exist. " & e & "  (savedoc)")
		end try
		set posixFolderPath to POSIX path of saveFolder
	end if
	
	-- We determine if the file already exist, we check if the file on disk ends with ".txt", we really have
	-- to do that no matter what, since our text edit file may initially be created with a txt extension.
	local didExist, realName
	repeat 1 times -- a block construct to add scope.
		local fn, fileProbe
		set {fn, didExist} to {posixFolderPath & fileName, true}
		try
			set fileProbe to (POSIX file fn) as alias
			-- if the file does exist, and the file with a txt extension also exist, then we do have 
			-- a problem!
		on error
			set didExist to false
		end try
		
		if not didExist then
			-- we'll also check out if we have a file with the suffix
			tell application id "sevs"
				set fileProbe to (get every file of saveFolder whose name begins with fileName)
				if fileProbe is not {} then
					set realName to name of item 1 of fileProbe
					set didExist to true
				else
					set realName to ""
				end if
			end tell
		else
			set realName to fileName
		end if
	end repeat
	
	-- We are about to save the file...
	if didExist = false then
		try
			local saveName
			set saveName to (posixFolderPath & fileName)
			do shell script "touch " & quoted form of saveName
			ttxtSave(saveName, false)
			-- should check out the name it ends up with when we save
			if not alwaysExtension then adjustTxtExt(posixFolderPath, fileName)
			
		on error e number n
			showErrorAndDie("Error during save: " & e & " (savedoc)")
			-- We may not posess rights to the directory.sa
		end try
		
	else if theScheme is null or theScheme is missing value then -- we'll overwrite it.
		try
			local saveName
			set saveName to (posixFolderPath & realName)
			ttxtSave(saveName, false)
			-- should check out the name it ends up with when we save
			
		on error e number n
			showErrorAndDie("Error during overwrite: " & e & " (savedoc)")
			-- the file may be protected.
		end try
	else
		-- The file exists, and we shall either move the previous version to an extension,
		-- just overwriting any previous with that extension, or we'll make a suffix
		-- for "our" file with the next higher available number.
		
		if theScheme is number then
			-- we had an orignal filename, with or without a txt extension.
			-- it is our job to create a new name, with the next free number.
			
			set newName to newNameWithNumber(posixFolderPath, realName)
			try
				local saveName
				set saveName to (posixFolderPath & newName)
				ttxtSave(saveName, true)
			on error e
				showErrorAndDie("Error during saving file with new name: " & e & " (savedoc)")
			end try
		else if class of theScheme is string then
			-- We are going to swap whatever ext the existing filname has
			-- move it to a file with the new extension.
			moveFileAndMakeEmpty(posixFolderPath, realName, theScheme)
			-- Having the existing file out of the way, we save our own.
			try
				local saveName
				set saveName to (posixFolderPath & realName)
				ttxtSave(saveName, false)
			on error e
				showErrorAndDie("Error during saving file after moving original: " & e & " (savedoc)")
			end try
		end if
	end if
end saveDoc

(* ========== HELPERS SAVING OUTPUT-DOCUMENTS  =============*)

on moveFileAndMakeEmpty(posixFolderPath, realName, theExt)
	
	local newFn, sfxofspos
	
	set sfxofspos to offset of "." in (reverse of (characters of realName) as text)
	if sfxofspos > 0 then
		set newFn to (text 1 thru -sfxofspos of realName) & theExt
	else
		set newFn to realName & "." & theExt
	end if
	local origFpath, newSPath, docPath
	set origFpath to (posixFolderPath & realName)
	set newSPath to (posixFolderPath & newFn)
	try
		do shell script "mv " & quoted form of origFpath & space & quoted form of newSPath & "; SetFile -c \"ttxt\" -t \"ttxt\" " & quoted form of newSPath
		set docPath to POSIX file newSPath as alias as text
	on error e
		showErrorAndDie("Error when moving file " & realName & " : " & e & "  (savedoc:moveFile)")
	end try
	-- we see to that we also can open the backup file from TextEdit.
	tell application id "sevs" to tell disk item docPath to set default application of it to "com.apple.textedit"
	
	do shell script "touch " & quoted form of origFpath & "; SetFile -c \"ttxt\" -t \"ttxt\" " & quoted form of origFpath
	set docPath to POSIX file origFpath as alias as text
	tell application id "sevs" to tell disk item docPath to set default application of it to "com.apple.textedit"
	
end moveFileAndMakeEmpty

on newNameWithNumber(posixFolderPath, realName)
	-- Tailored to be used with savedoc only! (qualification...)
	-- We start off with the previously obtained  realName, which we must surmise
	-- that has same the suffix we are supposed to use, at least it is wise for
	-- us to surmise so, and have the user manually remove any suffixes if it
	-- isn't the case, since we can't smell that we started from a wrong
	-- vantage point.
	
	-- we also know that realName exist, so we'll add numbers for finding the first
	-- filename that doesn't exist, and return that one.
	local sfxOfs, stem, theExt
	set sfxOfs to offset of "." in realName
	if sfxOfs > 0 then
		set stem to text 1 thru (sfxOfs - 1) of realName
		set thExt to text (sfxOfs + 1) thru -1 of realName
	else
		set stem to realName
	end if
	
	local fn, fnProbe, freeSlot
	set freeSlot to false
	repeat with i from 1 to 999 -- should be enough!
		set fn to stem & space & i
		if sfxOfs > 0 then set fn to fn & "." & thExt
		try
			set fnProbe to (POSIX file (posixFolderPath & fn)) as alias
		on error
			set freeSlot to true
			exit repeat
		end try
	end repeat
	if not freeSlot then showErrorAndDie("You have 999 files generated, limit exeeded, time to clean your disk! (newNameWithNumber)")
	return fn
end newNameWithNumber

on ttxtSave(saveName, numbered)
	-- Handler for circumventing TextEdit's sandboxing.
	local sandboxPath
	tell application id "ttxt"
		try
			save theDoc
			set sandboxPath to (path of theDoc)
		on error e number n
			error e number n
		end try
	end tell
	try
		do shell script "mv " & quoted form of sandboxPath & space & quoted form of saveName
	on error e number n
		error e number n
	end try
	ignoring application responses
		tell application id "ttxt"
			try
				tell theDoc
					set path of it to saveName
					save it
				end tell
			end try
		end tell
	end ignoring
	if not numbered then
		try
			toFrontInTextEdit()
		on error
			showErrorAndDie("I couldn't access the window I should have been able to access!. (ttxtSave:toFrontInTextEdit)")
		end try
		
		tell application id "ttxt" to activate
		delay 0.2
		tell application id "sevs" to if not numbered then tell application process "TextEdit" to keystroke return
	else
		local docName
		tell (a reference to text item delimiters)
			local tids
			set {tids, contents of it} to {contents of it, "/"}
			set docName to text item -1 of saveName
			set contents of it to tids
		end tell
		tell application id "ttxt" to set theDoc to its document docName
	end if
	do shell script "SetFile -c \"ttxt\" -t \"ttxt\" " & quoted form of saveName
	tell application id "sevs" to tell disk item (POSIX file saveName as alias as text) to set default application of it to "com.apple.textedit"
end ttxtSave

(* ========== UNIVERSAL ERROR HANDLER  =============*)
on showErrorAndDie(errText)
	-- Your choice... switch comments if your prefer notification!
	tell application id "com.apple.systemuiserver" to ¬
		display dialog errText with title "Output" with icon caution buttons {"Ok"} default button 1
	-- tell me to display notification errText subtitle "Output"
	error number -128
end showErrorAndDie

Hello.

I removed a bug regarding creation of numbered files, which made the bringtofront handler error, since the document name had changed.

Some of you may regard the fact that closing of a numbered document, doesn’t happen. Well, I think that when I get numbered documents up on my screen, then I want them up for a reason, and they are numbered because I can compare them, so I won’t fix that until I really want to close them. :slight_smile: