SUPPORT = ENV['TM_SUPPORT_PATH']
DIALOG = SUPPORT + '/bin/tm_dialog'
require SUPPORT + '/lib/exit_codes'
require SUPPORT + '/lib/escape'
require SUPPORT + '/lib/osx/plist'
DOCSET_CMD = "/Developer/usr/bin/docsetutil search -skip-text -query "
DOCSETS = [
"/Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.CoreReference.docset",
"/Developer/Platforms/iPhoneOS.platform/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiPhone2_0.iPhoneLibrary.docset"
]
Ref = Struct.new(:language, :type, :klass, :thing, :path)
# Split the query result into its component types and document path.
# language is 'Objective-C', 'C', 'C++'
# type is 'instm' (nstance method), 'clsm' (class method, 'func' , 'econst', 'tag', 'tdef' and so on.
# klass holds the class or '-' if no class is appropriate (for a C function, for example).
# thing is the method, function, constant, etc.
def parts_of_reference (ref_str)
ref = ref_str.split
if ref.length != 2
TextMate.exit_show_tool_tip "Cannot parse reference: #{str}"
end
language, type, klass, thing = ref[0].split('/')
Ref.new(language, type, klass, thing, ref[1])
end
def search_docs (query)
DOCSETS.each do |docset|
cmd = DOCSET_CMD + query + ' ' + docset
response = `#{cmd}`
case response # elaborate for doc purposes
when ''
# Not found.
when /Documentation set path does not exist/
# Docset not installed or moved somewhere else.
else
return docset, response.split("\n").map {|r| parts_of_reference(r)}
end
end
return nil, []
end
def show_document (docset, response, query)
if response.length == 0
TextMate.exit_show_tool_tip "Cannot find documentation for: #{query}"
elsif response.length == 1
path = response[0].path
else
# Ask the user which class they are interested in.
response.sort! {|a, b| a.klass <=> b.klass}
if response.all? {|ref| ref.language == "Objective-C"}
class_names = response.map {|ref| {'title' => ref.klass, 'path' => ref.path}}
else
class_names = response.map {|ref| {'title' => "#{ref.language} #{ref.klass}", 'path' => ref.path}}
end
class_names.sort! {|a, b| a['title'] <=> b['title']}
path = get_user_selected_reference(class_names)
end
if path
url = docset + "/Contents/Resources/Documents/" + path
TextMate.exit_show_html ""
else
TextMate.exit_discard
end
end
def man_page (query)
if `man -w #{query}` !~ /No manual entry/
page = `#{SUPPORT}/bin/html_man.sh -a #{query}`
TextMate.exit_show_html ""
else
TextMate.exit_show_tool_tip "Cannot find documentation for: #{query}"
end
end
def get_user_selected_reference (class_names)
plist = {'menuItems' => class_names}.to_plist
res = OSX::PropertyList::load(%x{#{e_sh DIALOG} -up #{e_sh plist} })
res['selectedMenuItem'] ? res['selectedMenuItem']['path'] : nil
end
def documentation_for_word
query = ENV['TM_SELECTED_TEXT'] || ENV['TM_CURRENT_WORD']
docset, response = search_docs(query)
if response.empty?
man_page(query)
else
show_document(docset, response, query)
end
end
def documentation_for_selector
lines = STDIN.readlines
# Find out the caret's position within the whole document as we may need to
# more back and forwards across line boundaries while building up the
# selector signature.
line_index = ENV['TM_LINE_INDEX'].to_i
line_number = ENV['TM_LINE_NUMBER'].to_i - 1 - 1 # starts from 1 and stop on line before
# caret_placement identifies the index of the character to the left of the caret's position.
caret_placement = (0..line_number).inject(0) {|sum, i| sum + lines[i].length} + line_index - 1
start_char = end_char = nil
doc = lines.join
# Scan backwards looking for the the matching '[' enclosing the method selector.
nesting_level = 1
caret_placement.downto(0) do |start_char|
nesting_level += 1 if doc[start_char, 1] == ']'
nesting_level -= 1 if doc[start_char, 1] == '['
break if nesting_level == 0
end
# Scan forwards looking for the the matching ']' enclosing the method selector.
nesting_level = 1
caret_placement.upto(doc.length - 1) do |end_char|
nesting_level += 1 if doc[end_char, 1] == '['
nesting_level -= 1 if doc[end_char, 1] == ']'
break if nesting_level == 0
end
if doc[start_char, 1] != '[' && doc[end_char, 1] != ']'
TextMate.exit_show_tool_tip "Failed to isolate selector from input starting at #{caret_placement} with input: #{doc[start_char...end_char]}"
end
selector = doc[(start_char + 1)...end_char]
# Whittle out everything but the selectors.
selector.gsub!(/\n/m, ' ') # remove newlines
selector.gsub!(/".*"\]/, ' ') # remove any string constants (may hold :)
selector.gsub!(/\[.*?\]/, ' ') # remove nested messages
query = selector.scan(/\w+:/).join
if query == ''
# Must have a message with no : in it, i.e. [fred init]
query = selector[/\w+\s*$/]
end
docset, response = search_docs(query)
# Filter out the non Objective-C responses.
refrences = response.delete_if {|r| r.language != 'Objective-C'}
show_document(docset, response, query)
end