//---------------------------------------------------------------------------- // 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; } }