new LogConsole api, android logcat toolwindow
[fedora-idea.git] / platform / lang-impl / src / com / intellij / diagnostic / logging / LogConsoleBase.java
blob4e6c412508c8095b3d65d33504d048b1c3a048a2
1 package com.intellij.diagnostic.logging;
3 import com.intellij.execution.filters.TextConsoleBuilder;
4 import com.intellij.execution.filters.TextConsoleBuilderFactory;
5 import com.intellij.execution.process.ProcessAdapter;
6 import com.intellij.execution.process.ProcessEvent;
7 import com.intellij.execution.process.ProcessHandler;
8 import com.intellij.execution.ui.ConsoleView;
9 import com.intellij.execution.ui.ConsoleViewContentType;
10 import com.intellij.openapi.actionSystem.*;
11 import com.intellij.openapi.application.ApplicationManager;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Document;
14 import com.intellij.openapi.editor.Editor;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.util.Comparing;
17 import com.intellij.openapi.util.Condition;
18 import com.intellij.openapi.util.Disposer;
19 import com.intellij.openapi.util.Key;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.ui.FilterComponent;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
25 import javax.swing.*;
26 import javax.swing.event.ChangeEvent;
27 import java.awt.*;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.awt.event.KeyEvent;
31 import java.io.*;
32 import java.util.List;
34 /**
35 * Created by IntelliJ IDEA.
36 * User: Eugene.Kudelevsky
37 * Date: Sep 11, 2009
38 * Time: 9:26:11 PM
39 * To change this template use File | Settings | File Templates.
41 public abstract class LogConsoleBase extends AdditionalTabComponent implements LogConsole, LogFilterListener {
42 private static final Logger LOG = Logger.getInstance("com.intellij.diagnostic.logging.LogConsoleImpl");
44 private ConsoleView myConsole;
45 private final LightProcessHandler myProcessHandler = new LightProcessHandler();
46 private ReaderThread myReaderThread;
47 private StringBuffer myOriginalDocument = null;
48 private String myLineUnderSelection = null;
49 private int myLineOffset = -1;
50 private LogContentPreprocessor myContentPreprocessor;
51 private String myTitle = null;
52 private boolean myWasInitialized;
53 private final JPanel myTopComponent = new JPanel(new BorderLayout());
54 private ActionGroup myActions;
55 private final boolean myBuildInActions;
56 private LogFilterModel myModel;
58 private FilterComponent myFilter = new FilterComponent("LOG_FILTER_HISTORY", 5) {
59 public void filter() {
60 myModel.updateCustomFilter(getFilter());
63 private JPanel mySearchComponent;
64 private JComboBox myLogFilterCombo;
65 private JPanel myTextFilterWrapper;
67 public LogConsoleBase(Project project, @Nullable Reader reader, String title, final boolean buildInActions, LogFilterModel model) {
68 super(new BorderLayout());
69 myTitle = title;
70 myModel = model;
71 myReaderThread = new ReaderThread(reader);
72 myBuildInActions = buildInActions;
73 TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
74 myConsole = builder.getConsole();
75 myConsole.attachToProcess(myProcessHandler);
76 myModel.addFilterListener(this);
79 @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
80 public LogConsoleBase(Project project, File file, long skippedContents, String title, boolean buildInActions, LogFilterModel model) {
81 this(project, getReader(file, skippedContents), title, buildInActions, model);
84 public LogConsoleBase(Project project, Reader reader, long skippedContents, String title, boolean buildInActions, LogFilterModel model) {
85 this(project, getReader(reader, skippedContents), title, buildInActions, model);
88 @Nullable
89 private static Reader getReader(Reader reader, long skippedContents) {
90 reader = new BufferedReader(reader);
91 try {
92 reader.skip(skippedContents);
94 catch (IOException e) {
95 reader = null;
97 return reader;
100 @Nullable
101 private static Reader getReader(File file, long skippedContents) {
102 Reader reader = null;
103 try {
104 try {
105 final FileInputStream inputStream = new FileInputStream(file);
106 reader = new BufferedReader(new InputStreamReader(inputStream));
107 if (file.length() >= skippedContents) { //do not skip forward
108 inputStream.skip(skippedContents);
111 catch (FileNotFoundException e) {
112 if (FileUtil.createIfDoesntExist(file)) {
113 reader = new BufferedReader(new FileReader(file));
117 catch (Throwable e) {
118 reader = null;
120 return reader;
123 public void setFilterModel(LogFilterModel model) {
124 if (myModel != null) {
125 myModel.removeFilterListener(this);
127 myModel = model;
128 myModel.addFilterListener(this);
131 public LogFilterModel getFilterModel() {
132 return myModel;
135 public LogContentPreprocessor getContentPreprocessor() {
136 return myContentPreprocessor;
139 public void setContentPreprocessor(final LogContentPreprocessor contentPreprocessor) {
140 myContentPreprocessor = contentPreprocessor;
143 @SuppressWarnings({"NonStaticInitializer"})
144 private JComponent createToolbar() {
145 String customFilter = myModel.getCustomFilter();
147 myFilter.reset();
148 myFilter.setSelectedItem(customFilter != null ? customFilter : "");
149 new AnAction() {
151 registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK)),
152 LogConsoleBase.this);
155 public void actionPerformed(final AnActionEvent e) {
156 myFilter.requestFocusInWindow();
160 if (myBuildInActions) {
161 final JComponent tbComp =
162 ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, getOrCreateActions(), true).getComponent();
163 myTopComponent.add(tbComp, BorderLayout.CENTER);
164 myTopComponent.add(getSearchComponent(), BorderLayout.EAST);
168 return myTopComponent;
171 public ActionGroup getOrCreateActions() {
172 if (myActions != null) return myActions;
173 DefaultActionGroup group = new DefaultActionGroup();
175 final AnAction[] actions = myConsole.createConsoleActions();
176 for (AnAction action : actions) {
177 group.add(action);
180 group.addSeparator();
182 /*for (final LogFilter filter : filters) {
183 group.add(new ToggleAction(filter.getName(), filter.getName(), filter.getIcon()) {
184 public boolean isSelected(AnActionEvent e) {
185 return prefs.isFilterSelected(filter);
188 public void setSelected(AnActionEvent e, boolean state) {
189 prefs.setFilterSelected(filter, state);
194 myActions = group;
196 return myActions;
199 public void onFilterStateChange(final LogFilter filter) {
200 filterConsoleOutput(new Condition<String>() {
201 public boolean value(final String line) {
202 return myModel.isApplicable(line);
207 public void onTextFilterChange() {
208 filterConsoleOutput(new Condition<String>() {
209 public boolean value(final String line) {
210 return myModel.isApplicable(line);
215 @NotNull
216 public JComponent getComponent() {
217 if (!myWasInitialized) {
218 myWasInitialized = true;
219 add(myConsole.getComponent(), BorderLayout.CENTER);
220 add(createToolbar(), BorderLayout.NORTH);
222 return this;
225 public abstract boolean isActive();
227 public void activate() {
228 if (myReaderThread == null) return;
229 if (isActive() && !myReaderThread.myRunning) {
230 myFilter.setSelectedItem(myModel.getCustomFilter());
231 myReaderThread.startRunning();
232 ApplicationManager.getApplication().executeOnPooledThread(myReaderThread);
234 else if (!isActive() && myReaderThread.myRunning) {
235 myReaderThread.stopRunning();
239 public void stateChanged(final ChangeEvent e) {
240 activate();
243 public String getTabTitle() {
244 return myTitle;
247 public void dispose() {
248 myModel.removeFilterListener(this);
249 if (myReaderThread != null && myReaderThread.myReader != null) {
250 myReaderThread.stopRunning();
251 try {
252 myReaderThread.myReader.close();
254 catch (IOException e) {
255 LOG.warn(e);
257 myReaderThread.myReader = null;
258 myReaderThread = null;
260 if (myConsole != null) {
261 Disposer.dispose(myConsole);
262 myConsole = null;
264 if (myFilter != null) {
265 myFilter.dispose();
266 myFilter = null;
268 myOriginalDocument = null;
271 private void stopRunning() {
272 if (myReaderThread != null && !isActive()) {
273 myReaderThread.stopRunning();
277 protected synchronized void addMessage(final String text) {
278 if (text == null) return;
279 if (myContentPreprocessor != null) {
280 final java.util.List<LogFragment> fragments = myContentPreprocessor.parseLogLine(text + "\n");
281 myOriginalDocument = getOriginalDocument();
282 for (LogFragment fragment : fragments) {
283 myProcessHandler.notifyTextAvailable(fragment.getText(), fragment.getOutputType());
284 if (myOriginalDocument != null) {
285 myOriginalDocument.append(fragment.getText());
289 else {
290 if (myModel.isApplicable(text)) {
291 myProcessHandler.notifyTextAvailable(text + "\n", myModel.processLine(text));
293 myOriginalDocument = getOriginalDocument();
294 if (myOriginalDocument != null) {
295 myOriginalDocument.append(text).append("\n");
300 public void attachStopLogConsoleTrackingListener(final ProcessHandler process) {
301 if (process != null) {
302 final ProcessAdapter stopListener = new ProcessAdapter() {
303 public void processTerminated(final ProcessEvent event) {
304 process.removeProcessListener(this);
305 stopRunning();
308 process.addProcessListener(stopListener);
312 private StringBuffer getOriginalDocument() {
313 if (myOriginalDocument == null) {
314 final Editor editor = getEditor();
315 if (editor != null) {
316 myOriginalDocument = new StringBuffer(editor.getDocument().getText());
319 return myOriginalDocument;
322 @Nullable
323 private Editor getEditor() {
324 return myConsole != null ? (Editor)((DataProvider)myConsole).getData(DataConstants.EDITOR) : null;
327 private synchronized void filterConsoleOutput(Condition<String> isApplicable) {
328 myOriginalDocument = getOriginalDocument();
329 if (myOriginalDocument != null) {
330 final Editor editor = getEditor();
331 LOG.assertTrue(editor != null);
332 final Document document = editor.getDocument();
333 final int caretOffset = editor.getCaretModel().getOffset();
334 if (caretOffset > -1) {
335 int line = document.getLineNumber(caretOffset);
336 if (line > -1 && line < document.getLineCount()) {
337 final int startOffset = document.getLineStartOffset(line);
338 myLineUnderSelection = document.getText().substring(startOffset, document.getLineEndOffset(line));
339 myLineOffset = caretOffset - startOffset;
342 myConsole.clear();
343 final String[] lines = myOriginalDocument.toString().split("\n");
344 int offset = 0;
345 boolean caretPositioned = false;
346 for (String line : lines) {
347 if (printMessageToConsole(line, isApplicable)) {
348 if (!caretPositioned) {
349 if (Comparing.strEqual(myLineUnderSelection, line)) {
350 caretPositioned = true;
351 offset += myLineOffset != -1 ? myLineOffset : 0;
353 else {
354 offset += line.length() + 1;
359 myConsole.scrollTo(offset);
363 private boolean printMessageToConsole(String line, Condition<String> isApplicable) {
364 if (myContentPreprocessor != null) {
365 List<LogFragment> fragments = myContentPreprocessor.parseLogLine(line + '\n');
366 for (LogFragment fragment : fragments) {
367 ConsoleViewContentType consoleViewType = ConsoleViewContentType.getConsoleViewType(fragment.getOutputType());
368 if (consoleViewType != null) {
369 myConsole.print(fragment.getText(), consoleViewType);
373 else if (isApplicable.value(line)) {
374 Key key = myModel.processLine(line);
375 myConsole.print(line + "\n", ConsoleViewContentType.getConsoleViewType(key));
377 return true;
380 public ActionGroup getToolbarActions() {
381 return getOrCreateActions();
384 public String getToolbarPlace() {
385 return ActionPlaces.UNKNOWN;
388 public JComponent getToolbarContextComponent() {
389 return myConsole.getComponent();
392 public JComponent getPreferredFocusableComponent() {
393 return myConsole.getPreferredFocusableComponent();
396 public String getTitle() {
397 return myTitle;
400 public synchronized void clear() {
401 myConsole.clear();
402 myOriginalDocument = null;
405 public JComponent getSearchComponent() {
406 List<? extends LogFilter> filters = myModel.getLogFilters();
407 myLogFilterCombo.setModel(new DefaultComboBoxModel(filters.toArray(new LogFilter[filters.size()])));
408 for (LogFilter filter : filters) {
409 if (myModel.isFilterSelected(filter)) {
410 myLogFilterCombo.setSelectedItem(filter);
411 break;
414 myLogFilterCombo.addActionListener(new ActionListener() {
415 public void actionPerformed(ActionEvent e) {
416 final LogFilter filter = (LogFilter)myLogFilterCombo.getSelectedItem();
417 myModel.selectFilter(filter);
420 myTextFilterWrapper.removeAll();
421 myTextFilterWrapper.add(myFilter);
422 return mySearchComponent;
425 public boolean isContentBuiltIn() {
426 return myBuildInActions;
429 private static class LightProcessHandler extends ProcessHandler {
430 protected void destroyProcessImpl() {
431 throw new UnsupportedOperationException();
434 protected void detachProcessImpl() {
435 throw new UnsupportedOperationException();
438 public boolean detachIsDefault() {
439 return false;
442 @Nullable
443 public OutputStream getProcessInput() {
444 return null;
448 protected class ReaderThread implements Runnable {
449 private BufferedReader myReader;
450 private boolean myRunning = false;
452 public ReaderThread(@Nullable Reader reader) {
453 myReader = reader != null ? new BufferedReader(reader) : null;
456 public void run() {
457 if (myReader == null) return;
458 while (myRunning) {
459 try {
460 int i = 0;
461 while (i++ < 100) {
462 if (myRunning && myReader != null && myReader.ready()) {
463 addMessage(myReader.readLine());
465 else {
466 break;
469 synchronized (this) {
470 wait(100);
473 catch (IOException e) {
474 LOG.error(e);
476 catch (InterruptedException e) {
477 Disposer.dispose(LogConsoleBase.this);
482 public void startRunning() {
483 myRunning = true;
486 public void stopRunning() {
487 myRunning = false;
488 synchronized (this) {
489 notifyAll();