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     */
019    package org.crsh.console;
020    
021    import jline.console.Operation;
022    import org.crsh.shell.Shell;
023    import org.crsh.shell.ShellProcess;
024    import org.crsh.util.Utils;
025    
026    import java.io.IOException;
027    import java.util.concurrent.BlockingDeque;
028    import java.util.concurrent.LinkedBlockingDeque;
029    import 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     */
036    public 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    }