GNUReadline.php 3.57 KB
Newer Older
Juliper committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2015 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Readline;

/**
 * A Readline interface implementation for GNU Readline.
 *
 * This is by far the coolest way to do it, but it doesn't work with new PHP.
 *
 * Oh well.
 */
class GNUReadline implements Readline
{
    protected $historyFile;
    protected $historySize;
    protected $eraseDups;

    /**
     * GNU Readline is supported iff `readline_list_history` is defined. PHP
     * decided it would be awesome to swap out GNU Readline for Libedit, but
     * they ended up shipping an incomplete implementation. So we've got this.
     *
     * @return bool
     */
    public static function isSupported()
    {
        return function_exists('readline_list_history');
    }

    /**
     * GNU Readline constructor.
     */
    public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
    {
        $this->historyFile = $historyFile;
        $this->historySize = $historySize;
        $this->eraseDups = $eraseDups;
    }

    /**
     * {@inheritdoc}
     */
    public function addHistory($line)
    {
        if ($res = readline_add_history($line)) {
            $this->writeHistory();
        }

        return $res;
    }

    /**
     * {@inheritdoc}
     */
    public function clearHistory()
    {
        if ($res = readline_clear_history()) {
            $this->writeHistory();
        }

        return $res;
    }

    /**
     * {@inheritdoc}
     */
    public function listHistory()
    {
        return readline_list_history();
    }

    /**
     * {@inheritdoc}
     */
    public function readHistory()
    {
        // Workaround PHP bug #69054
        //
        // If open_basedir is set, readline_read_history() segfaults. This will be fixed in 5.6.7:
        //
        //     https://github.com/php/php-src/blob/423a057023ef3c00d2ffc16a6b43ba01d0f71796/NEWS#L19-L21
        //
        // TODO: add a PHP version check after next point release
        if (!ini_get('open_basedir')) {
            readline_read_history();
        }
        readline_clear_history();

        return readline_read_history($this->historyFile);
    }

    /**
     * {@inheritdoc}
     */
    public function readline($prompt = null)
    {
        return readline($prompt);
    }

    /**
     * {@inheritdoc}
     */
    public function redisplay()
    {
        readline_redisplay();
    }

    /**
     * {@inheritdoc}
     */
    public function writeHistory()
    {
        // We have to write history first, since it is used
        // by Libedit to list history
        $res = readline_write_history($this->historyFile);
        if (!$res || !$this->eraseDups && !$this->historySize > 0) {
            return $res;
        }

        $hist = $this->listHistory();
        if (!$hist) {
            return true;
        }

        if ($this->eraseDups) {
            // flip-flip technique: removes duplicates, latest entries win.
            $hist = array_flip(array_flip($hist));
            // sort on keys to get the order back
            ksort($hist);
        }

        if ($this->historySize > 0) {
            $histsize = count($hist);
            if ($histsize > $this->historySize) {
                $hist = array_slice($hist, $histsize - $this->historySize);
            }
        }

        readline_clear_history();
        foreach ($hist as $line) {
            readline_add_history($line);
        }

        return readline_write_history($this->historyFile);
    }
}