Sublime Forum

Reading PHP help (chm)

#1

I have a copy of the PHP help file - php_enhanced_en.chm - installed on my Windows 7 computer at c:\windows\help. What I want to do is be able to press a key-combination to find help for the current word/function.

I thought it might be possible as a build option and tried variations of:

{ // "cmd": "C:\\Windows\\Help\\php_enhanced_en.chm::/function.${CURRENT_WORD}.html"] "cmd": "C:\\Windows\\Help\\php_enhanced_en.chm::/function.", "$(CURRENT_WORD)"] }
I don’t think a build can interpret ${CURRENT_WORD}. Any suggestions please on how I can get this working? (I have hh.exe available on my system.)

0 Likes

#2

I realised I should be using ${TM_CURRENT_WORD} but still no joy…

0 Likes

#3

I’m a windows (lo|u)ser too and I’ve got python docs setup using chm

You can use ctypes to hook into the chm apis for this

I’ll post the code when the power comes back on

0 Likes

#4

[code]#coding: utf8
#################################### IMPORTS ###################################

#Std Libs
import ctypes
from ctypes.wintypes import BOOL

#################################### HELPERS ###################################

Force unicode for c_wchar_p

def W(s): return s.decode(‘utf8’) if isinstance(s, str) else s

################################ CTYPES WRAPPING ###############################

HtmlHelp = ctypes.windll.LoadLibrary(‘hhctrl.ocx’).HtmlHelpW

HH_KEYWORD_LOOKUP = 0x000D

class HH_AKLINK(ctypes.Structure):
fields = (
# sizeof this structure
(‘cbStruct’, ctypes.c_int),

    # must be FALSE (really!)
    ('fReserved',    BOOL),

    # semi-colon separated keywords
    ('pszKeywords',  ctypes.c_wchar_p),

    # URL to jump to if no keywords found (may be NULL)
    ('pszUrl',       ctypes.c_wchar_p),

    # Message text to display in MessageBox if pszUrl is
    ('pszMsgText',   ctypes.c_wchar_p),
    ('pszMsgTitle',  ctypes.c_wchar_p),

    # Window to display URL in
    ('pszWindow',    ctypes.c_wchar_p),

    # Displays index if keyword lookup fails.
    ('fIndexOnFail', BOOL)
)

############################## OPEN HELP FUNCTION ##############################

def open_help_for( key_words, # semi-colon separated keywords or sequence
help_file, # chm file

               # URL to jump to if no keywords found
               backup_url    =  u'',

               # Displays index if keyword lookup fails.
               display_index = True,

               # 0 is fine; using sublime window handle will cause help
               # to `stay on top`
               hwnd=0 ):

key_words = ( u';'.join(key_words) if not isinstance(key_words, basestring)
                                     else W(key_words) )

ak = HH_AKLINK ( ctypes.sizeof(HH_AKLINK), False, key_words,
                 (W(backup_url) if backup_url else None), None, None,
                 None, display_index )

HtmlHelp(hwnd, W(help_file), HH_KEYWORD_LOOKUP, ctypes.byref(ak))

################################################################################[/code]

0 Likes

#5

I cut out the help file code from my plugin which relies on my helpers and crap

open_help_for(key_words, help_file)

Call it like that.

0 Likes

#6

@castles_made_of_sand Thank you for this :smile: , although I need a little more guidance.

It seems I need to create a TextCommand and pass your function open_help_for() the current word and the path to my php.chm file. Is this correct?
How can I pass ${TM_CURRENT_WORD}, or will I have to add a bit of ST-API-code to read the current word?

This seems a bit more intricate than the simple solution I was aiming for; but perhaps your method has some advantages?

I will certainly give yours a go but, at the same time, I’m still keen to get my version to work :wink: . Do you think if I create a key-binding for the command “exec” (or “open”?) that I can supply “args” : ${TM_CURRENT_WORD} and it will work?

Regards, Andy.

0 Likes

#7

Yeah, you’ll need to write a little TextCommand which gets the current word[s]

ie. open_help_for(keywords = map(view.substr, view.sel()), path_to_chm)

I’m not sure how opening help files from the cmd line works as this was the first solution I found back when.

So I don’t really know about the relative merits of each way.

I know with subsequent calls to the same chm file, it just reuses the already open window ?

I cobbled it together blind from some random internet source. I didn’t even understand the ctypes back then.

But it works for me :smile: Was more interested in the help file than how I was opening it.

0 Likes

#8

I’m not sure TM_* vars are expanded here. AFAIK they are only available in snippets?

0 Likes

#9

@castles

I was curious about this myself. That is, if I can get my version working - will it close and re-open the help, or possible open another instance. I won’t know until I succeed.

If I’m going to have to create a TextCommand anyway then not being able to read TM_CURRENT_WORD will be immaterial. Although, I’m curious if the following might return something:

os.environ'TM_CURRENT_WORD']

