PageRenderTime 92ms CodeModel.GetById 60ms app.highlight 25ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/Macros.java

#
Java | 1021 lines | 594 code | 114 blank | 313 comment | 76 complexity | 256b4131238bbaede747bbb0be71e568 MD5 | raw file
   1/*
   2 * Macros.java - Macro manager
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1999, 2004 Slava Pestov
   7 * Portions copyright (C) 2002 mike dillon
   8 *
   9 * This program is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU General Public License
  11 * as published by the Free Software Foundation; either version 2
  12 * of the License, or any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 *
  19 * You should have received a copy of the GNU General Public License
  20 * along with this program; if not, write to the Free Software
  21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  22 */
  23
  24package org.gjt.sp.jedit;
  25
  26//{{{ Imports
  27import javax.swing.*;
  28import java.awt.*;
  29import java.io.*;
  30import java.util.*;
  31import java.util.regex.Pattern;
  32import org.gjt.sp.jedit.msg.*;
  33import org.gjt.sp.util.Log;
  34//}}}
  35
  36/**
  37 * This class records and runs macros.<p>
  38 *
  39 * It also contains a few methods useful for displaying output messages
  40 * or obtaining input from a macro:
  41 *
  42 * <ul>
  43 * <li>{@link #confirm(Component,String,int)}</li>
  44 * <li>{@link #confirm(Component,String,int,int)}</li>
  45 * <li>{@link #error(Component,String)}</li>
  46 * <li>{@link #input(Component,String)}</li>
  47 * <li>{@link #input(Component,String,String)}</li>
  48 * <li>{@link #message(Component,String)}</li>
  49 * </ul>
  50 *
  51 * Note that plugins should not use the above methods. Call
  52 * the methods in the {@link GUIUtilities} class instead.
  53 *
  54 * @author Slava Pestov
  55 * @version $Id: Macros.java 5443 2006-06-18 18:51:40Z vanza $
  56 */
  57public class Macros
  58{
  59	//{{{ showRunScriptDialog() method
  60	/**
  61	 * Prompts for one or more files to run as macros
  62	 * @param view The view
  63	 * @since jEdit 4.0pre7
  64	 */
  65	public static void showRunScriptDialog(View view)
  66	{
  67		String[] paths = GUIUtilities.showVFSFileDialog(view,
  68			null,JFileChooser.OPEN_DIALOG,true);
  69		if(paths != null)
  70		{
  71			Buffer buffer = view.getBuffer();
  72			try
  73			{
  74				buffer.beginCompoundEdit();
  75
  76file_loop:			for(int i = 0; i < paths.length; i++)
  77					runScript(view,paths[i],false);
  78			}
  79			finally
  80			{
  81				buffer.endCompoundEdit();
  82			}
  83		}
  84	} //}}}
  85
  86	//{{{ runScript() method
  87	/**
  88	 * Runs the specified script.
  89	 * Unlike the {@link BeanShell#runScript(View,String,Reader,boolean)}
  90	 * method, this method can run scripts supported
  91	 * by any registered macro handler.
  92	 * @param view The view
  93	 * @param path The VFS path of the script
  94	 * @param ignoreUnknown If true, then unknown file types will be
  95	 * ignored; otherwise, a warning message will be printed and they will
  96	 * be evaluated as BeanShell scripts.
  97	 *
  98	 * @since jEdit 4.1pre2
  99	 */
 100	public static void runScript(View view, String path, boolean ignoreUnknown)
 101	{
 102		Handler handler = getHandlerForPathName(path);
 103		if(handler != null)
 104		{
 105			try
 106			{
 107				Macro newMacro = handler.createMacro(
 108					MiscUtilities.getFileName(path), path);
 109				newMacro.invoke(view);
 110			}
 111			catch (Exception e)
 112			{
 113				Log.log(Log.ERROR, Macros.class, e);
 114				return;
 115			}
 116			return;
 117		}
 118
 119		// only executed if above loop falls
 120		// through, ie there is no handler for
 121		// this file
 122		if(ignoreUnknown)
 123		{
 124			Log.log(Log.NOTICE,Macros.class,path +
 125				": Cannot find a suitable macro handler");
 126		}
 127		else
 128		{
 129			Log.log(Log.ERROR,Macros.class,path +
 130				": Cannot find a suitable macro handler, "
 131				+ "assuming BeanShell");
 132			getHandler("beanshell").createMacro(
 133				path,path).invoke(view);
 134		}
 135	} //}}}
 136
 137	//{{{ message() method
 138	/**
 139	 * Utility method that can be used to display a message dialog in a macro.
 140	 * @param comp The component to show the dialog on behalf of, this
 141	 * will usually be a view instance
 142	 * @param message The message
 143	 * @since jEdit 2.7pre2
 144	 */
 145	public static void message(Component comp, String message)
 146	{
 147		GUIUtilities.hideSplashScreen();
 148
 149		JOptionPane.showMessageDialog(comp,message,
 150			jEdit.getProperty("macro-message.title"),
 151			JOptionPane.INFORMATION_MESSAGE);
 152	} //}}}
 153
 154	//{{{ error() method
 155	/**
 156	 * Utility method that can be used to display an error dialog in a macro.
 157	 * @param comp The component to show the dialog on behalf of, this
 158	 * will usually be a view instance
 159	 * @param message The message
 160	 * @since jEdit 2.7pre2
 161	 */
 162	public static void error(Component comp, String message)
 163	{
 164		GUIUtilities.hideSplashScreen();
 165
 166		JOptionPane.showMessageDialog(comp,message,
 167			jEdit.getProperty("macro-message.title"),
 168			JOptionPane.ERROR_MESSAGE);
 169	} //}}}
 170
 171	//{{{ input() method
 172	/**
 173	 * Utility method that can be used to prompt for input in a macro.
 174	 * @param comp The component to show the dialog on behalf of, this
 175	 * will usually be a view instance
 176	 * @param prompt The prompt string
 177	 * @since jEdit 2.7pre2
 178	 */
 179	public static String input(Component comp, String prompt)
 180	{
 181		GUIUtilities.hideSplashScreen();
 182
 183		return input(comp,prompt,null);
 184	} //}}}
 185
 186	//{{{ input() method
 187	/**
 188	 * Utility method that can be used to prompt for input in a macro.
 189	 * @param comp The component to show the dialog on behalf of, this
 190	 * will usually be a view instance
 191	 * @param prompt The prompt string
 192	 * @since jEdit 3.1final
 193	 */
 194	public static String input(Component comp, String prompt, String defaultValue)
 195	{
 196		GUIUtilities.hideSplashScreen();
 197
 198		return (String)JOptionPane.showInputDialog(comp,prompt,
 199			jEdit.getProperty("macro-input.title"),
 200			JOptionPane.QUESTION_MESSAGE,null,null,defaultValue);
 201	} //}}}
 202
 203	//{{{ confirm() method
 204	/**
 205	 * Utility method that can be used to ask for confirmation in a macro.
 206	 * @param comp The component to show the dialog on behalf of, this
 207	 * will usually be a view instance
 208	 * @param prompt The prompt string
 209	 * @param buttons The buttons to display - for example,
 210	 * JOptionPane.YES_NO_CANCEL_OPTION
 211	 * @since jEdit 4.0pre2
 212	 */
 213	public static int confirm(Component comp, String prompt, int buttons)
 214	{
 215		GUIUtilities.hideSplashScreen();
 216
 217		return JOptionPane.showConfirmDialog(comp,prompt,
 218			jEdit.getProperty("macro-confirm.title"),buttons,
 219			JOptionPane.QUESTION_MESSAGE);
 220	} //}}}
 221
 222	//{{{ confirm() method
 223	/**
 224	 * Utility method that can be used to ask for confirmation in a macro.
 225	 * @param comp The component to show the dialog on behalf of, this
 226	 * will usually be a view instance
 227	 * @param prompt The prompt string
 228	 * @param buttons The buttons to display - for example,
 229	 * JOptionPane.YES_NO_CANCEL_OPTION
 230	 * @param type The dialog type - for example,
 231	 * JOptionPane.WARNING_MESSAGE
 232	 */
 233	public static int confirm(Component comp, String prompt, int buttons, int type)
 234	{
 235		GUIUtilities.hideSplashScreen();
 236
 237		return JOptionPane.showConfirmDialog(comp,prompt,
 238			jEdit.getProperty("macro-confirm.title"),buttons,type);
 239	} //}}}
 240
 241	//{{{ loadMacros() method
 242	/**
 243	 * Rebuilds the macros list, and sends a MacrosChanged message
 244	 * (views update their Macros menu upon receiving it)
 245	 * @since jEdit 2.2pre4
 246	 */
 247	public static void loadMacros()
 248	{
 249		macroActionSet.removeAllActions();
 250		macroHierarchy.removeAllElements();
 251		macroHash.clear();
 252
 253		// since subsequent macros with the same name are ignored,
 254		// load user macros first so that they override the system
 255		// macros.
 256		String settings = jEdit.getSettingsDirectory();
 257
 258		if(settings != null)
 259		{
 260			userMacroPath = MiscUtilities.constructPath(
 261				settings,"macros");
 262			loadMacros(macroHierarchy,"",new File(userMacroPath));
 263		}
 264
 265		if(jEdit.getJEditHome() != null)
 266		{
 267			systemMacroPath = MiscUtilities.constructPath(
 268				jEdit.getJEditHome(),"macros");
 269			loadMacros(macroHierarchy,"",new File(systemMacroPath));
 270		}
 271
 272		EditBus.send(new DynamicMenuChanged("macros"));
 273	} //}}}
 274
 275	//{{{ registerHandler() method
 276	/**
 277	 * Adds a macro handler to the handlers list
 278	 * @since jEdit 4.0pre6
 279	 */
 280	public static void registerHandler(Handler handler)
 281	{
 282		if (getHandler(handler.getName()) != null)
 283		{
 284			Log.log(Log.ERROR, Macros.class, "Cannot register more than one macro handler with the same name");
 285			return;
 286		}
 287
 288		Log.log(Log.DEBUG,Macros.class,"Registered " + handler.getName()
 289			+ " macro handler");
 290		macroHandlers.add(handler);
 291	} //}}}
 292
 293	//{{{ getHandlers() method
 294	/**
 295	 * Returns an array containing the list of registered macro handlers
 296	 * @since jEdit 4.0pre6
 297	 */
 298	public static Handler[] getHandlers()
 299	{
 300		Handler[] handlers = new Handler[macroHandlers.size()];
 301		return (Handler[])macroHandlers.toArray(handlers);
 302	} //}}}
 303
 304	//{{{ getHandlerForFileName() method
 305	/**
 306	 * Returns the macro handler suitable for running the specified file
 307	 * name, or null if there is no suitable handler.
 308	 * @since jEdit 4.1pre3
 309	 */
 310	public static Handler getHandlerForPathName(String pathName)
 311	{
 312		for (int i = 0; i < macroHandlers.size(); i++)
 313		{
 314			Handler handler = (Handler)macroHandlers.get(i);
 315			if (handler.accept(pathName))
 316				return handler;
 317		}
 318
 319		return null;
 320	} //}}}
 321
 322	//{{{ getHandler() method
 323	/**
 324	 * Returns the macro handler with the specified name, or null if
 325	 * there is no registered handler with that name.
 326	 * @since jEdit 4.0pre6
 327	 */
 328	public static Handler getHandler(String name)
 329	{
 330		Handler handler = null;
 331		for (int i = 0; i < macroHandlers.size(); i++)
 332		{
 333			handler = (Handler)macroHandlers.get(i);
 334			if (handler.getName().equals(name)) return handler;
 335		}
 336
 337		return null;
 338	}
 339	//}}}
 340
 341	//{{{ getMacroHierarchy() method
 342	/**
 343	 * Returns a vector hierarchy with all known macros in it.
 344	 * Each element of this vector is either a macro name string,
 345	 * or another vector. If it is a vector, the first element is a
 346	 * string label, the rest are again, either macro name strings
 347	 * or vectors.
 348	 * @since jEdit 2.6pre1
 349	 */
 350	public static Vector getMacroHierarchy()
 351	{
 352		return macroHierarchy;
 353	} //}}}
 354
 355	//{{{ getMacroActionSet() method
 356	/**
 357	 * Returns an action set with all known macros in it.
 358	 * @since jEdit 4.0pre1
 359	 */
 360	public static ActionSet getMacroActionSet()
 361	{
 362		return macroActionSet;
 363	} //}}}
 364
 365	//{{{ getMacro() method
 366	/**
 367	 * Returns the macro with the specified name.
 368	 * @param macro The macro's name
 369	 * @since jEdit 2.6pre1
 370	 */
 371	public static Macro getMacro(String macro)
 372	{
 373		return (Macro)macroHash.get(macro);
 374	} //}}}
 375
 376	//{{{ getLastMacro() method
 377	/**
 378	 * @since jEdit 4.3pre1
 379	 */
 380	public static Macro getLastMacro()
 381	{
 382		return lastMacro;
 383	} //}}}
 384
 385	//{{{ setLastMacro() method
 386	/**
 387	 * @since jEdit 4.3pre1
 388	 */
 389	public static void setLastMacro(Macro macro)
 390	{
 391		lastMacro = macro;
 392	} //}}}
 393
 394	//{{{ Macro class
 395	/**
 396	 * Encapsulates the macro's label, name and path.
 397	 * @since jEdit 2.2pre4
 398	 */
 399	public static class Macro extends EditAction
 400	{
 401		//{{{ Macro constructor
 402		public Macro(Handler handler, String name, String label, String path)
 403		{
 404			super(name);
 405			this.handler = handler;
 406			this.label = label;
 407			this.path = path;
 408		} //}}}
 409
 410		//{{{ getHandler() method
 411		public Handler getHandler()
 412		{
 413			return handler;
 414		}
 415		//}}}
 416
 417		//{{{ getPath() method
 418		public String getPath()
 419		{
 420			return path;
 421		} //}}}
 422
 423		//{{{ invoke() method
 424		public void invoke(View view)
 425		{
 426			setLastMacro(this);
 427
 428			if(view == null)
 429				handler.runMacro(null,this);
 430			else
 431			{
 432				try
 433				{
 434					view.getBuffer().beginCompoundEdit();
 435					handler.runMacro(view,this);
 436				}
 437				finally
 438				{
 439					view.getBuffer().endCompoundEdit();
 440				}
 441			}
 442		} //}}}
 443
 444		//{{{ getCode() method
 445		public String getCode()
 446		{
 447			return "Macros.getMacro(\"" + getName() + "\").invoke(view);";
 448		} //}}}
 449
 450		//{{{ macroNameToLabel() method
 451		public static String macroNameToLabel(String macroName)
 452		{
 453			int index = macroName.lastIndexOf('/');
 454			return macroName.substring(index + 1).replace('_', ' ');
 455		}
 456		//}}}
 457
 458		//{{{ Private members
 459		private Handler handler;
 460		private String path;
 461		String label;
 462		//}}}
 463	} //}}}
 464
 465	//{{{ recordTemporaryMacro() method
 466	/**
 467	 * Starts recording a temporary macro.
 468	 * @param view The view
 469	 * @since jEdit 2.7pre2
 470	 */
 471	public static void recordTemporaryMacro(View view)
 472	{
 473		String settings = jEdit.getSettingsDirectory();
 474
 475		if(settings == null)
 476		{
 477			GUIUtilities.error(view,"no-settings",new String[0]);
 478			return;
 479		}
 480		if(view.getMacroRecorder() != null)
 481		{
 482			GUIUtilities.error(view,"already-recording",new String[0]);
 483			return;
 484		}
 485
 486		Buffer buffer = jEdit.openFile(null,settings + File.separator
 487			+ "macros","Temporary_Macro.bsh",true,null);
 488
 489		if(buffer == null)
 490			return;
 491
 492		buffer.remove(0,buffer.getLength());
 493		buffer.insert(0,jEdit.getProperty("macro.temp.header"));
 494
 495		recordMacro(view,buffer,true);
 496	} //}}}
 497
 498	//{{{ recordMacro() method
 499	/**
 500	 * Starts recording a macro.
 501	 * @param view The view
 502	 * @since jEdit 2.7pre2
 503	 */
 504	public static void recordMacro(View view)
 505	{
 506		String settings = jEdit.getSettingsDirectory();
 507
 508		if(settings == null)
 509		{
 510			GUIUtilities.error(view,"no-settings",new String[0]);
 511			return;
 512		}
 513
 514		if(view.getMacroRecorder() != null)
 515		{
 516			GUIUtilities.error(view,"already-recording",new String[0]);
 517			return;
 518		}
 519
 520		String name = GUIUtilities.input(view,"record",null);
 521		if(name == null)
 522			return;
 523
 524		name = name.replace(' ','_');
 525
 526		Buffer buffer = jEdit.openFile(null,null,
 527			MiscUtilities.constructPath(settings,"macros",
 528			name + ".bsh"),true,null);
 529
 530		if(buffer == null)
 531			return;
 532
 533		buffer.remove(0,buffer.getLength());
 534		buffer.insert(0,jEdit.getProperty("macro.header"));
 535
 536		recordMacro(view,buffer,false);
 537	} //}}}
 538
 539	//{{{ stopRecording() method
 540	/**
 541	 * Stops a recording currently in progress.
 542	 * @param view The view
 543	 * @since jEdit 2.7pre2
 544	 */
 545	public static void stopRecording(View view)
 546	{
 547		Recorder recorder = view.getMacroRecorder();
 548
 549		if(recorder == null)
 550			GUIUtilities.error(view,"macro-not-recording",null);
 551		else
 552		{
 553			view.setMacroRecorder(null);
 554			if(!recorder.temporary)
 555				view.setBuffer(recorder.buffer);
 556			recorder.dispose();
 557		}
 558	} //}}}
 559
 560	//{{{ runTemporaryMacro() method
 561	/**
 562	 * Runs the temporary macro.
 563	 * @param view The view
 564	 * @since jEdit 2.7pre2
 565	 */
 566	public static void runTemporaryMacro(View view)
 567	{
 568		String settings = jEdit.getSettingsDirectory();
 569
 570		if(settings == null)
 571		{
 572			GUIUtilities.error(view,"no-settings",null);
 573			return;
 574		}
 575
 576		String path = MiscUtilities.constructPath(
 577			jEdit.getSettingsDirectory(),"macros",
 578			"Temporary_Macro.bsh");
 579
 580		if(jEdit.getBuffer(path) == null)
 581		{
 582			GUIUtilities.error(view,"no-temp-macro",null);
 583			return;
 584		}
 585
 586		Handler handler = getHandler("beanshell");
 587		Macro temp = handler.createMacro(path,path);
 588
 589		Buffer buffer = view.getBuffer();
 590
 591		try
 592		{
 593			buffer.beginCompoundEdit();
 594			temp.invoke(view);
 595		}
 596		finally
 597		{
 598			/* I already wrote a comment expaining this in
 599			 * Macro.invoke(). */
 600			if(buffer.insideCompoundEdit())
 601				buffer.endCompoundEdit();
 602		}
 603	} //}}}
 604
 605	//{{{ Private members
 606
 607	//{{{ Static variables
 608	private static String systemMacroPath;
 609	private static String userMacroPath;
 610
 611	private static ArrayList macroHandlers;
 612
 613	private static ActionSet macroActionSet;
 614	private static Vector macroHierarchy;
 615	private static Hashtable macroHash;
 616
 617	private static Macro lastMacro;
 618	//}}}
 619
 620	//{{{ Class initializer
 621	static
 622	{
 623		macroHandlers = new ArrayList();
 624		registerHandler(new BeanShellHandler());
 625		macroActionSet = new ActionSet(jEdit.getProperty("action-set.macros"));
 626		jEdit.addActionSet(macroActionSet);
 627		macroHierarchy = new Vector();
 628		macroHash = new Hashtable();
 629	} //}}}
 630
 631	//{{{ loadMacros() method
 632	private static void loadMacros(Vector vector, String path, File directory)
 633	{
 634		lastMacro = null;
 635
 636		File[] macroFiles = directory.listFiles();
 637		if(macroFiles == null || macroFiles.length == 0)
 638			return;
 639
 640		for(int i = 0; i < macroFiles.length; i++)
 641		{
 642			File file = macroFiles[i];
 643			String fileName = file.getName();
 644			if(file.isHidden())
 645			{
 646				/* do nothing! */
 647				continue;
 648			}
 649			else if(file.isDirectory())
 650			{
 651				String submenuName = fileName.replace('_',' ');
 652				Vector submenu = null;
 653				//{{{ try to merge with an existing menu first
 654				for(int j = 0; j < vector.size(); j++)
 655				{
 656					Object obj = vector.get(j);
 657					if(obj instanceof Vector)
 658					{
 659						Vector vec = (Vector)obj;
 660						if(((String)vec.get(0)).equals(submenuName))
 661						{
 662							submenu = vec;
 663							break;
 664						}
 665					}
 666				} //}}}
 667				if(submenu == null)
 668				{
 669					submenu = new Vector();
 670					submenu.addElement(submenuName);
 671					vector.addElement(submenu);
 672				}
 673
 674				loadMacros(submenu,path + fileName + '/',file);
 675			}
 676			else
 677			{
 678				addMacro(file,path,vector);
 679			}
 680		}
 681	} //}}}
 682
 683	//{{{ addMacro() method
 684	private static void addMacro(File file, String path, Vector vector)
 685	{
 686		String fileName = file.getName();
 687		Handler handler = getHandlerForPathName(file.getPath());
 688
 689		if(handler == null)
 690			return;
 691
 692		try
 693		{
 694			// in case macro file name has a space in it.
 695			// spaces break the view.toolBar property, for instance,
 696			// since it uses spaces to delimit action names.
 697			String macroName = (path + fileName).replace(' ','_');
 698			Macro newMacro = handler.createMacro(macroName,
 699				file.getPath());
 700			// ignore if already added.
 701			// see comment in loadMacros().
 702			if(macroHash.get(newMacro.getName()) != null)
 703				return;
 704
 705			vector.addElement(newMacro.getName());
 706			jEdit.setTemporaryProperty(newMacro.getName()
 707				+ ".label",
 708				newMacro.label);
 709			jEdit.setTemporaryProperty(newMacro.getName()
 710				+ ".mouse-over",
 711				handler.getLabel() + " - " + file.getPath());
 712			macroActionSet.addAction(newMacro);
 713			macroHash.put(newMacro.getName(),newMacro);
 714		}
 715		catch (Exception e)
 716		{
 717			Log.log(Log.ERROR, Macros.class, e);
 718			macroHandlers.remove(handler);
 719		}
 720	} //}}}
 721
 722	//{{{ recordMacro() method
 723	/**
 724	 * Starts recording a macro.
 725	 * @param view The view
 726	 * @param buffer The buffer to record to
 727	 * @param temporary True if this is a temporary macro
 728	 * @since jEdit 3.0pre5
 729	 */
 730	private static void recordMacro(View view, Buffer buffer, boolean temporary)
 731	{
 732		view.setMacroRecorder(new Recorder(view,buffer,temporary));
 733
 734		// setting the message to 'null' causes the status bar to check
 735		// if a recording is in progress
 736		view.getStatus().setMessage(null);
 737	} //}}}
 738
 739	//}}}
 740
 741	//{{{ Recorder class
 742	/**
 743	 * Handles macro recording.
 744	 */
 745	public static class Recorder implements EBComponent
 746	{
 747		View view;
 748		Buffer buffer;
 749		boolean temporary;
 750
 751		boolean lastWasInput;
 752		boolean lastWasOverwrite;
 753		int overwriteCount;
 754
 755		//{{{ Recorder constructor
 756		public Recorder(View view, Buffer buffer, boolean temporary)
 757		{
 758			this.view = view;
 759			this.buffer = buffer;
 760			this.temporary = temporary;
 761			EditBus.addToBus(this);
 762		} //}}}
 763
 764		//{{{ record() method
 765		public void record(String code)
 766		{
 767			flushInput();
 768
 769			append("\n");
 770			append(code);
 771		} //}}}
 772
 773		//{{{ record() method
 774		public void record(int repeat, String code)
 775		{
 776			if(repeat == 1)
 777				record(code);
 778			else
 779			{
 780				record("for(int i = 1; i <= " + repeat + "; i++)\n"
 781					+ "{\n"
 782					+ code + "\n"
 783					+ "}");
 784			}
 785		} //}}}
 786
 787		//{{{ recordInput() method
 788		/**
 789		 * @since jEdit 4.2pre5
 790		 */
 791		public void recordInput(int repeat, char ch, boolean overwrite)
 792		{
 793			// record \n and \t on lines specially so that auto indent
 794			// can take place
 795			if(ch == '\n')
 796				record(repeat,"textArea.userInput(\'\\n\');");
 797			else if(ch == '\t')
 798				record(repeat,"textArea.userInput(\'\\t\');");
 799			else
 800			{
 801				StringBuffer buf = new StringBuffer();
 802				for(int i = 0; i < repeat; i++)
 803					buf.append(ch);
 804				recordInput(buf.toString(),overwrite);
 805			}
 806		} //}}}
 807
 808		//{{{ recordInput() method
 809		/**
 810		 * @since jEdit 4.2pre5
 811		 */
 812		public void recordInput(String str, boolean overwrite)
 813		{
 814			String charStr = MiscUtilities.charsToEscapes(str);
 815
 816			if(overwrite)
 817			{
 818				if(lastWasOverwrite)
 819				{
 820					overwriteCount++;
 821					append(charStr);
 822				}
 823				else
 824				{
 825					flushInput();
 826					overwriteCount = 1;
 827					lastWasOverwrite = true;
 828					append("\ntextArea.setSelectedText(\"" + charStr);
 829				}
 830			}
 831			else
 832			{
 833				if(lastWasInput)
 834					append(charStr);
 835				else
 836				{
 837					flushInput();
 838					lastWasInput = true;
 839					append("\ntextArea.setSelectedText(\"" + charStr);
 840				}
 841			}
 842		} //}}}
 843
 844		//{{{ handleMessage() method
 845		public void handleMessage(EBMessage msg)
 846		{
 847			if(msg instanceof BufferUpdate)
 848			{
 849				BufferUpdate bmsg = (BufferUpdate)msg;
 850				if(bmsg.getWhat() == BufferUpdate.CLOSED)
 851				{
 852					if(bmsg.getBuffer() == buffer)
 853						stopRecording(view);
 854				}
 855			}
 856		} //}}}
 857
 858		//{{{ append() method
 859		private void append(String str)
 860		{
 861			buffer.insert(buffer.getLength(),str);
 862		} //}}}
 863
 864		//{{{ dispose() method
 865		private void dispose()
 866		{
 867			flushInput();
 868
 869			for(int i = 0; i < buffer.getLineCount(); i++)
 870			{
 871				buffer.indentLine(i,true);
 872			}
 873
 874			EditBus.removeFromBus(this);
 875
 876			// setting the message to 'null' causes the status bar to
 877			// check if a recording is in progress
 878			view.getStatus().setMessage(null);
 879		} //}}}
 880
 881		//{{{ flushInput() method
 882		/**
 883		 * We try to merge consecutive inputs. This helper method is
 884		 * called when something other than input is to be recorded.
 885		 */
 886		private void flushInput()
 887		{
 888			if(lastWasInput)
 889			{
 890				lastWasInput = false;
 891				append("\");");
 892			}
 893
 894			if(lastWasOverwrite)
 895			{
 896				lastWasOverwrite = false;
 897				append("\");\n");
 898				append("offset = buffer.getLineEndOffset("
 899					+ "textArea.getCaretLine()) - 1;\n");
 900				append("buffer.remove(textArea.getCaretPosition(),"
 901					+ "Math.min(" + overwriteCount
 902					+ ",offset - "
 903					+ "textArea.getCaretPosition()));");
 904			}
 905		} //}}}
 906	} //}}}
 907
 908	//{{{ Handler class
 909	/**
 910	 * Encapsulates creating and invoking macros in arbitrary scripting languages
 911	 * @since jEdit 4.0pre6
 912	 */
 913	public static abstract class Handler
 914	{
 915		//{{{ getName() method
 916		public String getName()
 917		{
 918			return name;
 919		} //}}}
 920
 921		//{{{ getLabel() method
 922		public String getLabel()
 923		{
 924			return label;
 925		} //}}}
 926
 927		//{{{ accept() method
 928		public boolean accept(String path)
 929		{
 930			return filter.matcher(MiscUtilities.getFileName(path)).matches();
 931		} //}}}
 932
 933		//{{{ createMacro() method
 934		public abstract Macro createMacro(String macroName, String path);
 935		//}}}
 936
 937		//{{{ runMacro() method
 938		/**
 939		 * Runs the specified macro.
 940		 * @param view The view - may be null.
 941		 * @param macro The macro.
 942		 */
 943		public abstract void runMacro(View view, Macro macro);
 944		//}}}
 945
 946		//{{{ runMacro() method
 947		/**
 948		 * Runs the specified macro. This method is optional; it is
 949		 * called if the specified macro is a startup script. The
 950		 * default behavior is to simply call {@link #runMacro(View,Macros.Macro)}.
 951		 *
 952		 * @param view The view - may be null.
 953		 * @param macro The macro.
 954		 * @param ownNamespace  A hint indicating whenever functions and
 955		 * variables defined in the script are to be self-contained, or
 956		 * made available to other scripts. The macro handler may ignore
 957		 * this parameter.
 958		 * @since jEdit 4.1pre3
 959		 */
 960		public void runMacro(View view, Macro macro, boolean ownNamespace)
 961		{
 962			runMacro(view,macro);
 963		} //}}}
 964
 965		//{{{ Handler constructor
 966		protected Handler(String name)
 967		{
 968			this.name = name;
 969			label = jEdit.getProperty("macro-handler."
 970				+ name + ".label", name);
 971			try
 972			{
 973				filter = Pattern.compile(MiscUtilities.globToRE(
 974					jEdit.getProperty(
 975					"macro-handler." + name + ".glob")));
 976			}
 977			catch (Exception e)
 978			{
 979				throw new InternalError("Missing or invalid glob for handler " + name);
 980			}
 981		} //}}}
 982
 983		//{{{ Private members
 984		private String name;
 985		private String label;
 986		private Pattern filter;
 987		//}}}
 988	} //}}}
 989
 990	//{{{ BeanShellHandler class
 991	static class BeanShellHandler extends Handler
 992	{
 993		//{{{ BeanShellHandler constructor
 994		BeanShellHandler()
 995		{
 996			super("beanshell");
 997		} //}}}
 998
 999		//{{{ createMacro() method
1000		public Macro createMacro(String macroName, String path)
1001		{
1002			// Remove '.bsh'
1003			macroName = macroName.substring(0, macroName.length() - 4);
1004
1005			return new Macro(this, macroName,
1006				Macro.macroNameToLabel(macroName), path);
1007		} //}}}
1008
1009		//{{{ runMacro() method
1010		public void runMacro(View view, Macro macro)
1011		{
1012			BeanShell.runScript(view,macro.getPath(),null,true);
1013		} //}}}
1014
1015		//{{{ runMacro() method
1016		public void runMacro(View view, Macro macro, boolean ownNamespace)
1017		{
1018			BeanShell.runScript(view,macro.getPath(),null,ownNamespace);
1019		} //}}}
1020	} //}}}
1021}