Chris@13
|
1 <?php
|
Chris@13
|
2
|
Chris@13
|
3 /*
|
Chris@13
|
4 * This file is part of Psy Shell.
|
Chris@13
|
5 *
|
Chris@13
|
6 * (c) 2012-2018 Justin Hileman
|
Chris@13
|
7 *
|
Chris@13
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@13
|
9 * file that was distributed with this source code.
|
Chris@13
|
10 */
|
Chris@13
|
11
|
Chris@13
|
12 namespace Psy\Readline;
|
Chris@13
|
13
|
Chris@13
|
14 /**
|
Chris@13
|
15 * A Readline interface implementation for GNU Readline.
|
Chris@13
|
16 *
|
Chris@13
|
17 * This is by far the coolest way to do it, but it doesn't work with new PHP.
|
Chris@13
|
18 *
|
Chris@13
|
19 * Oh well.
|
Chris@13
|
20 */
|
Chris@13
|
21 class GNUReadline implements Readline
|
Chris@13
|
22 {
|
Chris@13
|
23 /** @var string|false */
|
Chris@13
|
24 protected $historyFile;
|
Chris@13
|
25 /** @var int */
|
Chris@13
|
26 protected $historySize;
|
Chris@13
|
27 /** @var bool */
|
Chris@13
|
28 protected $eraseDups;
|
Chris@13
|
29
|
Chris@13
|
30 /**
|
Chris@13
|
31 * GNU Readline is supported iff `readline_list_history` is defined. PHP
|
Chris@13
|
32 * decided it would be awesome to swap out GNU Readline for Libedit, but
|
Chris@13
|
33 * they ended up shipping an incomplete implementation. So we've got this.
|
Chris@13
|
34 *
|
Chris@13
|
35 * @return bool
|
Chris@13
|
36 */
|
Chris@13
|
37 public static function isSupported()
|
Chris@13
|
38 {
|
Chris@17
|
39 return \function_exists('readline_list_history');
|
Chris@13
|
40 }
|
Chris@13
|
41
|
Chris@13
|
42 /**
|
Chris@13
|
43 * GNU Readline constructor.
|
Chris@13
|
44 *
|
Chris@13
|
45 * @param string|false $historyFile
|
Chris@13
|
46 * @param int $historySize
|
Chris@13
|
47 * @param bool $eraseDups
|
Chris@13
|
48 */
|
Chris@13
|
49 public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
|
Chris@13
|
50 {
|
Chris@13
|
51 $this->historyFile = ($historyFile !== null) ? $historyFile : false;
|
Chris@13
|
52 $this->historySize = $historySize;
|
Chris@13
|
53 $this->eraseDups = $eraseDups;
|
Chris@13
|
54 }
|
Chris@13
|
55
|
Chris@13
|
56 /**
|
Chris@13
|
57 * {@inheritdoc}
|
Chris@13
|
58 */
|
Chris@13
|
59 public function addHistory($line)
|
Chris@13
|
60 {
|
Chris@17
|
61 if ($res = \readline_add_history($line)) {
|
Chris@13
|
62 $this->writeHistory();
|
Chris@13
|
63 }
|
Chris@13
|
64
|
Chris@13
|
65 return $res;
|
Chris@13
|
66 }
|
Chris@13
|
67
|
Chris@13
|
68 /**
|
Chris@13
|
69 * {@inheritdoc}
|
Chris@13
|
70 */
|
Chris@13
|
71 public function clearHistory()
|
Chris@13
|
72 {
|
Chris@17
|
73 if ($res = \readline_clear_history()) {
|
Chris@13
|
74 $this->writeHistory();
|
Chris@13
|
75 }
|
Chris@13
|
76
|
Chris@13
|
77 return $res;
|
Chris@13
|
78 }
|
Chris@13
|
79
|
Chris@13
|
80 /**
|
Chris@13
|
81 * {@inheritdoc}
|
Chris@13
|
82 */
|
Chris@13
|
83 public function listHistory()
|
Chris@13
|
84 {
|
Chris@13
|
85 return readline_list_history();
|
Chris@13
|
86 }
|
Chris@13
|
87
|
Chris@13
|
88 /**
|
Chris@13
|
89 * {@inheritdoc}
|
Chris@13
|
90 */
|
Chris@13
|
91 public function readHistory()
|
Chris@13
|
92 {
|
Chris@13
|
93 // Workaround PHP bug #69054
|
Chris@13
|
94 //
|
Chris@13
|
95 // If open_basedir is set, readline_read_history() segfaults. This was fixed in 5.6.7:
|
Chris@13
|
96 //
|
Chris@13
|
97 // https://github.com/php/php-src/blob/423a057023ef3c00d2ffc16a6b43ba01d0f71796/NEWS#L19-L21
|
Chris@13
|
98 //
|
Chris@17
|
99 if (\version_compare(PHP_VERSION, '5.6.7', '>=') || !\ini_get('open_basedir')) {
|
Chris@17
|
100 \readline_read_history();
|
Chris@13
|
101 }
|
Chris@17
|
102 \readline_clear_history();
|
Chris@13
|
103
|
Chris@17
|
104 return \readline_read_history($this->historyFile);
|
Chris@13
|
105 }
|
Chris@13
|
106
|
Chris@13
|
107 /**
|
Chris@13
|
108 * {@inheritdoc}
|
Chris@13
|
109 */
|
Chris@13
|
110 public function readline($prompt = null)
|
Chris@13
|
111 {
|
Chris@17
|
112 return \readline($prompt);
|
Chris@13
|
113 }
|
Chris@13
|
114
|
Chris@13
|
115 /**
|
Chris@13
|
116 * {@inheritdoc}
|
Chris@13
|
117 */
|
Chris@13
|
118 public function redisplay()
|
Chris@13
|
119 {
|
Chris@17
|
120 \readline_redisplay();
|
Chris@13
|
121 }
|
Chris@13
|
122
|
Chris@13
|
123 /**
|
Chris@13
|
124 * {@inheritdoc}
|
Chris@13
|
125 */
|
Chris@13
|
126 public function writeHistory()
|
Chris@13
|
127 {
|
Chris@13
|
128 // We have to write history first, since it is used
|
Chris@13
|
129 // by Libedit to list history
|
Chris@13
|
130 if ($this->historyFile !== false) {
|
Chris@17
|
131 $res = \readline_write_history($this->historyFile);
|
Chris@13
|
132 } else {
|
Chris@13
|
133 $res = true;
|
Chris@13
|
134 }
|
Chris@13
|
135
|
Chris@13
|
136 if (!$res || !$this->eraseDups && !$this->historySize > 0) {
|
Chris@13
|
137 return $res;
|
Chris@13
|
138 }
|
Chris@13
|
139
|
Chris@13
|
140 $hist = $this->listHistory();
|
Chris@13
|
141 if (!$hist) {
|
Chris@13
|
142 return true;
|
Chris@13
|
143 }
|
Chris@13
|
144
|
Chris@13
|
145 if ($this->eraseDups) {
|
Chris@13
|
146 // flip-flip technique: removes duplicates, latest entries win.
|
Chris@17
|
147 $hist = \array_flip(\array_flip($hist));
|
Chris@13
|
148 // sort on keys to get the order back
|
Chris@17
|
149 \ksort($hist);
|
Chris@13
|
150 }
|
Chris@13
|
151
|
Chris@13
|
152 if ($this->historySize > 0) {
|
Chris@17
|
153 $histsize = \count($hist);
|
Chris@13
|
154 if ($histsize > $this->historySize) {
|
Chris@17
|
155 $hist = \array_slice($hist, $histsize - $this->historySize);
|
Chris@13
|
156 }
|
Chris@13
|
157 }
|
Chris@13
|
158
|
Chris@17
|
159 \readline_clear_history();
|
Chris@13
|
160 foreach ($hist as $line) {
|
Chris@17
|
161 \readline_add_history($line);
|
Chris@13
|
162 }
|
Chris@13
|
163
|
Chris@13
|
164 if ($this->historyFile !== false) {
|
Chris@17
|
165 return \readline_write_history($this->historyFile);
|
Chris@13
|
166 }
|
Chris@13
|
167
|
Chris@13
|
168 return true;
|
Chris@13
|
169 }
|
Chris@13
|
170 }
|