I think I’ll create a TextCommand and try os.system() or process.call() or something similar. Once I’ve conquered this I’ll return to your version. Ta, Andy.

PS I tried looking into ctags previously, but I couldn’t find a simple description of what it does…

0 Likes

#10

How do you open a chm from the command line with a particular keyword?

Manually building one of those commands you mentioned doesn’t work for me in a console:

C:\Program Files\Microsoft Visual Studio 9.0\VC>C:\Python27\Doc\python271.chm::/index.html The filename, directory name, or volume label syntax is incorrect.

0 Likes

#11

Doh! Hadn’t occurred to me yet to try it from a command line.

hh.exe c:\windows\help\php_enhanced_en.chm::/res/function.range.html

…works for me. I had to specifically call ‘hh.exe’ (HtmlHelp). And /res was not mentioned when I googled earlier.

But I notice a second instance opening. I’ll have to see how, and whether, it behaves from a TextCommand. I tried subprocess.call() from the Console and it returned 1 but didn’t open (that I noticed) the .chm.

Added: Also, if you open the .chm and visit a page, then choose the View menu, URL, you’ll see something like:

mk:@MSITStore:C:\Windows\Help\php_enhanced_en.chm::/res/index.html
0 Likes

#12
subprocess.call("hh.exe mk:@MSITStore:C:\\Windows\\Help\\php_enhanced_en.chm::/res/function.range.html",shell=True)

works from the console, but suspends/crashes ST until I close .chm - with either shell=True or False. But it IS progress…

0 Likes

#13

subprocess.call() waits for the command to return

you could use start or cmd /k (can’t recall, on my mactard book atm) and shell=True, along with the hide console window flags

or use subprocess.Popen directly (call is a wrapper) and don’t call wait()

0 Likes

#14

He, he you’re a minute ahead of (or behind?) me:

subprocess.Popen("hh.exe mk:@MSITStore:C:\\Windows\\Help\\php_enhanced_en.chm::/res/function.range.html",shell=False)

I’ll try as a TextCommand later and see if two instances occur. Actually, just ran it again from the Console with a different fn and two instances are open. Mmm. I’ll have to dig deeper. I need to somehow kill the first instance.

0 Likes

#15

Cool, got this working:

[code]import sublime, sublime_plugin
import subprocess

PHP_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\php_enhanced_en.chm::/res/function.%(func)s.html”""

class PhpHelpCommand(sublime_plugin.TextCommand):
proc1 = None
def run(self, edit):
curr_view = self.view
if not curr_view.match_selector(0, ‘source.php’): return
word_end = curr_view.sel()[0].end()
if curr_view.sel()[0].empty():
word = curr_view.substr(curr_view.word(word_end)).lower()
else:
word = curr_view.substr(curr_view.sel()[0]).lower()
if word is None or len(word) <= 1:
sublime.status_message(‘No function selected’)
return
word = word.replace(’_’, ‘-’)
try:
if self.proc1 is not None:
self.proc1.kill()
except Exception:
pass
self.proc1 = subprocess.Popen(PHP_HELP % { “func”: word }, shell=False)[/code]
I like the possibilities for this:

Could have one key-binding which would work with many languages (providing there is a .chm file available for the language);
[the TextCommand could check the current file-syntax and search the corresponding .chm]
Consistency of behaviour;
Limited set-up required, possibly none if the help file is in a default location;
If a function search fails, it could then be made to search for a method or class.

For example, my JavaScript .chm search looks like this:

mk:@MSITStore:C:\Windows\Help\javascript.chm::/jsref_decodeURI.htm
0 Likes

#16

Here’s a working proof of concept, that will open either a PHP or JS .chm help file on a single keybinding:

[code]import sublime, sublime_plugin
import subprocess

PHP_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\php_enhanced_en.chm::/res/function.%(func)s.html”""
JS_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\javascript.chm::/jsref_%(func)s.htm”""

