Passed
Push — 0.8.x ( 82ebd0...4d6b49 )
by Alexander
06:51 queued 03:22
created
src/components/Debug/Exceptions/Handlers/PleasingPageHandler.php 1 patch
Indentation   +416 added lines, -416 removed lines patch added patch discarded remove patch
@@ -39,439 +39,439 @@
 block discarded – undo
39 39
  */
40 40
 class PleasingPageHandler extends MainHandler
41 41
 {
42
-	/**
43
-	 * The brand main of handler.
44
-	 * 
45
-	 * @var string $brand
46
-	 */
47
-	protected $brand = 'Lenevor Debug';
48
-
49
-	/**
50
-	 * A string identifier for a known IDE/text editor, or a closure
51
-	 * that resolves a string that can be used to open a given file
52
-	 * in an editor.
53
-	 * 
54
-	 * @var mixed $editor
55
-	 */
56
-	protected $editor;
57
-
58
-	/**
59
-	 * A list of known editor strings.
60
-	 * 
61
-	 * @var array $editors
62
-	 */
63
-	protected $editors = [
64
-		"vscode"   => "vscode://file/%file:%line",
65
-		"sublime"  => "subl://open?url=file://%file&line=%line",
66
-		"phpstorm" => "phpstorm://open?file://%file&line=%line",
67
-		"textmate" => "txmt://open?url=file://%file&line=%line",
68
-		"atom"     => "atom://core/open/file?filename=%file&line=%line",
69
-	];
42
+    /**
43
+     * The brand main of handler.
44
+     * 
45
+     * @var string $brand
46
+     */
47
+    protected $brand = 'Lenevor Debug';
48
+
49
+    /**
50
+     * A string identifier for a known IDE/text editor, or a closure
51
+     * that resolves a string that can be used to open a given file
52
+     * in an editor.
53
+     * 
54
+     * @var mixed $editor
55
+     */
56
+    protected $editor;
57
+
58
+    /**
59
+     * A list of known editor strings.
60
+     * 
61
+     * @var array $editors
62
+     */
63
+    protected $editors = [
64
+        "vscode"   => "vscode://file/%file:%line",
65
+        "sublime"  => "subl://open?url=file://%file&line=%line",
66
+        "phpstorm" => "phpstorm://open?file://%file&line=%line",
67
+        "textmate" => "txmt://open?url=file://%file&line=%line",
68
+        "atom"     => "atom://core/open/file?filename=%file&line=%line",
69
+    ];
70 70
 	
71
-	/**
72
-	 * The page title main of handler.
73
-	 * 
74
-	 * @var string $pageTitle
75
-	 */
76
-	protected $pageTitle = 'Lenevor Debug! There was an error';
71
+    /**
72
+     * The page title main of handler.
73
+     * 
74
+     * @var string $pageTitle
75
+     */
76
+    protected $pageTitle = 'Lenevor Debug! There was an error';
77 77
 	
78
-	/**
79
-	 * Fast lookup cache for known resource locations.
80
-	 * 
81
-	 * @var array $resourceCache
82
-	 */
83
-	protected $resourceCache = [];
78
+    /**
79
+     * Fast lookup cache for known resource locations.
80
+     * 
81
+     * @var array $resourceCache
82
+     */
83
+    protected $resourceCache = [];
84 84
 	
85
-	/**
86
-	 * The path to the directory containing the html error template directories.
87
-	 * 
88
-	 * @var array $searchPaths
89
-	 */
90
-	protected $searchPaths = [];
91
-
92
-	/**
93
-	 * Gets the table of data.
94
-	 * 
95
-	 * @var array $tables
96
-	 */
97
-	protected $tables = [];
85
+    /**
86
+     * The path to the directory containing the html error template directories.
87
+     * 
88
+     * @var array $searchPaths
89
+     */
90
+    protected $searchPaths = [];
91
+
92
+    /**
93
+     * Gets the table of data.
94
+     * 
95
+     * @var array $tables
96
+     */
97
+    protected $tables = [];
98 98
 	
99
-	/**
100
-	 * The template handler system.
101
-	 * 
102
-	 * @var string|object $template
103
-	 */
104
-	protected $template;	
99
+    /**
100
+     * The template handler system.
101
+     * 
102
+     * @var string|object $template
103
+     */
104
+    protected $template;	
105 105
 	
106
-	/**
107
-	 * Constructor. The PleasingPageHandler class.
108
-	 * 
109
-	 * @return void
110
-	 */
111
-	public function __construct()
112
-	{
113
-		$this->template      = new TemplateHandler;
114
-		$this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
115
-	}
116
-
117
-	/**
118
-	 * Adds an editor resolver, identified by a string name, and that may be a 
119
-	 * string path, or a callable resolver.
120
-	 * 
121
-	 * @param  string            $identifier
122
-	 * @param  string|\Callable  $resolver
123
-	 * 
124
-	 * @return void
125
-	 */
126
-	public function addEditor($identifier, $resolver): void
127
-	{
128
-		$this->editors[$identifier] = $resolver;
129
-	}
130
-
131
-	/**
132
-	 * Adds an entry to the list of tables displayed in the template.
133
-	 * The expected data is a simple associative array. Any nested arrays
134
-	 * will be flattened with print_r.
135
-	 * 
136
-	 * @param  \Syscodes\Components\Contracts\Debug\Table  $table
137
-	 * 
138
-	 * @return void
139
-	 */
140
-	public function addTables(Table $table): void
141
-	{
142
-		$this->tables[] = $table;
143
-	}
106
+    /**
107
+     * Constructor. The PleasingPageHandler class.
108
+     * 
109
+     * @return void
110
+     */
111
+    public function __construct()
112
+    {
113
+        $this->template      = new TemplateHandler;
114
+        $this->searchPaths[] = dirname(__DIR__).DIRECTORY_SEPARATOR.'Resources';
115
+    }
116
+
117
+    /**
118
+     * Adds an editor resolver, identified by a string name, and that may be a 
119
+     * string path, or a callable resolver.
120
+     * 
121
+     * @param  string            $identifier
122
+     * @param  string|\Callable  $resolver
123
+     * 
124
+     * @return void
125
+     */
126
+    public function addEditor($identifier, $resolver): void
127
+    {
128
+        $this->editors[$identifier] = $resolver;
129
+    }
130
+
131
+    /**
132
+     * Adds an entry to the list of tables displayed in the template.
133
+     * The expected data is a simple associative array. Any nested arrays
134
+     * will be flattened with print_r.
135
+     * 
136
+     * @param  \Syscodes\Components\Contracts\Debug\Table  $table
137
+     * 
138
+     * @return void
139
+     */
140
+    public function addTables(Table $table): void
141
+    {
142
+        $this->tables[] = $table;
143
+    }
144 144
 	
145
-	/**
146
-	 * Gathers the variables that will be made available to the view.
147
-	 * 
148
-	 * @return  array
149
-	 */
150
-	protected function collectionVars(): array
151
-	{
152
-		$supervisor = $this->getSupervisor();
153
-		$style      = file_get_contents($this->getResource('css/debug.base.css'));
154
-		$jscript    = file_get_contents($this->getResource('js/debug.base.js'));
155
-		$tables     = array_merge($this->getDefaultTables(), $this->tables);
145
+    /**
146
+     * Gathers the variables that will be made available to the view.
147
+     * 
148
+     * @return  array
149
+     */
150
+    protected function collectionVars(): array
151
+    {
152
+        $supervisor = $this->getSupervisor();
153
+        $style      = file_get_contents($this->getResource('css/debug.base.css'));
154
+        $jscript    = file_get_contents($this->getResource('js/debug.base.js'));
155
+        $tables     = array_merge($this->getDefaultTables(), $this->tables);
156 156
 		
157
-		return [ 
158
-			'class' => explode('\\', $supervisor->getExceptionName()),
159
-			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
160
-			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
161
-			'header' => $this->getResource('views/partials/updown/header.php'),
162
-			'footer' => $this->getResource('views/partials/updown/footer.php'),
163
-			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
164
-			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
165
-			'section_frame' => $this->getResource('views/partials/section_frame.php'),
166
-			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
167
-			'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
168
-			'section_code' => $this->getResource('views/partials/section_code.php'),
169
-			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
170
-			'request_info' => $this->getResource('views/partials/request_info.php'),
171
-			'navigation' => $this->getResource('views/partials/details/navigation.php'),
172
-			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
173
-			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
174
-			'handler' => $this,
175
-			'handlers' => $this->getDebug()->getHandlers(),
176
-			'debug' => $this->getDebug(),
177
-			'code' => $this->getExceptionCode(),
178
-			'message' => $supervisor->getExceptionMessage(),
179
-			'frames' => $this->getExceptionFrames(),
180
-			'tables' => $this->getProcessTables($tables),
181
-		];
182
-	}
157
+        return [ 
158
+            'class' => explode('\\', $supervisor->getExceptionName()),
159
+            'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
160
+            'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
161
+            'header' => $this->getResource('views/partials/updown/header.php'),
162
+            'footer' => $this->getResource('views/partials/updown/footer.php'),
163
+            'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
164
+            'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
165
+            'section_frame' => $this->getResource('views/partials/section_frame.php'),
166
+            'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
167
+            'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
168
+            'section_code' => $this->getResource('views/partials/section_code.php'),
169
+            'code_source' => $this->getResource('views/partials/codes/code_source.php'),
170
+            'request_info' => $this->getResource('views/partials/request_info.php'),
171
+            'navigation' => $this->getResource('views/partials/details/navigation.php'),
172
+            'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
173
+            'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
174
+            'handler' => $this,
175
+            'handlers' => $this->getDebug()->getHandlers(),
176
+            'debug' => $this->getDebug(),
177
+            'code' => $this->getExceptionCode(),
178
+            'message' => $supervisor->getExceptionMessage(),
179
+            'frames' => $this->getExceptionFrames(),
180
+            'tables' => $this->getProcessTables($tables),
181
+        ];
182
+    }
183 183
 	
184
-	/**
185
-	 * The way in which the data sender (usually the server) can tell the recipient
186
-	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
187
-	 * 
188
-	 * @return string
189
-	 */
190
-	public function contentType(): string
191
-	{
192
-		return 'text/html;charset=UTF-8';
193
-	}
194
-
195
-	/**
196
-	 * Gets the brand of project.
197
-	 * 
198
-	 * @return string
199
-	 */
200
-	public function getBrand(): string
201
-	{
202
-		return $this->brand;
203
-	}
204
-
205
-	/**
206
-	 * Returns the default tables.
207
-	 * 
208
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
209
-	 */
210
-	protected function getDefaultTables()
211
-	{
212
-		$server = [
213
-			'host' => $_SERVER['HTTP_HOST'], 
214
-			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
215
-			'accept' => $_SERVER['HTTP_ACCEPT'], 
216
-			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
217
-			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
218
-		    'connection' => $_SERVER['HTTP_CONNECTION'],
219
-			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
220
-			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
221
-			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
222
-			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
223
-			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
224
-		];
225
-
226
-		return [new ArrayTable($server)];
227
-	}
228
-
229
-	/**
230
-	 * Get the code of the exception that is currently being handled.
231
-	 * 
232
-	 * @return string
233
-	 */
234
-	protected function getExceptionCode()
235
-	{
236
-		$exception = $this->getException();
237
-		$code      = $exception->getCode();
238
-
239
-		if ($exception instanceof ErrorException) {
240
-			$code = Misc::translateErrorCode($exception->getSeverity());
241
-		}
242
-
243
-		return (string) $code;
244
-	}
245
-
246
-	/**
247
-	 * Get the stack trace frames of the exception that is currently being handled.
248
-	 * 
249
-	 * @return \Syscodes\Components\Debug\Engine\Supervisor;
250
-	 */
251
-	protected function getExceptionFrames()
252
-	{
253
-		$frames = $this->getSupervisor()->getFrames();
184
+    /**
185
+     * The way in which the data sender (usually the server) can tell the recipient
186
+     * (the browser, in general) what type of data is being sent in this case, html format tagged.
187
+     * 
188
+     * @return string
189
+     */
190
+    public function contentType(): string
191
+    {
192
+        return 'text/html;charset=UTF-8';
193
+    }
194
+
195
+    /**
196
+     * Gets the brand of project.
197
+     * 
198
+     * @return string
199
+     */
200
+    public function getBrand(): string
201
+    {
202
+        return $this->brand;
203
+    }
204
+
205
+    /**
206
+     * Returns the default tables.
207
+     * 
208
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
209
+     */
210
+    protected function getDefaultTables()
211
+    {
212
+        $server = [
213
+            'host' => $_SERVER['HTTP_HOST'], 
214
+            'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
215
+            'accept' => $_SERVER['HTTP_ACCEPT'], 
216
+            'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
217
+            'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
218
+            'connection' => $_SERVER['HTTP_CONNECTION'],
219
+            'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
220
+            'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
221
+            'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
222
+            'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
223
+            'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
224
+        ];
225
+
226
+        return [new ArrayTable($server)];
227
+    }
228
+
229
+    /**
230
+     * Get the code of the exception that is currently being handled.
231
+     * 
232
+     * @return string
233
+     */
234
+    protected function getExceptionCode()
235
+    {
236
+        $exception = $this->getException();
237
+        $code      = $exception->getCode();
238
+
239
+        if ($exception instanceof ErrorException) {
240
+            $code = Misc::translateErrorCode($exception->getSeverity());
241
+        }
242
+
243
+        return (string) $code;
244
+    }
245
+
246
+    /**
247
+     * Get the stack trace frames of the exception that is currently being handled.
248
+     * 
249
+     * @return \Syscodes\Components\Debug\Engine\Supervisor;
250
+     */
251
+    protected function getExceptionFrames()
252
+    {
253
+        $frames = $this->getSupervisor()->getFrames();
254 254
 		
255
-		return $frames;
256
-	}
255
+        return $frames;
256
+    }
257 257
 	
258
-	/**
259
-	 * Gets the page title web.
260
-	 * 
261
-	 * @return string
262
-	 */
263
-	public function getPageTitle(): string
264
-	{
265
-		return $this->pageTitle;
266
-	}
267
-
268
-	/**
269
-	 * Processes an array of tables making sure everything is allright.
270
-	 * 
271
-	 * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
272
-	 * 
273
-	 * @return array
274
-	 */
275
-	protected function getProcessTables(array $tables): array
276
-	{
277
-		$processTables = [];
278
-
279
-		foreach ($tables as $table) {
280
-			if ( ! $table instanceof Table) {
281
-				continue;
282
-			}
258
+    /**
259
+     * Gets the page title web.
260
+     * 
261
+     * @return string
262
+     */
263
+    public function getPageTitle(): string
264
+    {
265
+        return $this->pageTitle;
266
+    }
267
+
268
+    /**
269
+     * Processes an array of tables making sure everything is allright.
270
+     * 
271
+     * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
272
+     * 
273
+     * @return array
274
+     */
275
+    protected function getProcessTables(array $tables): array
276
+    {
277
+        $processTables = [];
278
+
279
+        foreach ($tables as $table) {
280
+            if ( ! $table instanceof Table) {
281
+                continue;
282
+            }
283 283
 			
284
-			$label = $table->getLabel();
285
-
286
-			try {
287
-				$data = (array) $table->getData();
288
-
289
-				if ( ! (is_array($data) || $data instanceof Traversable)) {
290
-					$data = [];
291
-				}
292
-			} catch (Exception $e) {
293
-				$data = [];
294
-			}
295
-
296
-			$processTables[$label] = $data;
297
-		}
298
-
299
-		return $processTables;
300
-	}
301
-
302
-	/**
303
-	 * Finds a resource, by its relative path, in all available search paths.
304
-	 *
305
-	 * @param  string  $resource
306
-	 * 
307
-	 * @return string
308
-	 * 
309
-	 * @throws \RuntimeException
310
-	 */
311
-	protected function getResource($resource)
312
-	{
313
-		if (isset($this->resourceCache[$resource])) {
314
-			return $this->resourceCache[$resource];
315
-		}
316
-
317
-		foreach ($this->searchPaths as $path) {
318
-			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
319
-
320
-			if (is_file($fullPath)) {
321
-				// Cache:
322
-				$this->resourceCache[$resource] = $fullPath;
323
-
324
-				return $fullPath;
325
-			}
326
-		}
327
-
328
-		throw new RuntimeException( 
329
-				"Could not find resource '{$resource}' in any resource paths.". 
330
-				"(searched: ".join(", ", $this->searchPaths).")");
331
-	}
284
+            $label = $table->getLabel();
285
+
286
+            try {
287
+                $data = (array) $table->getData();
288
+
289
+                if ( ! (is_array($data) || $data instanceof Traversable)) {
290
+                    $data = [];
291
+                }
292
+            } catch (Exception $e) {
293
+                $data = [];
294
+            }
295
+
296
+            $processTables[$label] = $data;
297
+        }
298
+
299
+        return $processTables;
300
+    }
301
+
302
+    /**
303
+     * Finds a resource, by its relative path, in all available search paths.
304
+     *
305
+     * @param  string  $resource
306
+     * 
307
+     * @return string
308
+     * 
309
+     * @throws \RuntimeException
310
+     */
311
+    protected function getResource($resource)
312
+    {
313
+        if (isset($this->resourceCache[$resource])) {
314
+            return $this->resourceCache[$resource];
315
+        }
316
+
317
+        foreach ($this->searchPaths as $path) {
318
+            $fullPath = $path.DIRECTORY_SEPARATOR.$resource;
319
+
320
+            if (is_file($fullPath)) {
321
+                // Cache:
322
+                $this->resourceCache[$resource] = $fullPath;
323
+
324
+                return $fullPath;
325
+            }
326
+        }
327
+
328
+        throw new RuntimeException( 
329
+                "Could not find resource '{$resource}' in any resource paths.". 
330
+                "(searched: ".join(", ", $this->searchPaths).")");
331
+    }
332 332
 	
333
-	/**
334
-	 * Given an exception and status code will display the error to the client.
335
-	 * 
336
-	 * @return int|null
337
-	 */
338
-	public function handle()
339
-	{	
340
-		$templatePath = $this->getResource('views/debug.layout.php');
341
-
342
-		$vars = $this->collectionVars();
333
+    /**
334
+     * Given an exception and status code will display the error to the client.
335
+     * 
336
+     * @return int|null
337
+     */
338
+    public function handle()
339
+    {	
340
+        $templatePath = $this->getResource('views/debug.layout.php');
341
+
342
+        $vars = $this->collectionVars();
343 343
 		
344
-		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
344
+        if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
345 345
 		
346
-		$this->template->setVariables($vars);
347
-		$this->template->render($templatePath);
346
+        $this->template->setVariables($vars);
347
+        $this->template->render($templatePath);
348 348
 		
349
-		return MainHandler::QUIT;
350
-	}
351
-
352
-	/**
353
-	 * Set the editor to use to open referenced files, by a string identifier or callable
354
-	 * that will be executed for every file reference. Should return a string.
355
-	 * 
356
-	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
357
-	 * @example  $debug->setEditor('vscode');
358
-	 * 
359
-	 * @param  string  $editor
360
-	 * 
361
-	 * @return void
362
-	 * 
363
-	 * @throws \InvalidArgumentException
364
-	 */
365
-	public function setEditor($editor)
366
-	{
367
-		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
368
-			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
369
-				implode(', ', array_keys($this->editors))
370
-			);
371
-		}
372
-
373
-		$this->editor = $editor;
374
-	}
375
-
376
-	/**
377
-	 * Given a string file path, and an integer file line,
378
-	 * executes the editor resolver and returns.
379
-	 * 
380
-	 * @param  string  $file
381
-	 * @param  int	   $line
382
-	 * 
383
-	 * @return string|bool
384
-	 * 
385
-	 * @throws \UnexpectedValueException
386
-	 */
387
-	public function getEditorAtHref($file, $line)
388
-	{
389
-		$editor = $this->getEditor($file, $line);
390
-
391
-		if (empty($editor))	{
392
-			return false;
393
-		}
394
-
395
-		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
396
-			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
397
-		}
398
-
399
-		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
400
-		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
401
-
402
-		return $editor['url'];
403
-	}
404
-
405
-	/**
406
-	 * The editor must be a valid callable function/closure.
407
-	 * 
408
-	 * @param  string  $file
409
-	 * @param  int	   $line
410
-	 * 
411
-	 * @return array
412
-	 */
413
-	protected function getEditor($file, $line): array
414
-	{
415
-		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
416
-			return [];
417
-		}
418
-
419
-		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
420
-			return ['url' => $this->editors[$this->editor]];
421
-		}
422
-
423
-		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
424
-			if (is_callable($this->editor)) {
425
-				$callback = call_user_func($this->editor, $file, $line);
426
-			} else {
427
-				$callback = call_user_func($this->editors[$this->editor], $file, $line);
428
-			}
429
-
430
-			if (empty($callback)) {
431
-				return [];
432
-			}
433
-
434
-			if (is_string($callback)) {
435
-				return ['url' => $callback];
436
-			}
349
+        return MainHandler::QUIT;
350
+    }
351
+
352
+    /**
353
+     * Set the editor to use to open referenced files, by a string identifier or callable
354
+     * that will be executed for every file reference. Should return a string.
355
+     * 
356
+     * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
357
+     * @example  $debug->setEditor('vscode');
358
+     * 
359
+     * @param  string  $editor
360
+     * 
361
+     * @return void
362
+     * 
363
+     * @throws \InvalidArgumentException
364
+     */
365
+    public function setEditor($editor)
366
+    {
367
+        if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
368
+            throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
369
+                implode(', ', array_keys($this->editors))
370
+            );
371
+        }
372
+
373
+        $this->editor = $editor;
374
+    }
375
+
376
+    /**
377
+     * Given a string file path, and an integer file line,
378
+     * executes the editor resolver and returns.
379
+     * 
380
+     * @param  string  $file
381
+     * @param  int	   $line
382
+     * 
383
+     * @return string|bool
384
+     * 
385
+     * @throws \UnexpectedValueException
386
+     */
387
+    public function getEditorAtHref($file, $line)
388
+    {
389
+        $editor = $this->getEditor($file, $line);
390
+
391
+        if (empty($editor))	{
392
+            return false;
393
+        }
394
+
395
+        if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
396
+            throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
397
+        }
398
+
399
+        $editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
400
+        $editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
401
+
402
+        return $editor['url'];
403
+    }
404
+
405
+    /**
406
+     * The editor must be a valid callable function/closure.
407
+     * 
408
+     * @param  string  $file
409
+     * @param  int	   $line
410
+     * 
411
+     * @return array
412
+     */
413
+    protected function getEditor($file, $line): array
414
+    {
415
+        if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
416
+            return [];
417
+        }
418
+
419
+        if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
420
+            return ['url' => $this->editors[$this->editor]];
421
+        }
422
+
423
+        if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
424
+            if (is_callable($this->editor)) {
425
+                $callback = call_user_func($this->editor, $file, $line);
426
+            } else {
427
+                $callback = call_user_func($this->editors[$this->editor], $file, $line);
428
+            }
429
+
430
+            if (empty($callback)) {
431
+                return [];
432
+            }
433
+
434
+            if (is_string($callback)) {
435
+                return ['url' => $callback];
436
+            }
437 437
 			
