hasseg.org

Flex SDK Language Reference Helper Macro for jEdit

Filed under ActionScript 3, Flex, jEdit, Mac, Programming

VeryVito at turdhead.com has been writing lately about customizing jEdit for writing ActionScript, which is exactly what I've also been doing. A While ago he posted macros for referencing the help files that came with Flash CS3. Well, I don't have Flash CS3, and I use the Flex SDK to do my ActionScripting, so I took the macro he posted and modified it so that it can be used with the Flex language reference.

Update (Oct 5, 07): Now it can search from any number of AsDoc sources
Update (Oct 8, 07): Also finds documentation for methods, changing the command for opening a file in a web browser is easier
Update (Oct 10, 07): now the script searches through files, not buffers, so you needn't have all of your project's files open for it to be able to search through them

The way my version works is that it gets the selection from the current buffer (or if there's no selection made, it selects the word that the caret is on top of) and searches the Flex SDK language reference for it. The files it goes through and the regular expressions + other logic that is used to do the pattern matching are designed so that it would work for class, package and method names.

Here's the macro code (with some unnecessary whitespace removed):

/*
* Copyright (c) 2007 Ali Rantakari
* 
* fromjeditflexlangrefmacro.20.hasseg@spamgourmet.com
* http://hasseg.org
* 
* Based on macros by VeryVito from turdhead.com :
* http://www.turdhead.com/2007/09/30/more-actionscript-magic-for-jedit-context-sensitive-reference-material/
* 
* thanks!
* 
* ---------------------------------------------------------------------------
* 
* DESCRIPTION:
* 
* A jEdit macro for displaying Flex SDK language reference documentation
* for a selected class, package or method.
* 
* Developed and tested only on OS X, no guarantee of this working on 
* other platforms.
* 
* ---------------------------------------------------------------------------
* 
* REQUIRES:
* 
* - jEdit
* 
* - OPTIONAL: InfoViewer plugin for jEdit
* 
* ---------------------------------------------------------------------------
* 
* USAGE:
* 
* 1) Change the settings below to correspond to your system
* 
* 2) Add this to your macros folder ( ~/.jedit/macros )
* 
* 3) Bind it to a keyboard shortcut ( utilities -> global options ->
*    shortcuts -> macros )
* 
* 4) Place your pointer on the search term or select the search
*    term (an AS3/Flex class, package or method name)
* 
* 5) Run the macro by pressing the shortcut you assigned (or from
*    the macros menu)
* 
* ---------------------------------------------------------------------------
* 
* Licensed under the MIT License:
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


import java.util.regex.*;
import java.text.MessageFormat;



// SETTINGS: --------------------------------------
	
	/*
	* Paths to AsDoc folders to search (in this order)
	* (with trailing slashes)
	*/
	String[] foldersToCheck = new String[] {	"/Flex/flex201_documentation/langref/",
												"/Flex/SVN_working_copies/SSGUI-Flex/asdoc-output/"};
	
	/*
	* The files in the AsDoc folders to check (in this order)
	* (if they don't exist in all of the folders, no worries)
	*/
	String[] filesToCheck = new String[] {	"all-classes.html", 
											"class-list.html", 
											"package-summary.html"};
	
	
	
	/*
	* Whether to display the found documentation page
	* in the InfoViewer plugin or not (if not, the
	* openCommand (below) will be run in the console)
	*/
	displayInInfoViewer = false;
	
	
	/*
	* The command to run in the console for opening
	* the found documentation file in, for example,
	* a web browser
	* 
	* {0} will be substituted with the filename to open,
	*     surrounded by double quotes (so don't add the
	*     double quotes here)
	*/
	openCommand = "open -a Safari {0}";
	
	
// /settings - - - - - - - - - - - - - - - - - - - 



// Macro implementation begins here:  ------------



// functions -------------------------------------


Vector searchDirForFilesMatching(File aDirectory, String aRegEx) {
	Vector retVal = new Vector();
	if (aDirectory != null) {
		if (aDirectory.exists()) {
			if (aDirectory.isDirectory()) {
				File[] files = aDirectory.listFiles();
				for (int i = 0; i < files.length; i++) {
					if (files[i].isFile() && !files[i].isHidden()) {
						if (files[i].getName().matches(aRegEx)) retVal.add(files[i]);
					}
				}
			}else{
				Macros.error(view, "\""+aDirectory.getPath()+"\" is not a directory");
			}
		}else{
			Macros.error(view, "\""+aDirectory.getPath()+"\" does not exist!");
		}
	}
	return retVal;
}



/**
* Returns a Vector of HashMaps. Each HashMap contains a match, where
* the keys are the specified group numbers and the values are the
* corresponding strings.
* 
* Returns null if no matches.
*/
Vector regExSearchFile(String aRegEx, String aFilePath, int[] aGroupsToReturn) {
	Vector retVal = new Vector();
	Pattern regPat = Pattern.compile(aRegEx);
	Matcher matcher;
	BufferedReader bufr;
	File f = new File(aFilePath);
	if (f.exists()) {
		try {
			bufr = new BufferedReader (new FileReader (aFilePath));
			while ( (record = bufr.readLine()) != null ) {
				matcher = regPat.matcher(record);
				if (matcher.find()) {
					HashMap thisRetEntry = new HashMap();
					for (int j = 0; j < aGroupsToReturn.length; j++) {
						thisRetEntry.put(aGroupsToReturn[j], matcher.group(aGroupsToReturn[j]));
					}
					retVal.add(thisRetEntry);
				}
			}
		} catch (java.io.IOException e)	 {
			Macros.error(view, "An error occurred while trying to parse \""+aFilePath+"\"");
		}
	}
	return retVal;
}



/**
* Searches for the specified class or package name (aWordToFind) in
* the AsDoc folders and files specified in the settings.
* 
* Returns the documentation file path or null if not found.
*/
String searchForDocs(String aWordToFind) {
	String retVal = null;
	String regEx = "a.*href\\s*=\\s*\"(.*?)\".*>("+aWordToFind+")<";
	Pattern regPat = Pattern.compile(regEx);
	Matcher matcher;
	BufferedReader bufr;
	int j = 0;
	while ((retVal == null) && (j < foldersToCheck.length)) {
		int i = 0;
		while ((retVal == null) && (i < filesToCheck.length)) {
			String thisFile = foldersToCheck[j]+filesToCheck[i];
			File f = new File(thisFile);
			if (f.exists()) {
				try {
					bufr = new BufferedReader (new FileReader (thisFile));
					while ( (record = bufr.readLine()) != null ) {
						matcher = regPat.matcher(record);
						if (matcher.find()) retVal = foldersToCheck[j] + matcher.group(1);
					}
				} catch (java.io.IOException e)	 {
					Macros.error(view, "An error occurred while trying to parse \""+thisFile+"\"");
				}
			}
			i++;
		}
		j++;
	}
	return retVal;
}


/**
* Give this a variable name and the path to the file where
* it appears, and get the class name of the type
* of the variable, or null if not found.
*/
String findTypeOfVarFromFile(String aVarName, String aFilePath) {
	String retVal = null;
	File f = new File(aFilePath);
	if (f.exists()) {
		if (f.isFile()) {
			returnCurrentClassName = false;
			if (aVarName == null) returnCurrentClassName = true;
			else if (aVarName == "this" || aVarName.trim() == "") returnCurrentClassName = true;
			if (returnCurrentClassName == true) {
				// find first class name from specified file
				Vector results = regExSearchFile("\\b(class)\\s+\\b(\\w+)\\b", aFilePath, new int[]{2});
				if (!results.isEmpty()) retVal = results.get(0).get(2);
			}else{
				// find definition of specified variable from specified file
				Vector results = regExSearchFile((".*("+aVarName+")\\s*:\\s*(.*?)[;,=\\s\\)]+"), aFilePath, new int[]{2});
				if (!results.isEmpty()) {
					retVal = results.get(0).get(2).trim();
					// if file classname+"class.as" exists, prefer that class name
					Vector codeBehindMatches = searchDirForFilesMatching(f.getParentFile(), ("^"+retVal+"Class\\.as$"));
					if (!codeBehindMatches.isEmpty()) retVal = retVal+"Class";
				}
				// if nothing found...
				if (retVal == null) {
					// find superclass name (if there is one)
					Vector results = regExSearchFile(("class\\s+(.*?)\\s+extends\\s+(.*?)[;\\s]+"), aFilePath, new int[]{2});
					if (!results.isEmpty()) {
						String superClassName = results.get(0).get(2).trim();
						// find source file for superclass (in the same dir)
						File superClassFile = null;
						Vector straightMatches = searchDirForFilesMatching(f.getParentFile(), ("^"+superClassName+"\\.as$"));
						Vector codeBehindMatches = searchDirForFilesMatching(f.getParentFile(), ("^"+superClassName+"Class\\.as$"));
						if (!codeBehindMatches.isEmpty()) superClassFile = codeBehindMatches.get(0);
						else if (!straightMatches.isEmpty()) superClassFile = straightMatches.get(0);
						if (superClassFile != null) retVal = findTypeOfVarFromFile(aVarName, superClassFile.getPath());
					}
				}
			}
		}else{
			Macros.error(view, "\""+aFilePath+"\" is not a file!");
		}
	}else{
		Macros.error(view, "File \""+aFilePath+"\" does not exist!");
	}
	return retVal;
}



String getMethodOwner(String functionName, String lineWhereItAppears) {
	String retVal = null;
	String methodRegEx = "((.+)\\s*\\.\\s*)?("+functionName+")\\s*\\((.*?)\\)";
	Pattern methodRegPat = Pattern.compile(methodRegEx);
	Matcher methodMatcher = methodRegPat.matcher(lineWhereItAppears);
	if (methodMatcher.find()) {
		retVal = methodMatcher.group(2);
		if (retVal != null) {
			retVal = retVal.trim();
			// if using a cast (e.g. "MyClass(event.target).methodToSearchFor();")...
			if ((retVal.length() > 0) && (retVal.matches(".*\\(.*\\)$")))
				retVal = retVal.substring(0,(retVal.lastIndexOf("(")));
			// if 'inline'
			methodRegPat = Pattern.compile(".+\\b(\\w+)$");
			methodMatcher = methodRegPat.matcher(retVal);
			if (methodMatcher.find()) retVal = methodMatcher.group(1);
			if (retVal != null) retVal = retVal.trim();
			
			//Macros.message(view, "method owner variable is '"+retVal+"'");
		}
	}
	return retVal;
}

// /functions - - - - - - - - - - - - - - - - - - - - -



String foundDocFilePath = null; // the path to the documentation html file to display

// run search on the word under cursor or the selected text
if (textArea.selectedText == null) {
	textArea.goToPrevCharacter(false);
	textArea.selectWord();
}
String wordToFind = textArea.selectedText;
foundDocFilePath = searchForDocs(wordToFind);


// if nothing found, see if the selection is defined as a variable. 
// If it is, take variable's type and run a search on that
if (foundDocFilePath == null) {
	String typeOfVar = findTypeOfVarFromFile(wordToFind, buffer.getPath());
	if (typeOfVar != null) foundDocFilePath = searchForDocs(typeOfVar);
}


// if still nothing found...
if (foundDocFilePath == null) {
	// if selection is a function/method name, get its owner variable
	String methodOwnerVar = getMethodOwner(wordToFind, textArea.getLineText(textArea.getCaretLine()));
	
	if (methodOwnerVar != null) {
		// if starts with an uppercase letter, probably a class name -> try that
		if (methodOwnerVar.substring(0,1).matches("[A-Z]")) {
			foundDocFilePath = searchForDocs(methodOwnerVar);
		}
	}
	if (foundDocFilePath == null) {
		if (methodOwnerVar == null) methodOwnerVar == "this";
		// find type of this owner var and if found, search for that class
		String typeOfOwnerVar = null;
		typeOfOwnerVar = findTypeOfVarFromFile(methodOwnerVar, buffer.getPath());
		//Macros.message(view, "method owner type is '"+typeOfOwnerVar+"'");
		if (typeOfOwnerVar != null) foundDocFilePath = searchForDocs(typeOfOwnerVar);
	}
	if (foundDocFilePath != null) foundDocFilePath = foundDocFilePath+"#"+wordToFind+"\\\\(\\\\)";
}




// if found a doc URL, display it
if (foundDocFilePath != null)	 {
	
	if (!foundDocFilePath.startsWith("file://")) foundDocFilePath = "file://"+foundDocFilePath;
	
	if (displayInInfoViewer) infoviewer.InfoViewerPlugin.openURL(view, foundDocFilePath);
	else {
		
		Object[] mfArgs = {("\""+foundDocFilePath+"\"")};
		commandToRun = MessageFormat.format(openCommand, mfArgs);
		
		runInSystemShell(view, commandToRun);
		
		if (view.getDockableWindowManager().isDockableWindowDocked("console") == true) {
			if (view.getDockableWindowManager().isDockableWindowVisible("console") == true)
				view.getDockableWindowManager().removeDockableWindow("console");
		}
		
	}
	
}else{
	
	Macros.error(view, "Sorry. No Flex documentation found for \""+wordToFind+"\".");
	
}



/*

Macro index data (in DocBook format)

  <listitem>
    <para><filename>Display_Flex_documentation.bsh</filename></para>
    <abstract><para>
      Displays documentation about selected class, package or method from Flex language reference
	  and optionally also other AsDoc sources
    </para></abstract>
  </listitem>

*/

// end Display_Flex_documentation.bsh

As you can see from the settings, the macro can be set to open the documentation html page in either the InfoViewer plugin in jEdit or an external web browser. I prefer to have it open up in the browser, since the formatting and styling of the Flex language reference contribute to the fact that the pages don't really work very well in the InfoViewer plugin. The OS X open command is used to make the external browser open up the documentation, which means that if someone would want to use this in Windows or Linux, that part would have to be re-implemented. Update: Now you can simply edit the openCommand variable in the "settings" area to change the command that is run in the system shell to open up docs in the browser.

Get the script here:

Display_Flex_documentation.bsh

Update (October 5, 07): Modified the script so that it can search from any number of different AsDoc sources (which the official Flex language reference is one of)

Update (October 8, 07): Modified the script to also find documentation for methods. Made it easier to modify the command that is run in the system shell to open up documentation .html pages in an external browser.

Update (October 10, 07): Modified the script to operate on files instead of buffers so that it can also search files that are not currently open.

Categories