Script: Wrap search terms in asterisks (but leave prefixes alone)

Hi there, this script wraps every freeform search term in asterisks but leaves search operators (e.g. OR) and DEVONthink 3 search prefixes (e.g. kind:text) alone. It can be used to start a search e.g. via Alfred or it can be used to toggle an existing query, meaning it will add or remove asterisks. I use one version (internalUsage=false) in Alfred and one (internalUsage=true) in the toolbar (but it should work from the script menu as well).

Background:

Before DEVONthink 3 I had a nice workflow to start simple searches (no search operators) in a new search window. I used Alfred at the start of the workflow to input the query (mainly because it’s possible to use modifiers to alter it) and I used Keyboard Maestro to open a new search window and insert the query (as this turned out to be more reliable then AppleScript in Alfred). With Alfreds modifiers (which were used with very simple regex) I had the choice between

  • wrap every search term in asterisks (used as default)
  • wrap whole query in quotes
  • prefix tilde
  • input = output

Now with DEVONthink 3 I want to make use of search prefixes and I think this means the old workflow is gone. I tried to write regex that lets me use Alfreds modifiers as I used to but failed… If someone knows how to split the “freeform” search terms from potentially existing search prefixes and search operators with regex (or another approach) please let me know. This could make altering a query much simpler and some things seem not to be possible in AppleScript at all, at least not for me.

However after failing on a regex solution (which was no surprise) I started to try at least the asterisks thing in AppleScript as this was my default way to search, So here’s the result

-- Wrap search terms in asterisks or remove them [Toggle] (but leave search operators and DEVONthink 3 search prefixes alone)
-- Can be used to start a search via Alfred, Keyboard Maestro etc. or to toggle an existing search query (e.g. from the toolbar or the scripts menu)
-- Needs a query like this but all parts are optional: [freeform search terms (and search operators] [search prefixes] [scope] 
-- If used from DEVONthinks toolbar it grabs the current content of the search field; it will only work if you already searched something
-- If changes you've made in the script doesn't apply immediately when used from toolbar restart DEVONthink 3
-- Use two versions to take full effect, one to start a search, one to toggle an existing query
-- RemoveFromString handler: http://applescript.bratis-lover.net

property prefixOperators : {":", "!", "=", "<", ">", "~", "#"}
property searchOperators : {"-", "!", "?", "[", "]", "*", "&", "^", "|", "~", "AFTER", "AND", "BUT", "EOR", "NEAR", "NEXT", "NOT", "OPT", "OR", "XOR"}
property noSearchTerm : false
property internalUsage : false -- set this to true and uncomment the first code block for usage in DEVONthink 3 toolbar 

#if internalUsage = true then
#	activate application "DEVONthink 3"
#	tell application "System Events"
#		tell process "DEVONthink 3"
#			set theQuery to value of text field 1 of group 3 of toolbar 1 of window 1
#		end tell
#	end tell
#	if theQuery = "" then return
#end if

if theQuery = "" then return

if theQuery does not contain "*" then
	
	set theFreeformList to getFreeformList(theQuery, prefixOperators)
	
	set modifiedFreeformList to {}
	
	repeat with thisItem in theFreeformList
		set isOperator to false
		
		repeat with thisOperator in searchOperators
			considering case
				if thisItem contains thisOperator then
					set isOperator to true
					exit repeat
				end if
			end considering
		end repeat
		
		if isOperator = false then
			
			if thisItem starts with "(" then
				set end of modifiedFreeformList to "(*" & characters 2 thru -1 in thisItem & "*"
				
			else if thisItem ends with ")" then
				set end of modifiedFreeformList to "*" & characters 1 thru -2 in thisItem & "*)"
				
			else
				set end of modifiedFreeformList to "*" & thisItem & "*"
			end if
			
		else
			set end of modifiedFreeformList to thisItem
		end if
		
	end repeat
	
	set modifiedFreeformString to makeStringFromList(modifiedFreeformList, space)
	
	try
		set lastFreeformItem to item -1 in theFreeformList
		set splitAt to (offset of lastFreeformItem in theQuery) + (count of (characters of lastFreeformItem)) + 1
		set theModifiedQuery to modifiedFreeformString & space & (characters splitAt thru -1 in theQuery)
	on error
		set theModifiedQuery to modifiedFreeformString
	end try
	
else
	
	set theModifiedQuery to RemoveFromString(theQuery, "*")
	
end if

if noSearchTerm = true then set theModifiedQuery to characters 4 thru -1 in theModifiedQuery as string


tell application id "com.devon-technologies.think3"
	tell viewer window 1
		set search query to theModifiedQuery
	end tell
end tell

on getFreeformList(theQuery, prefixOperators)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to " "
	set TextItems to text items of theQuery
	set AppleScript's text item delimiters to d
	
	set theFreeformList to {}
	
	repeat with thisTextItem in TextItems
		if thisTextItem as string ≠ "" then
			
			repeat with thisOperator in prefixOperators
				if thisTextItem contains thisOperator then
					set noPrefix to false
					exit repeat
				else
					set noPrefix to true
				end if
			end repeat
			
			if noPrefix = true then
				set end of theFreeformList to thisTextItem as string
			else
				exit repeat
			end if
			
		end if
	end repeat
	
	if theFreeformList = {} then
		set theFreeformList to {""}
		set noSearchTerm to true
	end if
	
	return theFreeformList
end getFreeformList


on makeStringFromList(theList, theDelimiter)
	set theString to ""
	set theCount to 0
	
	repeat with thisItem in theList
		set thisItem to thisItem as string
		set theCount to theCount + 1
		if theCount ≠ (count of theList) then
			set theString to theString & thisItem & theDelimiter
		else
			set theString to theString & thisItem
		end if
	end repeat
	
	return theString
end makeStringFromList


-- http://applescript.bratis-lover.net

--c--   RemoveFromString(theText, CharOrString)
--d--   Case-sensitive remove substring from string.
--a--   theText : string -- the string to search
--a--   CharOrString : string -- the string to remove
--r--   string
--x--   RemoveFromString("Hello hello", "hello") --> "Hello "
--u--   ljr (http://applescript.bratis-lover.net/library/string/)
on RemoveFromString(theText, CharOrString)
	local ASTID, theText, CharOrString, lst
	set ASTID to AppleScript's text item delimiters
	try
		considering case
			if theText does not contain CharOrString then ¬
				return theText
			set AppleScript's text item delimiters to CharOrString
			set lst to theText's text items
		end considering
		set AppleScript's text item delimiters to ASTID
		return lst as text
	on error eMsg number eNum
		set AppleScript's text item delimiters to ASTID
		error "Can't RemoveFromString: " & eMsg number eNum
	end try
end RemoveFromString