438
-			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
439
-		}
438
+            return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
439
+        }
440 440
 		
441
-		return [];
442
-	}
443
-
444
-	/**
445
-	 * Registered the editor.
446
-	 * 
447
-	 * @return string
448
-	 */
449
-	public function getEditorcode(): string
450
-	{
451
-		return $this->editor;
452
-	}
441
+        return [];
442
+    }
443
+
444
+    /**
445
+     * Registered the editor.
446
+     * 
447
+     * @return string
448
+     */
449
+    public function getEditorcode(): string
450
+    {
451
+        return $this->editor;
452
+    }
453 453
 	
454
-	/**
455
-	 * Sets the brand of project.
456
-	 * 
457
-	 * @param  string  $brand
458
-	 * 
459
-	 * @return void
460
-	 */
461
-	public function setBrand($brand): void
462
-	{
463
-		$this->brand = (string) $brand;
464
-	}
454
+    /**
455
+     * Sets the brand of project.
456
+     * 
457
+     * @param  string  $brand
458
+     * 
459
+     * @return void
460
+     */
461
+    public function setBrand($brand): void
462
+    {
463
+        $this->brand = (string) $brand;
464
+    }
465 465
 	
466
-	/**
467
-	 * Sets the page title web.
468
-	 * 
469
-	 * @param  string  $title
470
-	 * 
471
-	 * @return void
472
-	 */
473
-	public function setPageTitle($title): void
474
-	{
475
-		$this->pageTitle = (string) $title;
476
-	}
466
+    /**
467
+     * Sets the page title web.
468
+     * 
469
+     * @param  string  $title
470
+     * 
471
+     * @return void
472
+     */
473
+    public function setPageTitle($title): void
474
+    {
475
+        $this->pageTitle = (string) $title;
476
+    }
477 477
 }
478 478
\ No newline at end of file
Please login to merge, or discard this patch.