class LanguageHelpCommand(sublime_plugin.TextCommand):
proc1 = None
def run(self, edit):
curr_view = self.view
curr_sel = curr_view.sel()[0]
if curr_view.match_selector(curr_sel.begin(), ‘source.php’):
source = ‘PHP’
elif curr_view.match_selector(curr_sel.begin(), ‘source.js’):
source = ‘JS’
else:
return

    word_end = curr_sel.end()
    if curr_sel.empty():
        word = curr_view.substr(curr_view.word(word_end)).lower()
    else:
        word = curr_view.substr(curr_sel).lower()
    if word is None or len(word) <= 1:
        sublime.status_message('No function selected')
        return
    if source == 'PHP':
        word = word.replace('_', '-')
        HELP = PHP_HELP % { "func": word }
    elif source == 'JS':
        HELP = JS_HELP % { "func": word }
    try:
        if self.proc1 is not None:
            self.proc1.kill()
    except Exception:
        pass
    self.proc1 = subprocess.Popen(HELP, shell=False)[/code]

If anyone is interested in trying this then you’ll need to revise the path and filename to your JS and PHP .chm files. You’ll also need the file ‘hh.exe’. This may already be on your (Windows) computer - mine’s at ‘C:\Windows’, so I don’t need to provide a path to it. I believe a more recent version of this HTMLHelp file is named ‘keyhh.exe’.

[Also, ensure that the .chm files are unblocked.]

My key-binding:

{ "keys": "ctrl+f1"], "command": "language_help" },
0 Likes

#17

Unfortunately, the Python help file (pydoc.chm) looks unworkable (I already have a help feature for this anyway…), but jQuery looks very good:

[code]# The Python help file is a mess:

mk:@MSITStore:C:\Windows\Help\pydoc.chm::/library/stdtypes.html#str.replace

jQuery:

mk:@MSITStore:C:\Windows\Help\jqdoc.chm::/api/add.html[/code]

0 Likes

#18

If you are able to specify jQuery syntax, and can track down jQuery-UI-Reference-1.7.chm or similar, then:

[code]import sublime, sublime_plugin
import subprocess

PHP_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\php_enhanced_en.chm::/res/function.%(func)s.html”""
JS_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\javascript.chm::/jsref_%(func)s.htm”""
jQuery_HELP =
“”“hh.exe mk:@MSITStore:C:\Windows\Help\jQuery-UI-Reference-1.7.chm::/api/%(func)s.htm”""

The Python help file is a mess:

mk:@MSITStore:C:\Windows\Help\pydoc.chm::/library/stdtypes.html#str.replace

mk:@MSITStore:C:\Windows\Help\jqdoc.chm::/api/add.html # jQuery 1.4

mk:@MSITStore:C:\Windows\Help\css-2.1.chm::/colors.html#propdef-background-color

mk:@MSITStore:C:\Windows\Help\css-2.1.chm::/box.html#propdef-border-top

class LanguageHelpCommand(sublime_plugin.TextCommand):
proc1 = None
def run(self, edit):
curr_view = self.view
curr_sel = curr_view.sel()[0]
if curr_view.match_selector(curr_sel.begin(), ‘source.php’):
source = ‘PHP’
elif curr_view.match_selector(curr_sel.begin(), ‘source.js.jquery’):
source = ‘JQUERY’
elif curr_view.match_selector(curr_sel.begin(), ‘source.js’):
source = ‘JS’
else:
return

    word_end = curr_sel.end()
    if curr_sel.empty():
        word = curr_view.substr(curr_view.word(word_end)).lower()
    else:
        word = curr_view.substr(curr_sel).lower()
    if word is None or len(word) <= 1:
        sublime.status_message('No function selected')
        return
    if source == 'PHP':
        word = word.replace('_', '-')
        HELP = PHP_HELP % { "func": word }
    elif source == 'JQUERY':
        HELP = jQuery_HELP % { "func": word }
    elif source == 'JS':
        HELP = JS_HELP % { "func": word }
    try:
        if self.proc1 is not None:
            self.proc1.kill()
    except Exception:
        pass
    self.proc1 = subprocess.Popen(HELP, shell=False)[/code]

Unfortunately, CSS, HTML and Python .chm files are unworkable :frowning:

0 Likes

#19

… I might even considering bundling all these .chm files together into a Package. This way, it wouldn’t require any set up at all :smiley: (but Windows only).

I am aware that there are already help systems available, and they are certainly more comprehensive. This version would only look up functions OR methods.

ON the other hand, this would:
Not require any detailed set-up;
Be quick and consistent.

0 Likes

#20

And finally… I won’t be pursuing this much further :frowning: . Most .chm files are unworkable for this. They either put things in different subfolders, use numbered bookmarks, or complex code numbers.

But my previous code works for PHP functions, JS, and jQuery methods. Andy.

0 Likes