001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019package org.crsh.console;
020
021import jline.console.Operation;
022import org.crsh.shell.Shell;
023import org.crsh.shell.ShellProcess;
024import org.crsh.util.Utils;
025
026import java.io.IOException;
027import java.util.concurrent.BlockingDeque;
028import java.util.concurrent.LinkedBlockingDeque;
029import java.util.concurrent.atomic.AtomicReference;
030
031/**
032 * A console state machine, which delegates the state machine to the {@link Plugin} implementation.
033 *
034 * @author Julien Viet
035 */
036public class Console {
037
038  /** . */
039  final Shell shell;
040
041  /** The current handler. */
042  final AtomicReference<Plugin> handler;
043
044  /** The buffer. */
045  final BlockingDeque<KeyStroke> buffer;
046
047  /** . */
048  final ConsoleDriver driver;
049
050  /** . */
051  final Editor editor;
052
053  /** . */
054  boolean running;
055
056  public Console(Shell shell, ConsoleDriver driver) throws NullPointerException {
057    if (shell == null) {
058      throw new NullPointerException("No null shell accepted");
059    }
060    this.driver = driver;
061    this.shell = shell;
062    this.buffer = new LinkedBlockingDeque<KeyStroke>(1024);
063    this.handler = new AtomicReference<Plugin>();
064    this.editor = new Editor(this);
065    this.running = true;
066  }
067
068  public void setMode(Mode mode) {
069    editor.setMode(mode);
070  }
071
072  public void toEmacs() {
073    setMode(Mode.EMACS);
074  }
075
076  public void toMove() {
077    setMode(Mode.VI_MOVE);
078  }
079
080  public void toInsert() {
081    setMode(Mode.VI_INSERT);
082  }
083
084  public Mode getMode() {
085    return editor.getMode();
086  }
087
088  public void addModeListener(Runnable runnable) {
089    editor.addModeListener(runnable);
090  }
091
092  public boolean isRunning() {
093    return running;
094  }
095
096  /**
097   * Initiali
098   */
099  public void init() {
100    // Take care of pormpt
101    String welcome = shell.getWelcome();
102    if (welcome != null && welcome.length() > 0) {
103      try {
104        driver.write(welcome);
105        driver.flush();
106      }
107      catch (IOException e) {
108        // Log it
109      }
110    }
111    edit();
112  }
113
114  public Iterable<KeyStroke> getKeyBuffer() {
115    return buffer;
116  }
117
118  public void on(Operation operation, int... buffer) {
119    on(new KeyStroke(operation, buffer));
120  }
121
122  public void on(KeyStroke keyStroke) {
123    if (keyStroke.operation == Operation.INTERRUPT) {
124      Plugin current = handler.get();
125      if (current == null) {
126        throw new IllegalStateException("Not initialized");
127      } else if (current instanceof ProcessHandler) {
128        ProcessHandler processHandler = (ProcessHandler)current;
129        ProcessHandler.Reader reader = processHandler.editor.get();
130        if (reader != null) {
131          reader.thread.interrupt();
132        }
133        processHandler.process.cancel();
134        return;
135      }
136    }
137    buffer.add(keyStroke);
138    iterate();
139  }
140
141  public void on(KeyStroke[] keyStrokes) {
142    for (KeyStroke keyStroke : keyStrokes) {
143      on(keyStroke);
144    }
145  }
146
147
148  void close() {
149    running = false;
150    Utils.close(driver);
151  }
152
153  /**
154   * Switch to edit.
155   */
156  Editor edit() {
157    String prompt = shell.getPrompt();
158    if (prompt != null && prompt.length() > 0) {
159      try {
160        driver.write(prompt);
161        driver.flush();
162      }
163      catch (IOException e) {
164        // Swallow for now...
165      }
166    }
167    editor.reset();
168    handler.set(editor);
169    return editor;
170  }
171
172  /**
173   * Process the state machine.
174   */
175  void iterate() {
176    while (running) {
177      Plugin current = handler.get();
178      KeyStroke key = buffer.poll();
179      if (key != null) {
180        if (current == null) {
181          throw new IllegalStateException("Not initialized");
182        } else if (current instanceof Editor) {
183          Editor editor = (Editor)current;
184          EditorAction action = editor.getMode().on(key);
185          if (action != null) {
186            String line = editor.append(action, key.sequence);
187            if (line != null) {
188              ShellProcess process = shell.createProcess(line);
189              ProcessHandler context = new ProcessHandler(this, process);
190              handler.set(context);
191              process.execute(context);
192            }
193          }
194        } else if (current instanceof ProcessHandler) {
195          ProcessHandler processHandler = (ProcessHandler)current;
196          ProcessHandler.Reader reader = processHandler.editor.get();
197          if (reader != null) {
198            EditorAction action = editor.getMode().on(key);
199            if (action != null) {
200              String s = reader.editor.append(action, key.sequence);
201              if (s != null) {
202                reader.line.add(s);
203              }
204            }
205          } else {
206            KeyHandler keyHandler = processHandler.process.getKeyHandler();
207            if (keyHandler != null) {
208              KeyType type = KeyType.map(key.operation, key.sequence);
209              keyHandler.handle(type, key.sequence);
210            } else {
211              buffer.addFirst(key);
212            }
213            return;
214          }
215        } else {
216          throw new UnsupportedOperationException();
217        }
218      } else {
219        return;
220      }
221    }
222  }
223}