//----------------------------------------------------------------------------
// COMPONENT NAME: LPEX Editor
//
// © Copyright IBM Corporation 1998, 2008
// All Rights Reserved.
//
// DESCRIPTION:
// TestUserProfile - sample user profile
//----------------------------------------------------------------------------
package com.ibm.lpex.samples;
import com.ibm.lpex.core.LpexAction;
import com.ibm.lpex.core.LpexCommand;
import com.ibm.lpex.core.LpexDocumentLocation;
import com.ibm.lpex.core.LpexView;
/**
* Sample user profile - customize keys, commands, actions.
* It customizes the base editor profile (if not "vi") by redefining several
* settings and keys:
* <ul>
* <li>restricts the action of the Backspace and Delete keys to the current line</li>
* <li>sets the Enter keys to go to the next line rather than split</li>
* <li>sets the Home and End keys to actions <b>contextHome</b> and <b>contextEnd</b></li>
* <li>sets Alt+. to action <b>zoom</b> (defined in
* {@link ZoomAction}) to zoom in / out a document segment</li>
* <li>sets Alt+F1 to action <b>compose</b> (defined in
* {@link ComposeAction}) to enter special characters</li>
* <li>redefines Alt+J (join) (see {@link CloseJoin CloseJoin})
* to remove extra spaces between the joined texts</li>
* <li>sets Alt+T to action <b>blockTransfer</b> (defined in
* {@link BlockTransferAction}) to transfer selection, adds it to the popup</li>
* <li>redefines Ctrl+Backspace (delete line) to preserve the cursor column position</li>
* <li>redefines Ctrl+N (find next) (see {@link FindNextSelection FindNextSelection})
* to search for the selected text, if any</li>
* <li>sets Ctrl+Shift+V to do a paste replace</li>
* <li>redefines mouse button 1 double-click to select the current line</li>
* <li>sets the default selection type to character</li>
* <li>redefines the <b>findText</b> command (using {@link FindTextContextCommand})
* to show the context for the result of find-text command and actions</li>
* <li>defines commands <b>detab, entab</b> (using {@link DetabCommand},
* {@link EntabCommand})</li>
* <li>defines command synonyms <b>diff</b> for <b>compare</b>, <b>k</b> for <b>calc</b>,
* <b>reload</b> for <b>load</b>, and a <b>pre</b> command as 'synonym' for the
* command-line implicit set/query of the <b>prefixArea</b> parameter</li>
* <li>sets new mouse button 1 drag actions to always start new selections (using
* {@link MouseReselect})</li>
* <li>adds an action to the pop-up menu of Java properties document views to
* compare ignoring the mnemonics ('&'s).</li>
* </ul>
*
* <p>Here is the TestUserProfile <a href="doc-files/TestUserProfile.java.html">source
* code</a>.</p>
*
* <p>To run this sample:
* <ul>
* <li>Register this user profile from the "User Profile" preference page, where
* available, or from the editor command line:
* <pre>set [default.]updateProfile.userProfile com.ibm.lpex.samples.TestUserProfile
*updateProfile</pre></li>
* </ul></p>
*
* <p>The user profile is run during the processing of the <b>updateProfile</b>
* command. The <b>updateProfile</b> command is normally run when a document
* view is created, and it may be issued at any time to allow the document view
* to reflect changes to the editor profile (for example, a change to default
* settings done from the preference pages).</p>
*
* <p>A user profile is a public Java class that has a method of the form:
* <pre>
* public static void userProfile(LpexView lpexView) </pre>
* See the <b>updateProfile.userProfile</b> parameter.</p>
*
* @see com.ibm.lpex.samples All the samples
*/
public class TestUserProfile
{
/**
* Sample action <b>closeJoin</b> - join with just one space between the texts.
* This action is similar to the <b>join</b> default editor action, but it will
* keep one and only one space between the joined lines.
*
* <p>Defined as a separate, named class inside {@link TestUserProfile} (where
* you can also see its <a href="doc-files/TestUserProfile.java.html#line79">source
* code</a>) so that it can be easily registered by itself, when TestUserProfile
* is not used as the user profile. For example:
* <pre> set actionClass.closeJoin com.ibm.lpex.samples.TestUserProfile$CloseJoin
* set keyAction.a-j closeJoin</pre></p>
*/
public static class CloseJoin implements LpexAction
{
/**
* Runs the action.
* Joins the current and next text lines, keeps one space (no more, no less)
* between the concatenated texts.
*/
public void doAction(LpexView lpexView)
{
LpexDocumentLocation joinLocation = lpexView.documentLocation();
if (joinLocation.element > 0)
{
// save cursor position, may be affected by deleteWhiteSpace / insertText
int displayPosition = lpexView.queryInt("displayPosition");
joinLocation.position = lpexView.queryInt("length") + 1;
lpexView.doAction(lpexView.actionId("join"));
lpexView.doCommand(joinLocation, "action oneSpace");
// restore original cursor position
lpexView.doCommand("set displayPosition " + displayPosition);
}
}
/**
* Returns the availability of this action.
* This action can be run in any visible, writable document view.
*/
public boolean available(LpexView lpexView)
{
return lpexView.currentElement() != 0 && !lpexView.queryOn("readonly");
}
}
/**
* Sample action <b>findNextSelection</b> - first search for the selected text, if any.
* This action is similar to the <b>findNext</b> built-in editor action, but will find
* the next occurrence of the selected text if there is a selection in the current view
* suitable as find text. This selection is <b>not</b> recorded in the findText
* parameters. The found text is selected.
*
* <p>Defined as a separate, named class inside {@link TestUserProfile} (where
* you can also see its <a href="doc-files/TestUserProfile.java.html#line124">source
* code</a>) so that it can be easily registered by itself, when TestUserProfile
* is not used as the user profile. For example:
* <pre> set actionClass.findNextSelection com.ibm.lpex.samples.TestUserProfile$FindNextSelection
* set keyAction.c-n.t.p.c findNextSelection</pre></p>
*/
public static class FindNextSelection implements LpexAction
{
/**
* Runs the action.
* Searches for the selected text or the regular find text.
*/
public void doAction(LpexView lpexView)
{
if (lpexView.actionAvailable(lpexView.actionId("findSelection")))
{
// preserve current & default findText parameters (by default they are
// changed to the selection text, no regex), select the found text
String findText = lpexView.query("findText.findText");
String regex = lpexView.query("findText.regularExpression");
String defaultFindText = lpexView.query("default.findText.findText");
String defaultRegex = lpexView.query("default.findText.regularExpression");
String mark = lpexView.query("findText.mark");
lpexView.doCommand("set findText.mark on");
lpexView.doAction(lpexView.actionId("findSelection"));
lpexView.doCommand("set findText.findText " + findText);
lpexView.doCommand("set findText.regularExpression " + regex);
lpexView.doCommand("set default.findText.findText " + defaultFindText);
lpexView.doCommand("set default.findText.regularExpression " + defaultRegex);
lpexView.doCommand("set findText.mark " + mark);
}
else
{
lpexView.doAction(lpexView.actionId("findNext"));
}
}
/**
* Returns the availability of this action.
* This action can be run whenever either <b>findSelection</b> or <b>findNext</b> can.
*/
public boolean available(LpexView lpexView)
{
return lpexView.actionAvailable(lpexView.actionId("findSelection")) ||
lpexView.actionAvailable(lpexView.actionId("findNext"));
}
}
// Private constructor, not called explicitly.
private TestUserProfile() {}
/**
* This is the method in a user profile that is called by the
* <b>updateProfile</b> editor command.
*
* @param lpexView the document view for which this profile is being run
*/
public static void userProfile(LpexView lpexView)
{
// if the current editor profile is "vi", don't touch anything
String baseProfile = lpexView.query("baseProfile");
if ("vi".equals(baseProfile))
{
lpexView.doDefaultCommand("set messageText Base profile NOT modified by TestUserProfile.");
return;
}
/*------------------------------*/
/* define new actions, commands */
/*------------------------------*/
// define an action to select the current line
lpexView.defineAction("myBlockMarkElement", new LpexAction() {
public void doAction(LpexView view)
{
view.doCommand("block clear");
view.doCommand("block set element");
}
public boolean available(LpexView view) { return true; }
});
// define a delete line action that preserves the cursor column position
lpexView.defineAction("myDeleteLine", new LpexAction() {
public void doAction(LpexView view)
{
int displayPosition = view.queryInt("displayPosition");
if (displayPosition > 0)
{
view.doAction(view.actionId("deleteLine"));
view.doCommand("set displayPosition " + displayPosition);
}
}
public boolean available(LpexView view) { return true; }
});
// define the close join action in this view
lpexView.defineAction("closeJoin", new CloseJoin());
// define the find next selection action in this view
lpexView.defineAction("findNextSelection", new FindNextSelection());
// define a compare ignoring mnemonics action for the Java properties document parser
lpexView.defineAction("myPropCompare", new LpexAction() {
public void doAction(LpexView view)
{ view.doCommand("compare ignoredStyles \"a\" prompt"); }
public boolean available(LpexView view) { return true; }
});
// redefine the "findText" command to show context for the found text
lpexView.defineCommand("findText", new FindTextContextCommand());
// define a synonym "diff" for the "compare" command
lpexView.defineCommand("diff", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return view.doCommand("compare " + parameters); }
});
// define a synonym "k" for the "calc" command
lpexView.defineCommand("k", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return view.doCommand("calc " + parameters); }
});
// define a synonym "reload" for the "load" command
lpexView.defineCommand("reload", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return view.doCommand("load " + parameters); }
});
// define "pre" as shortcut for command-line implicit access of the "prefixArea" parameter
lpexView.defineCommand("pre", new LpexCommand() {
public boolean doCommand(LpexView view, String parameters)
{ return (parameters.trim().length() == 0)?
view.doCommand("set messageText prefixArea " + view.query("current.prefixArea")) :
view.doCommand("set prefixArea " + parameters); }
});
/*---------------------------------------------------*/
/* (re)register keys, mouse events, action, commands */
/*---------------------------------------------------*/
boolean isEmacs = "emacs".equals(baseProfile);
// set "Delete" and "Backspace" keys to keep it inside the current line
lpexView.doDefaultCommand("set keyAction.backSpace backSpaceInLine");
lpexView.doDefaultCommand("set keyAction.delete deleteInLine");
// set "Enter" keys to go to the next line (not split)
lpexView.doDefaultCommand("set keyAction.enter newLine");
lpexView.doDefaultCommand("set keyAction.numpadEnter newLine");
// set smarter "Home" and "End"
lpexView.doDefaultCommand("set keyAction.home contextHome");
lpexView.doDefaultCommand("set keyAction.end contextEnd");
// set "Alt+." key to zoom in/out a document segment (com.ibm.lpex.samples.ZoomAction)
lpexView.defineAction("zoom", new ZoomAction());
lpexView.doDefaultCommand("set keyAction.a-period zoom");
// set "Alt+F1" key to compose special characters (com.ibm.lpex.samples.ComposeAction)
lpexView.defineCommand("compose", ComposeAction.composeCommand);
lpexView.defineAction("compose", ComposeAction.composeAction);
lpexView.doDefaultCommand("set keyAction.a-f1 compose");
// set "Alt+J" to join next line's text with just one space in-between
lpexView.doDefaultCommand("set keyAction.a-j closeJoin");
// set "Alt+T" to do a block transfer (com.ibm.lpex.samples.BlockTransferAction)
lpexView.doDefaultCommand("set actionClass.blockTransfer " +
"com.ibm.lpex.samples.BlockTransferAction");
if (!isEmacs)
lpexView.doDefaultCommand("set keyAction.a-t blockTransfer");
// set "Ctrl+Backspace" to delete and preserve the cursor column position
lpexView.doDefaultCommand("set keyAction.c-backSpace.t.p.c myDeleteLine");
// set "Ctrl+N" to first search for the selected text, if any
if (!isEmacs)
lpexView.doDefaultCommand("set keyAction.c-n.t.p.c findNextSelection");
// set "Ctrl+Shift+V" to do a paste replace, like "Alt+Z" does a block replace
lpexView.doDefaultCommand("set keyAction.c-s-v.t pasteOverlay");
// set mouse button1 double-click to select the line (not word)
lpexView.doDefaultCommand("set mouseAction.1-pressed.2 myBlockMarkElement");
// set default selection type to character (rather than the default stream)
lpexView.doDefaultCommand("set block.defaultType character");
// register commands "detab", "entab" (com.ibm.lpex.samples.DetabCommand, .EntabCommand)
lpexView.doDefaultCommand("set commandClass.detab com.ibm.lpex.samples.DetabCommand");
lpexView.doDefaultCommand("set commandClass.entab com.ibm.lpex.samples.EntabCommand");
// set new mouse button 1 drag to start a new selection
MouseReselect.install(lpexView);
/*--------------------*/
/* redefine the popup */
/*--------------------*/
// add block-transfer action to the pop-up (context) menu, submenu "Selected"
StringBuilder sb = new StringBuilder(lpexView.query("current.popup"));
String transferSubmenu = " \"&Transfer selection\" blockTransfer ";
int i = sb.indexOf("popup.blockDelete ");
if (i >= 0)
{
sb.insert(i, transferSubmenu);
}
else
{
sb.append(" separator" + transferSubmenu);
}
// if properties file, add option to compare ignoring mnemonics on submenu "Source"
if ("prop".equals(parser(lpexView)))
{
String compareSubmenu = " \"Compare w/o &mnemonics...\" myPropCompare ";
i = sb.indexOf("popup.sourceMenu");
if (i >= 0)
{
sb.insert(i + "popup.sourceMenu".length(), compareSubmenu);
}
else
{
sb.append(" separator beginSubmenu popup.sourceMenu" + compareSubmenu + "endSubmenu");
}
}
lpexView.doDefaultCommand("set popup " + sb.toString());
// indicate this profile has run
lpexView.doDefaultCommand("set messageText * TestUserProfile settings in effect.");
}
/**
* Determines the document parser which will run after this user profile completes.
*
* @param lpexView the document view for which this profile is being run
* @return the document parser name, or <code>null</code> if none
*/
public static String parser(LpexView lpexView)
{
String parserName = null;
if (lpexView != null && !lpexView.queryOn("current.updateProfile.noParser"))
{
parserName = lpexView.query("current.updateProfile.parser");
// if "associated", determine the parser via the document name / name extension
if ("associated".equals(parserName))
{
parserName = null;
// 1.- try the particular document name
String baseName = baseName(lpexView);
if (baseName != null)
{
if (baseName.indexOf('.') == -1)
{
baseName += '.'; // e.g., "MAKEFILE."
}
parserName = lpexView.query("current.updateProfile.parserAssociation." + baseName);
}
// 2.- try just the document-name extension
if (parserName == null || parserName.length() == 0)
{
String extension = nameExtension(lpexView);
if (extension != null)
{
parserName = lpexView.query("current.updateProfile.parserAssociation." + extension);
}
}
}
}
return parserName;
}
/**
* Returns the base document name (e.g., "test.java"), or
* <code>null</code> if untitled.
*/
private static String baseName(LpexView lpexView)
{
String baseName = lpexView.query("name");
if (baseName != null)
{
// extract file name from path (NB assumes [canonical] doc name on current platform)
int i = baseName.lastIndexOf(System.getProperty("file.separator"));
if (i >= 0 && i < baseName.length()-1)
{
baseName = baseName.substring(i+1); // "test.java"
}
}
return baseName;
}
/**
* Returns the document name's extension (e.g., "java"), or
* <code>null</code> if untitled or no extension.
*/
private static String nameExtension(LpexView lpexView)
{
String extension = lpexView.query("name");
if (extension != null)
{
int i = extension.lastIndexOf('.');
extension = (i >= 0)? extension.substring(i+1) : null;
}
return extension;
}
}