PageRenderTime 108ms CodeModel.GetById 64ms app.highlight 35ms RepoModel.GetById 2ms app.codeStats 1ms

/bundles/plugins-trunk/Console/console/Console.java

#
Java | 1165 lines | 802 code | 146 blank | 217 comment | 118 complexity | 5e824303e355d75124f9af44a6e8b12e MD5 | raw file
   1/*
   2 * Console.java - The console window
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 2000, 2005 Slava Pestov
   7 * parts Copyright (C) 2006 Alan Ezust
   8 * parts Copyright (C) 2010 Eric Le Lay
   9 *
  10 * This program is free software; you can redistribute it and/or
  11 * modify it under the terms of the GNU General Public License
  12 * as published by the Free Software Foundation; either version 2
  13 * of the License, or any later version.
  14 *
  15 * This program is distributed in the hope that it will be useful,
  16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18 * GNU General Public License for more details.
  19 *
  20 * You should have received a copy of the GNU General Public License
  21 * along with this program; if not, write to the Free Software
  22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  23 */
  24
  25package console;
  26
  27//{{{ Imports
  28import java.awt.BorderLayout;
  29import java.awt.Color;
  30import java.awt.Component;
  31import java.awt.Dimension;
  32import java.awt.Image;
  33import java.awt.image.BufferedImage;
  34import java.awt.Graphics2D;
  35import java.awt.Insets;
  36import java.awt.Toolkit;
  37import java.awt.event.ActionEvent;
  38import java.awt.event.ActionListener;
  39import java.awt.event.InputEvent;
  40import java.awt.event.KeyEvent;
  41import java.io.File;
  42import java.util.Arrays;
  43import java.util.ArrayList;
  44import java.util.HashMap;
  45import java.util.Iterator;
  46import java.util.Map;
  47
  48import javax.swing.*;
  49import javax.swing.border.EmptyBorder;
  50import javax.swing.text.*;
  51
  52import org.gjt.sp.jedit.*;
  53import org.gjt.sp.jedit.View;
  54import org.gjt.sp.jedit.gui.*;
  55import org.gjt.sp.jedit.msg.DockableWindowUpdate;
  56import org.gjt.sp.jedit.msg.VFSPathSelected;
  57import org.gjt.sp.jedit.msg.PluginUpdate;
  58import org.gjt.sp.jedit.msg.PropertiesChanged;
  59import org.gjt.sp.jedit.msg.ViewUpdate;
  60import org.gjt.sp.util.Log;
  61
  62import console.SystemShell.ConsoleState;
  63import errorlist.DefaultErrorSource;
  64import errorlist.ErrorSource;
  65import org.gjt.sp.util.StandardUtilities;
  66//}}}
  67
  68// {{{ class Console
  69/**
  70 * Console - an instance of a panel inside a dockablewindow.
  71 * May contain multiple Shells, each with its own shell state.
  72 *
  73 *
  74 * @version $Id: Console.java 21235 2012-03-01 02:59:00Z ezust $
  75 */
  76
  77public class Console extends JPanel
  78implements EBComponent, DefaultFocusComponent
  79{
  80	//{{{ Private members
  81
  82	//{{{ Icons
  83	private static final Icon RUN_AGAIN = GUIUtilities.loadIcon("RunAgain.png");
  84	private static final Icon RUN = GUIUtilities.loadIcon("Run.png");
  85	private static final Icon TO_BUFFER = GUIUtilities.loadIcon("RunToBuffer.png");
  86	private static final Icon STOP = GUIUtilities.loadIcon("Cancel.png");
  87	private static final Icon CLEAR = GUIUtilities.loadIcon("Clear.png");
  88	//}}}
  89
  90	//{{{ Instance variables
  91	private View view;
  92	private Map<String, ShellState> shellStateMap;
  93
  94	// A pointer to the currently selected Shell instance
  95	private Shell currentShell;
  96
  97	// The Output instance corresponding to the current shell.
  98	private ShellState shellState;
  99
 100	// The selector of shells
 101	private JComboBox shellCombo;
 102
 103	private RolloverButton runAgain, run, toBuffer, stop, clear;
 104	private JLabel animationLabel;
 105	private AnimatedIcon animation;
 106	private ConsolePane text;
 107	private Color infoColor, warningColor, errorColor, plainColor;
 108	private DefaultErrorSource errorSource;
 109	private ProjectTreeListener listener;
 110	// }}}
 111	// }}}
 112
 113	//{{{ Console constructor
 114	public Console(View view)
 115	{
 116		super(new BorderLayout());
 117
 118		this.view = view;
 119
 120		shellStateMap = new HashMap<String, ShellState>();
 121
 122		initGUI();
 123
 124		propertiesChanged();
 125		String defShell = jEdit.getProperty("console.shell.default", "System");
 126		if (defShell.equals(jEdit.getProperty("options.last-selected"))) {
 127			defShell = jEdit.getProperty("console.shell", "System");
 128		}
 129
 130		Shell s = Shell.getShell(defShell);
 131		if (s == null) s = Shell.getShell("System");
 132		setShell(s);
 133		load();
 134		propertiesChanged();
 135
 136	} //}}}
 137
 138	// {{{ methods
 139
 140	//{{{ focusOnDefaultComponent() method
 141	public void focusOnDefaultComponent()
 142	{
 143		text.requestFocus();
 144	} //}}}
 145
 146	//{{{ load() method
 147	public void load()
 148	{
 149		EditBus.addToBus(this);
 150		addProjectListener();
 151		errorSource = new DefaultErrorSource("error parsing");
 152	} //}}}
 153
 154	void addProjectListener()
 155	{
 156		if (listener != null) return;
 157		listener = new ProjectTreeListener(this);
 158	}
 159
 160	//{{{ unload() method
 161	public void unload()
 162	{
 163		EditBus.removeFromBus(this);
 164		if (listener != null) {
 165			EditBus.removeFromBus(listener);
 166			listener.finalize();
 167		}
 168		ErrorSource.unregisterErrorSource(errorSource);
 169		Iterator<ShellState> iter = shellStateMap.values().iterator();
 170		while(iter.hasNext())
 171		{
 172			ShellState state = iter.next();
 173			state.shell.closeConsole(this);
 174		}
 175		animation.stop();
 176	} //}}}
 177
 178	//{{{ getView() method
 179	public View getView()
 180	{
 181		return view;
 182	} //}}}
 183
 184	//{{{ getShell() method
 185	public Shell getShell()
 186	{
 187		String shellName = (String)shellCombo.getSelectedItem();
 188		return Shell.getShell(shellName);
 189	} //}}}
 190
 191	//{{{ setShell() method
 192	public Shell setShell(String shellStr)
 193	{
 194		Shell shell = Shell.getShell(shellStr);
 195		if (shell == null) throw new RuntimeException("Unknown Shell: " + shellStr);
 196		return setShell(shell);
 197	} //}}}
 198
 199	//{{{ setShell() method
 200	/**
 201	 * Creates a ShellState (output instance) if necessary.
 202	 * Sets the current active shell to be this new shell.
 203	 */
 204	public Shell setShell(Shell shell)
 205	{
 206		if(shell == null)
 207			return null;
 208
 209		String name = shell.getName();
 210		String shellHistory = getShellHistory(shell);
 211		String limit = jEdit.getProperty("console.historyLimit");
 212		if(limit != null)
 213		{
 214			try
 215			{
 216				HistoryModel.getModel(shellHistory).setMax(Integer.parseInt(limit));
 217			}
 218			catch(NumberFormatException nfe)
 219			{
 220				jEdit.unsetProperty("console.historyLimit");
 221			}
 222		}
 223		text.setHistoryModel(shellHistory);
 224
 225		shellState = shellStateMap.get(name);
 226		if(shellState == null)
 227		{
 228			shellState = new ShellState(shell);
 229			shellStateMap.put(shell.getName(),shellState);
 230			shell.printInfoMessage(shellState);
 231			shell.printPrompt(this,shellState);
 232		}
 233		jEdit.setProperty("console.shell", name);
 234		text.setDocument(shellState.scrollback);
 235		if (shell != this.currentShell) {
 236			shellCombo.setSelectedItem(name);
 237		}
 238		this.currentShell = shell;
 239		scrollToBottom();
 240		return shell;
 241	} //}}}
 242
 243	public void scrollToBottom() {
 244		SwingUtilities.invokeLater(new Runnable()
 245		{
 246			public void run()
 247			{
 248				text.setCaretPosition(text.getDocument().getLength());
 249				text.scrollRectToVisible(text.getVisibleRect());
 250				updateAnimation();
 251			}
 252		});
 253	}
 254
 255	//{{{ getConsolePane() method
 256	public ConsolePane getConsolePane()
 257	{
 258		return text;
 259	} //}}}
 260
 261	//{{{ getOutputPane() method
 262	/**
 263	 * @deprecated Use getConsolePane() instead.
 264	 */
 265	public JTextPane getOutputPane()
 266	{
 267		return text;
 268	} //}}}
 269
 270	//{{{ clear() method
 271	public void clear()
 272	{
 273		text.setText("");
 274		getShell().printInfoMessage(shellState);
 275	} //}}}
 276
 277	//{{{ getOutput() methods
 278	/**
 279	 * Returns the Output corresponding to a particular Shell, without changing
 280	 * the selected Shell.
 281	 */
 282	public Output getOutput(String shellName) {
 283		ShellState retval = shellStateMap.get(shellName);
 284		if (retval == null) {
 285			Shell s = Shell.getShell(shellName);
 286			retval = new ShellState(s);
 287			shellStateMap.put(shellName, retval);
 288		}
 289		return retval;
 290	}
 291	/**
 292	 * Returns the output instance for the currently selected Shell.
 293	 * @since Console 3.6
 294	 */
 295	public Output getOutput()
 296	{
 297		return shellState;
 298	} //}}}
 299
 300	//{{{ runLastCommand() method
 301	/**
 302	 * Meant to be used as a user action.
 303	 */
 304	public void runLastCommand()
 305	{
 306		HistoryModel history = text.getHistoryModel();
 307		if(history.getSize() == 0)
 308		{
 309			getToolkit().beep();
 310			return;
 311		}
 312		else
 313			run(getShell(),null, getOutput(), null, history.getItem(0));
 314	} //}}}
 315
 316	//{{{ handleMessage() method
 317	public void handleMessage(EBMessage msg)
 318	{
 319		if(msg instanceof PropertiesChanged) propertiesChanged();
 320		else if (msg instanceof DockableWindowUpdate) {
 321			DockableWindowUpdate dwu = (DockableWindowUpdate) msg;
 322			if (dwu.getWhat() != null &&  dwu.getWhat().equals(DockableWindowUpdate.ACTIVATED))
 323				if (dwu.getDockable().equals("console"))
 324					scrollToBottom();
 325		}
 326		else if(msg instanceof PluginUpdate)
 327			handlePluginUpdate((PluginUpdate)msg);
 328		else if (msg instanceof VFSPathSelected)
 329			handleNodeSelected((VFSPathSelected)msg);
 330		else if (msg instanceof ViewUpdate)
 331			handleViewUpdate((ViewUpdate)msg);
 332		else if (listener != null) {
 333			listener.handleMessage(msg);
 334		}
 335	} // }}}
 336
 337	//{{{ handleViewUpdate() method
 338	private void handleViewUpdate(ViewUpdate vu) {
 339		if (vu.getWhat() == ViewUpdate.CLOSED && vu.getView() == view)
 340			unload();
 341	}
 342	//}}}
 343
 344	//{{{ getErrorSource() method
 345	/**
 346	 * Returns this console's error source instance. Plugin shells can
 347	 * either add errors to this error source, or use their own; both
 348	 * methods will look the same to the user.
 349	 */
 350	public DefaultErrorSource getErrorSource()
 351	{
 352		return errorSource;
 353	} //}}}
 354
 355	//{{{ getInfoColor() method
 356	/**
 357	 * Returns the informational text color.
 358	 */
 359	public Color getInfoColor()
 360	{
 361		return infoColor;
 362	} //}}}
 363
 364	//{{{ getWarningColor() method
 365	/**
 366	 * Returns the warning text color.
 367	 */
 368	public Color getWarningColor()
 369	{
 370		return warningColor;
 371	} //}}}
 372
 373	//{{{ getErrorColor() method
 374	/**
 375	 * Returns the error text color.
 376	 */
 377	public Color getErrorColor()
 378	{
 379		return errorColor;
 380	} //}}}
 381
 382	// {{{ getPlainColor() method
 383	public Color getPlainColor() {
 384		return plainColor;
 385	} // }}}
 386
 387	// {{{ getId() method
 388	/** @return a unique identifier starting at 0, representing which instance of Console this is,
 389	    or -1 if that value can not be determined.
 390	*/
 391	public int getId() {
 392		int retval = 0;
 393		View v = jEdit.getFirstView();
 394		while (v != null) {
 395			/* In fact, this is not unique: there can be more than one Console per View.
 396			 * A better way of enumerating instances of Console for new floating
 397			 * instances is desired. */
 398			if (v == view) return retval;
 399			++retval;
 400			v = v.getNext();
 401		}
 402		return -1;
 403	} // }}}
 404
 405	//{{{ print() method
 406	/**
 407	 * @deprecated Do not use the console as an <code>Output</code>
 408	 * instance, use the <code>Output</code> given to you in
 409	 * <code>Shell.execute()</code> instead.
 410	 */
 411	public void print(Color color, String msg)
 412	{
 413		getOutput().print(color,msg);
 414	} //}}}
 415
 416	//{{{ writeAttrs() method
 417	/**
 418	 * @deprecated Do not use the console as an <code>Output</code>
 419	 * instance, use the <code>Output</code> given to you in
 420	 * <code>Shell.execute()</code> instead.
 421	 *
 422	 * see @ref Output for information about how to create additional
 423	 *    console Output instances.
 424	 */
 425	public void writeAttrs(AttributeSet attrs, String msg)
 426	{
 427		getOutput().writeAttrs(attrs,msg);
 428	} //}}}
 429
 430	//{{{ commandDone() method
 431	/**
 432	 * @deprecated Do not use the console as an <code>Output</code>
 433	 * instance, use the <code>Output</code> given to you in
 434	 * <code>Shell.execute()</code> instead.
 435	 */
 436	public void commandDone()
 437	{
 438		getOutput().commandDone();
 439	} //}}}
 440
 441	//{{{ getShellState() method
 442	/**
 443	 * @returns the Output of a Shell, assuming the Shell was already created....
 444	 *
 445	 * @since Console 4.0.2.
 446	 */
 447	public ShellState getShellState(Shell shell)
 448	{
 449		return shellStateMap.get(shell.getName());
 450	} //}}}
 451
 452	// {{{ stopAnimation() method
 453	public void stopAnimation() {
 454		shellState.commandRunning=false;
 455		animation.stop();
 456	} // }}}
 457
 458	// {{{ startAnimation method
 459	public void startAnimation() {
 460		currentShell = getShell();
 461		shellState = getShellState(currentShell);
 462		shellState.commandRunning = true;
 463		animationLabel.setVisible(true);
 464		animation.start();
 465	} // }}}
 466
 467	//{{{ run() methods
 468	/**
 469	 * Runs the specified command. Note that with most shells, this
 470	 * method returns immediately, and execution of the command continues
 471	 * in a different thread. If you want to wait for command completion,
 472	 * call the <code>waitFor()</code> method of the shell instance.
 473	 *
 474	 * @param shell The shell instance. Obtain one either with
 475	 * <code>Console.getShell()</code> or <code>Shell.getShell()</code>.
 476	 * @param input The input to send to the command
 477	 * @param output The output stream. Either the return value of
 478	 * <code>getOutput()</code>, or a new instance of
 479	 * <code>BufferOutput</code>.
 480	 * @param error The error stream. Either the return value of
 481	 * <code>getOutput()</code>, or a new instance of
 482	 * <code>BufferOutput</code>.
 483	 * @param cmd The command
 484	 */
 485	public void run(Shell shell, String input, Output output,
 486		Output error, String cmd)
 487	{
 488		run(shell,input,output,error,cmd,true);
 489	}
 490
 491
 492	public void run(Shell shell, String command) {
 493		run (shell, null, null, null, command);
 494	}
 495	/**
 496	 * Convenience function currently used by some beanshell macros.
 497	 * @param shell the shell to execute it in
 498	 * @param output something to write to
 499	 * @param command the thing to execute
 500	 *
 501	 */
 502	public void run(Shell shell, Output output, String command) {
 503		run(shell, null, output, null, command);
 504	}
 505
 506	private void run(Shell shell, String input, Output output,
 507		Output error, String cmd, boolean printInput)
 508	{
 509		if(cmd.length() != 0)
 510			HistoryModel.getModel(getShellHistory(shell)).addItem(cmd);
 511		text.setHistoryIndex(-1);
 512
 513		if(cmd.startsWith(":"))
 514		{
 515			Shell _shell = Shell.getShell(cmd.substring(1));
 516			if(_shell != null)
 517			{
 518				text.setInput(null);
 519				setShell(_shell);
 520				return;
 521			}
 522		}
 523		setShell(shell);
 524
 525		if(output == null)
 526			output = getOutput();
 527		if(error == null)
 528			error = getOutput();
 529
 530		this.text.setCaretPosition(this.text.getDocument().getLength());
 531
 532		ShellState state = getShellState(shell);
 533		state.commandRunning = true;
 534
 535		updateAnimation();
 536
 537
 538		Macros.Recorder recorder = view.getMacroRecorder();
 539		if(recorder != null)
 540		{
 541			if(output instanceof BufferOutput)
 542			{
 543				recorder.record("runCommandToBuffer(view,\""
 544					+ shell.getName()
 545					+ "\",\""
 546					+ StandardUtilities.charsToEscapes(cmd)
 547					+ "\");");
 548			}
 549			else
 550			{
 551				recorder.record("runCommandInConsole(view,\""
 552					+ shell.getName()
 553					+ "\",\""
 554					+ StandardUtilities.charsToEscapes(cmd)
 555					+ "\");");
 556			}
 557		}
 558
 559		if(printInput)
 560			error.print(infoColor, cmd);
 561		else
 562			error.print(null,"");
 563
 564		errorSource.clear();
 565		ErrorSource.unregisterErrorSource(errorSource);
 566		try
 567		{
 568			shell.execute(this, input, output, null, cmd);
 569			startAnimation();
 570//			shell.execute(this,input,output,error,cmd);
 571		}
 572		catch(RuntimeException e)
 573		{
 574			print(getErrorColor(),e.toString());
 575			Log.log(Log.ERROR,this,e);
 576			output.commandDone();
 577//			error.commandDone();
 578		}
 579	} //}}}
 580
 581	//{{{ initGUI() method
 582	private void initGUI()
 583	{
 584		Box box = new Box(BoxLayout.X_AXIS);
 585
 586		shellCombo = new JComboBox();
 587		updateShellList();
 588		shellCombo.addActionListener(new ActionHandler());
 589		shellCombo.setRequestFocusEnabled(false);
 590
 591		box.add(shellCombo);
 592		box.add(Box.createGlue());
 593
 594		animationLabel = new JLabel();
 595		animationLabel.setBorder(new EmptyBorder(2,3,2,3));
 596
 597		initAnimation();
 598
 599		animationLabel.setIcon(animation);
 600		animationLabel.setVisible(false);
 601		animation.stop();
 602		box.add(animationLabel);
 603
 604
 605		box.add(runAgain = new RolloverButton(RUN_AGAIN));
 606		runAgain.setToolTipText(jEdit.getProperty("run-last-console-command.label"));
 607		Insets margin = new Insets(0,0,0,0);
 608		runAgain.setMargin(margin);
 609		runAgain.addActionListener(new ActionHandler());
 610		runAgain.setRequestFocusEnabled(false);
 611
 612		box.add(run = new RolloverButton(RUN));
 613		run.setToolTipText(jEdit.getProperty("console.run"));
 614		margin = new Insets(0,0,0,0);
 615		run.setMargin(margin);
 616		run.addActionListener(new RunActionHandler());
 617		run.setRequestFocusEnabled(false);
 618
 619		box.add(toBuffer = new RolloverButton(TO_BUFFER));
 620		toBuffer.setToolTipText(jEdit.getProperty("console.to-buffer"));
 621		toBuffer.setMargin(margin);
 622		toBuffer.addActionListener(new RunActionHandler());
 623		toBuffer.setRequestFocusEnabled(false);
 624
 625		box.add(stop = new RolloverButton(STOP));
 626		stop.setToolTipText(jEdit.getProperty("console.stop"));
 627		stop.setMargin(margin);
 628		stop.addActionListener(new ActionHandler());
 629		stop.setRequestFocusEnabled(false);
 630
 631		box.add(clear = new RolloverButton(CLEAR));
 632		clear.setToolTipText(jEdit.getProperty("console.clear"));
 633		clear.setMargin(margin);
 634		clear.addActionListener(new ActionHandler());
 635		clear.setRequestFocusEnabled(false);
 636
 637
 638		add(BorderLayout.NORTH,box);
 639
 640		text = new ConsolePane();
 641		InputMap inputMap = text.getInputMap();
 642
 643		/* Press ctrl-enter to run command to buffer */
 644		KeyStroke ctrlEnter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_MASK);
 645		inputMap.put(ctrlEnter, new RunToBuffer());
 646
 647		/* Press tab to complete input */
 648		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,0),
 649			new CompletionAction());
 650
 651		/* Press C+d to send EOF */
 652		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D,
 653			InputEvent.CTRL_MASK),
 654			new EOFAction());
 655
 656		/* Press C+z to detach process */
 657		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z,
 658			InputEvent.CTRL_MASK),
 659			new DetachAction());
 660
 661		text.addActionListener(new RunActionHandler());
 662		JScrollPane scroller = new JScrollPane(text);
 663		scroller.setPreferredSize(new Dimension(400,100));
 664		add(BorderLayout.CENTER,scroller);
 665	} //}}}
 666
 667	//{{{ initAnimation() method
 668	private void initAnimation()
 669	{
 670		// TODO: First frame of animation icon should be visible at gui init
 671
 672		Toolkit toolkit = getToolkit();
 673		Image processImg = toolkit.getImage(Console.class.getResource("/console/process-working.png"));
 674		Image standbyImg = null;
 675
 676		int iconSize = 22;
 677
 678		ArrayList<Image> frames = new ArrayList<Image>();
 679
 680		// Wait for the image to load by setting up an icon and discarding it again
 681		new ImageIcon(processImg).getImage();
 682		int procImgWidth = processImg.getWidth(null);
 683		int procImgHeight = processImg.getHeight(null);
 684
 685		int currentX = 0, currentY = 0;
 686		int frameNo = 0;
 687		while(currentY < procImgHeight)
 688		{
 689			BufferedImage bufImg = new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
 690			Graphics2D bufGraphics = bufImg.createGraphics();
 691			bufGraphics.drawImage(processImg,
 692				0,
 693				0,
 694				iconSize-1,
 695				iconSize-1,
 696				currentX,
 697				currentY,
 698				currentX + iconSize - 1,
 699				currentY + iconSize - 1,
 700				null);
 701
 702			// First frame is the standby icon
 703			if(frameNo == 0)
 704				standbyImg = bufImg;
 705			else
 706				frames.add(bufImg);
 707
 708			frameNo++;
 709			currentX += iconSize;
 710			if(currentX + iconSize > procImgWidth)
 711			{
 712				currentX = 0;
 713				currentY += iconSize;
 714			}
 715		}
 716
 717		animation = new AnimatedIcon(
 718			standbyImg,
 719			frames.toArray(new Image[0]),
 720			25,
 721			animationLabel
 722		);
 723	}
 724	//}}}
 725
 726	//{{{ updateShellList() method
 727	private void updateShellList()
 728	{
 729		String[] shells = Shell.getShellNames();
 730		Arrays.sort(shells,new StandardUtilities.StringCompare<String>(true));
 731		shellCombo.setModel(new DefaultComboBoxModel(shells));
 732		shellCombo.setMaximumSize(shellCombo.getPreferredSize());
 733	} //}}}
 734
 735	//{{{ propertiesChanged() method
 736	private void propertiesChanged()
 737	{
 738		if (jEdit.getBooleanProperty("textColors")) {
 739			plainColor = jEdit.getColorProperty("view.fgColor", Color.BLACK);
 740			text.setBackground(jEdit.getColorProperty("view.bgColor", Color.WHITE));
 741			text.setCaretColor(jEdit.getColorProperty("view.caretColor", plainColor));
 742		}
 743		else {
 744			plainColor = jEdit.getColorProperty("console.plainColor", Color.BLACK);
 745			text.setBackground(jEdit.getColorProperty("console.bgColor", Color.WHITE));
 746			text.setCaretColor(jEdit.getColorProperty("console.caretColor", plainColor));
 747		}
 748		text.setForeground(plainColor);
 749		text.setFont(jEdit.getFontProperty("console.font"));
 750		infoColor = jEdit.getColorProperty("console.infoColor");
 751		warningColor = jEdit.getColorProperty("console.warningColor");
 752		errorColor = jEdit.getColorProperty("console.errorColor");
 753		
 754	} //}}}
 755
 756	// {{{ handleNodeSelected()
 757	public void handleNodeSelected(VFSPathSelected msg) {
 758//		Log.log(Log.WARNING, this, "VFSPathSelected: " + msg.getPath());
 759		if (view != msg.getView()) return;
 760		if (!isVisible()) return;
 761		if (!jEdit.getBooleanProperty("console.changedir.nodeselect")) return;
 762		String path = msg.getPath();
 763		File f = new File(path);
 764		if (!f.isDirectory())
 765		{
 766			path = f.getParent();
 767			f = new File(path);
 768			if (!f.isDirectory()) return;
 769		}
 770		Shell sysShell = Shell.getShell("System");
 771		SystemShell ss = (SystemShell) sysShell;
 772		ConsoleState cs = ss.getConsoleState(this);
 773		if (cs.currentDirectory.equals(path)) return;
 774		Output output = getShellState(sysShell);
 775		String cmd = "cd \"" + path + "\"";
 776		sysShell.execute(this, cmd, output);
 777		output.print(getPlainColor(), "\n");
 778		sysShell.printPrompt(this, output);
 779	} //}}}
 780
 781	//{{{ handlePluginUpdate() method
 782	public void handlePluginUpdate(PluginUpdate pmsg)
 783	{
 784		if(pmsg.getWhat() == PluginUpdate.LOADED
 785			|| pmsg.getWhat() == PluginUpdate.UNLOADED)
 786		{
 787			boolean resetShell = false;
 788
 789			updateShellList();
 790
 791			Iterator<String> iter = shellStateMap.keySet().iterator();
 792			while(iter.hasNext())
 793			{
 794				String name = iter.next();
 795				if(Shell.getShell(name) == null)
 796				{
 797					if(this.currentShell.getName().equals(name))
 798						resetShell = true;
 799					iter.remove();
 800				}
 801			}
 802
 803			if(resetShell)
 804				setShell((String)shellStateMap.keySet().iterator().next());
 805			else
 806				shellCombo.setSelectedItem(currentShell.getName());
 807		}
 808	} //}}}
 809
 810	//{{{ updateAnimation() method
 811	public void updateAnimation()
 812	{
 813
 814		if(shellState.commandRunning)
 815		{
 816			animationLabel.setVisible(true);
 817			animation.start();
 818		}
 819		else
 820		{
 821			animationLabel.setVisible(false);
 822			animation.stop();
 823		}
 824	} //}}}
 825
 826	//{{{ complete() method
 827	/**
 828	 * TODO: update this so it uses the current APIs.
 829	 */
 830	private void complete()
 831	{
 832		String input = text.getInput();
 833		int cmdStart = text.getInputStart();
 834		int caret = text.getCaretPosition();
 835		int offset = caret - cmdStart;
 836		Shell.CompletionInfo info = currentShell.getCompletions(this,
 837			input.substring(0,offset));
 838
 839		if(info == null || info.completions.length == 0)
 840			getToolkit().beep();
 841		else if(info.completions.length == 1)
 842		{
 843			text.select(cmdStart + info.offset,caret);
 844			text.replaceSelection(info.completions[0]);
 845		}
 846		else //if(info.completions.length > 1)
 847		{
 848			// Find a partial completion
 849			String longestCommonStart = MiscUtilities
 850				.getLongestPrefix(info.completions,
 851				ProcessRunner.getProcessRunner()
 852				.isCaseSensitive());
 853
 854			if(longestCommonStart.length() != 0)
 855			{
 856				if(offset - info.offset
 857					!= longestCommonStart.length())
 858				{
 859					text.select(cmdStart + info.offset,caret);
 860					text.replaceSelection(longestCommonStart);
 861					return;
 862				}
 863			}
 864
 865			getOutput().print(null,"");
 866
 867			getOutput().print(getInfoColor(), jEdit.getProperty(
 868				"console.completions"));
 869
 870			Arrays.sort(info.completions,new StandardUtilities.StringCompare<String>(true));
 871
 872			for(int i = 0; i < info.completions.length; i++)
 873				print(null,info.completions[i]);
 874
 875			getOutput().print(getInfoColor(),jEdit.getProperty(
 876				"console.completions-end"));
 877
 878			currentShell.printPrompt(this,shellState);
 879			cmdStart = text.getDocument().getLength();
 880			getOutput().writeAttrs(null,input);
 881			text.setInputStart(cmdStart);
 882			text.setCaretPosition(cmdStart + offset);
 883		}
 884	} //}}}
 885
 886	//{{{ getShellHistory() method
 887	private String getShellHistory(Shell shell)
 888	{
 889		return "console." + shell.getName();
 890	} //}}}
 891	// }}}
 892
 893	// {{{ Inner classes
 894	// {{{ ShellState class
 895
 896	/**
 897	 * Each Shell of a Console has its own ShellState
 898	 * A ShellState is a writable Output.
 899	 * It holds the document which is the "scrollback buffer".
 900	 */
 901	public class ShellState implements Output
 902	{
 903		Shell shell;
 904		Document scrollback;
 905		private boolean commandRunning;
 906
 907		//{{{ getDocument() method
 908		public Document getDocument()
 909		{
 910			return scrollback;
 911		} //}}}
 912
 913		public ShellState(Shell shell)
 914		{
 915			this.shell = shell;
 916			commandRunning = false;
 917			scrollback = new DefaultStyledDocument();
 918			((DefaultStyledDocument)scrollback).setDocumentFilter(new LengthFilter());
 919
 920			// ick! talk about tightly coupling two classes.
 921			shell.openConsole(Console.this);
 922		}
 923
 924		//{{{ getInputStart() method
 925		public int getInputStart()
 926		{
 927			return ((Integer)scrollback.getProperty(
 928				ConsolePane.InputStart)).intValue();
 929		} //}}}
 930
 931		//{{{ setInputStart() method
 932		public void setInputStart(int cmdStart)
 933		{
 934			scrollback.putProperty(ConsolePane.InputStart,
 935				Integer.valueOf(cmdStart));
 936		} //}}}
 937
 938		//{{{ print() method
 939		public void print(Color color, String msg)
 940		{
 941			writeAttrs(ConsolePane.colorAttributes(color),
 942				msg + "\n");
 943		} //}}}
 944
 945		//{{{ writeAttrs() method
 946		public void writeAttrs(final AttributeSet attrs,
 947			final String msg)
 948		{
 949			if(SwingUtilities.isEventDispatchThread())
 950				writeSafely(attrs,msg);
 951			else
 952			{
 953				SwingUtilities.invokeLater(new Runnable()
 954				{
 955					public void run()
 956					{
 957						writeSafely(attrs,msg);
 958					}
 959				});
 960			}
 961		} //}}}
 962
 963		//{{{ setAttrs() method
 964		public void setAttrs(final int length, final AttributeSet attrs)
 965		{
 966			if(SwingUtilities.isEventDispatchThread())
 967			setSafely(scrollback.getLength() - length, length, attrs);
 968			else
 969			{
 970				SwingUtilities.invokeLater(new Runnable()
 971				{
 972					public void run()
 973					{
 974						setSafely(scrollback.getLength() - length, length, attrs);
 975					//writeSafely(null, " - " + (scrollback.getLength() - length) + ":" + length);
 976					}
 977				});
 978			}
 979		} //}}}
 980
 981		//{{{ setSafely() method
 982		private void setSafely(int start, int length, AttributeSet attrs)
 983		{
 984			((DefaultStyledDocument)scrollback).setCharacterAttributes(start, length,
 985				attrs, true);
 986		} //}}}
 987
 988
 989		//{{{ commandDone() method
 990		public void commandDone()
 991		{
 992			SwingUtilities.invokeLater(new Runnable()
 993			{
 994				public void run()
 995				{
 996					// WTF?
 997					if(commandRunning)
 998						shell.printPrompt(Console.this, ShellState.this);
 999					commandRunning = false;
1000//					updateAnimation();
1001					stopAnimation();
1002					if(errorSource.getErrorCount() != 0)
1003						ErrorSource.registerErrorSource(errorSource);
1004				}
1005			});
1006		} //}}}
1007
1008		//{{{ writeSafely() method
1009		private void writeSafely(AttributeSet attrs, String msg)
1010		{
1011			try
1012			{
1013				if(attrs != null && StyleConstants.getIcon(attrs) != null)
1014					msg = " ";
1015				scrollback.insertString(scrollback.getLength(),
1016					msg,attrs);
1017			}
1018			catch(BadLocationException bl)
1019			{
1020				Log.log(Log.ERROR,this,bl);
1021			}
1022
1023			setInputStart(scrollback.getLength());
1024		} //}}}
1025	} //}}}
1026
1027	// {{{ LengthFilter class
1028	static private class LengthFilter extends DocumentFilter
1029	{
1030		public LengthFilter()
1031		{
1032			super();
1033		}
1034
1035		//{{{ insertString() method
1036		public void insertString(DocumentFilter.FilterBypass fb, int offset,
1037			String str, AttributeSet attr) throws BadLocationException
1038		{
1039			replace(fb, offset, 0, str, attr);
1040		} //}}}
1041
1042		//{{{ replace() method
1043		public void replace(DocumentFilter.FilterBypass fb, int offset,
1044			int length, String str, AttributeSet attrs)
1045				throws BadLocationException
1046		{
1047			int newLength = fb.getDocument().getLength() -
1048				length + str.length();
1049			fb.replace(offset, length, str, attrs);
1050			int limit = jEdit.getIntegerProperty("console.outputLimit", DEFAULT_LIMIT);
1051			if(newLength > limit)
1052				fb.remove(0, newLength - limit - 1);
1053		} //}}}
1054
1055		// Not so large default limit to avoid performance down
1056		// with large output.
1057		// This will be sufficient to first use.
1058		private final int DEFAULT_LIMIT = 80/*column*/ * 1000/*lines*/;
1059	} //}}}
1060
1061	// {{{ EvalAction class
1062	public static class EvalAction extends AbstractAction
1063	{
1064		private String command;
1065
1066		public EvalAction(String label, String command)
1067		{
1068			super(label);
1069			this.command = command;
1070		}
1071
1072		public void actionPerformed(ActionEvent evt)
1073		{
1074			Console console = (Console)GUIUtilities.getComponentParent(
1075				(Component)evt.getSource(),Console.class);
1076			
1077			console.run(console.getShell(), null, console.getOutput(),
1078					null, command);
1079		}
1080	} //}}}
1081
1082	// {{{ ActionHandler class
1083	class ActionHandler implements ActionListener
1084	{
1085		public void actionPerformed(ActionEvent evt)
1086		{
1087			Object source = evt.getSource();
1088
1089			if(source == shellCombo)
1090				setShell((String)shellCombo.getSelectedItem());
1091			else if(source == runAgain)
1092				runLastCommand();
1093			else if(source == stop)
1094				getShell().stop(Console.this);
1095			else if(source == clear)
1096			{
1097				clear();
1098				getShell().printPrompt(Console.this,shellState);
1099			}
1100		}
1101	} //}}}
1102
1103	// {{{ RunActionHandler class
1104	class RunActionHandler implements ActionListener
1105	{
1106		public void actionPerformed(ActionEvent evt)
1107		{
1108			String cmd = text.getInput();
1109			Object source = evt.getSource();
1110			String input = view.getTextArea().getSelectedText();
1111			Output output = shellState;
1112			boolean printInput = false;
1113
1114			if(source == run)
1115				printInput = true;
1116			else if(source == toBuffer)
1117			{
1118				output = new BufferOutput(Console.this);
1119			}
1120
1121			run(getShell(), input, output, shellState, cmd, printInput);
1122		}
1123	} //}}}
1124
1125	// {{{ runToBuffer class
1126	class RunToBuffer extends AbstractAction
1127	{
1128		public void actionPerformed(ActionEvent evt) {
1129			String cmd = text.getInput();
1130			Output output = new BufferOutput(Console.this);
1131			run(getShell(), null, output, shellState, cmd, false);
1132		}
1133
1134	}
1135	// }}}
1136
1137	// {{{ CompletionAction class
1138	class CompletionAction extends AbstractAction
1139	{
1140		public void actionPerformed(ActionEvent evt)
1141		{
1142			complete();
1143		}
1144	} //}}}
1145
1146	// {{{ EOFAction class
1147	class EOFAction extends AbstractAction
1148	{
1149		public void actionPerformed(ActionEvent evt)
1150		{
1151			currentShell.endOfFile(Console.this);
1152		}
1153	} //}}}
1154
1155	// {{{ DetachAction class
1156	class DetachAction extends AbstractAction
1157	{
1158		public void actionPerformed(ActionEvent evt)
1159		{
1160			currentShell.detach(Console.this);
1161		}
1162	} //}}}
1163	// }}}
1164	private static final long serialVersionUID = -9185531673809120587L;
1165} // }}}