PageRenderTime 545ms CodeModel.GetById 211ms app.highlight 314ms RepoModel.GetById 10ms app.codeStats 1ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/Macros.java

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