Passed
Push — master ( f08e5d...a3e128 )
by Alexander
11:58
created
src/components/Debug/Exceptions/Handlers/PleasingPageHandler.php 1 patch
Indentation   +432 added lines, -432 removed lines patch added patch discarded remove patch
@@ -39,455 +39,455 @@
 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
-		$servers    = array_merge($this->getDefaultServers(), $this->tables);
156
-		$routing    = array_merge($this->getDefaultRouting(), $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
+        $servers    = array_merge($this->getDefaultServers(), $this->tables);
156
+        $routing    = array_merge($this->getDefaultRouting(), $this->tables);
157 157
 		
158
-		return [ 
159
-			'class' => explode('\\', $supervisor->getExceptionName()),
160
-			'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
161
-			'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
162
-			'header' => $this->getResource('views/partials/updown/header.php'),
163
-			'footer' => $this->getResource('views/partials/updown/footer.php'),
164
-			'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
165
-			'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
166
-			'section_frame' => $this->getResource('views/partials/section_frame.php'),
167
-			'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
168
-			'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
169
-			'section_code' => $this->getResource('views/partials/section_code.php'),
170
-			'code_source' => $this->getResource('views/partials/codes/code_source.php'),
171
-			'request_info' => $this->getResource('views/partials/request_info.php'),
172
-			'navigation' => $this->getResource('views/partials/details/navigation.php'),
173
-			'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
174
-			'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
175
-			'handler' => $this,
176
-			'handlers' => $this->getDebug()->getHandlers(),
177
-			'debug' => $this->getDebug(),
178
-			'code' => $this->getExceptionCode(),
179
-			'message' => $supervisor->getExceptionMessage(),
180
-			'frames' => $this->getExceptionFrames(),
181
-			'servers' => $this->getProcessTables($servers),
182
-			'routes' => $this->getProcessTables($routing),
183
-		];
184
-	}
158
+        return [ 
159
+            'class' => explode('\\', $supervisor->getExceptionName()),
160
+            'stylesheet' => preg_replace('#[\r\n\t ]+#', ' ', $style),
161
+            'javascript' => preg_replace('#[\r\n\t ]+#', ' ', $jscript),
162
+            'header' => $this->getResource('views/partials/updown/header.php'),
163
+            'footer' => $this->getResource('views/partials/updown/footer.php'),
164
+            'info_exception' => $this->getResource('views/partials/info/info_exception.php'),
165
+            'section_stack_exception' => $this->getResource('views/partials/section_stack_exception.php'),
166
+            'section_frame' => $this->getResource('views/partials/section_frame.php'),
167
+            'frame_description' => $this->getResource('views/partials/frames/frame_description.php'),
168
+            'frame_list' => $this->getResource('views/partials/frames//frame_list.php'),
169
+            'section_code' => $this->getResource('views/partials/section_code.php'),
170
+            'code_source' => $this->getResource('views/partials/codes/code_source.php'),
171
+            'request_info' => $this->getResource('views/partials/request_info.php'),
172
+            'navigation' => $this->getResource('views/partials/details/navigation.php'),
173
+            'section_detail_context' => $this->getResource('views/partials/details/section_detail_context.php'),
174
+            'plain_exception' => Formatter::formatExceptionAsPlainText($this->getSupervisor()),
175
+            'handler' => $this,
176
+            'handlers' => $this->getDebug()->getHandlers(),
177
+            'debug' => $this->getDebug(),
178
+            'code' => $this->getExceptionCode(),
179
+            'message' => $supervisor->getExceptionMessage(),
180
+            'frames' => $this->getExceptionFrames(),
181
+            'servers' => $this->getProcessTables($servers),
182
+            'routes' => $this->getProcessTables($routing),
183
+        ];
184
+    }
185 185
 	
186
-	/**
187
-	 * The way in which the data sender (usually the server) can tell the recipient
188
-	 * (the browser, in general) what type of data is being sent in this case, html format tagged.
189
-	 * 
190
-	 * @return string
191
-	 */
192
-	public function contentType(): string
193
-	{
194
-		return 'text/html;charset=UTF-8';
195
-	}
196
-
197
-	/**
198
-	 * Gets the brand of project.
199
-	 * 
200
-	 * @return string
201
-	 */
202
-	public function getBrand(): string
203
-	{
204
-		return $this->brand;
205
-	}
206
-
207
-	/**
208
-	 * Returns the default servers.
209
-	 * 
210
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
211
-	 */
212
-	protected function getDefaultServers()
213
-	{
214
-		$server = [
215
-			'host' => $_SERVER['HTTP_HOST'], 
216
-			'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
217
-			'accept' => $_SERVER['HTTP_ACCEPT'], 
218
-			'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
219
-			'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
220
-		    'connection' => $_SERVER['HTTP_CONNECTION'],
221
-			'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
222
-			'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
223
-			'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
224
-			'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
225
-			'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
226
-		];
227
-
228
-		return [new ArrayTable($server)];
229
-	}
230
-
231
-	/**
232
-	 * Returns the default routing.
233
-	 * 
234
-	 * @return \Syscodes\Components\Contracts\Debug\Table[]
235
-	 */
236
-	protected function getDefaultRouting()
237
-	{
238
-		$routing = [
239
-			'middleware' => config('routes.routes'),
240
-		];
241
-
242
-		return [new ArrayTable($routing)];
243
-	}
244
-
245
-	/**
246
-	 * Get the code of the exception that is currently being handled.
247
-	 * 
248
-	 * @return string
249
-	 */
250
-	protected function getExceptionCode()
251
-	{
252
-		$exception = $this->getException();
253
-		$code      = $exception->getCode();
254
-
255
-		if ($exception instanceof ErrorException) {
256
-			$code = Misc::translateErrorCode($exception->getSeverity());
257
-		}
258
-
259
-		return (string) $code;
260
-	}
261
-
262
-	/**
263
-	 * Get the stack trace frames of the exception that is currently being handled.
264
-	 * 
265
-	 * @return \Syscodes\Components\Debug\Engine\Supervisor;
266
-	 */
267
-	protected function getExceptionFrames()
268
-	{
269
-		$frames = $this->getSupervisor()->getFrames();
186
+    /**
187
+     * The way in which the data sender (usually the server) can tell the recipient
188
+     * (the browser, in general) what type of data is being sent in this case, html format tagged.
189
+     * 
190
+     * @return string
191
+     */
192
+    public function contentType(): string
193
+    {
194
+        return 'text/html;charset=UTF-8';
195
+    }
196
+
197
+    /**
198
+     * Gets the brand of project.
199
+     * 
200
+     * @return string
201
+     */
202
+    public function getBrand(): string
203
+    {
204
+        return $this->brand;
205
+    }
206
+
207
+    /**
208
+     * Returns the default servers.
209
+     * 
210
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
211
+     */
212
+    protected function getDefaultServers()
213
+    {
214
+        $server = [
215
+            'host' => $_SERVER['HTTP_HOST'], 
216
+            'user-agent' => $_SERVER['HTTP_USER_AGENT'], 
217
+            'accept' => $_SERVER['HTTP_ACCEPT'], 
218
+            'accept-language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'], 
219
+            'accept-encoding' => $_SERVER['HTTP_ACCEPT_ENCODING'],
220
+            'connection' => $_SERVER['HTTP_CONNECTION'],
221
+            'upgrade-insecure-requests' => $_SERVER['HTTP_UPGRADE_INSECURE_REQUESTS'], 
222
+            'sec-fetch-dest' => $_SERVER['HTTP_SEC_FETCH_DEST'],
223
+            'sec-fetch-mode' => $_SERVER['HTTP_SEC_FETCH_MODE'],
224
+            'sec-fetch-site' => $_SERVER['HTTP_SEC_FETCH_SITE'],
225
+            'sec-fetch-user' => $_SERVER['HTTP_SEC_FETCH_USER'],
226
+        ];
227
+
228
+        return [new ArrayTable($server)];
229
+    }
230
+
231
+    /**
232
+     * Returns the default routing.
233
+     * 
234
+     * @return \Syscodes\Components\Contracts\Debug\Table[]
235
+     */
236
+    protected function getDefaultRouting()
237
+    {
238
+        $routing = [
239
+            'middleware' => config('routes.routes'),
240
+        ];
241
+
242
+        return [new ArrayTable($routing)];
243
+    }
244
+
245
+    /**
246
+     * Get the code of the exception that is currently being handled.
247
+     * 
248
+     * @return string
249
+     */
250
+    protected function getExceptionCode()
251
+    {
252
+        $exception = $this->getException();
253
+        $code      = $exception->getCode();
254
+
255
+        if ($exception instanceof ErrorException) {
256
+            $code = Misc::translateErrorCode($exception->getSeverity());
257
+        }
258
+
259
+        return (string) $code;
260
+    }
261
+
262
+    /**
263
+     * Get the stack trace frames of the exception that is currently being handled.
264
+     * 
265
+     * @return \Syscodes\Components\Debug\Engine\Supervisor;
266
+     */
267
+    protected function getExceptionFrames()
268
+    {
269
+        $frames = $this->getSupervisor()->getFrames();
270 270
 		
271
-		return $frames;
272
-	}
271
+        return $frames;
272
+    }
273 273
 	
274
-	/**
275
-	 * Gets the page title web.
276
-	 * 
277
-	 * @return string
278
-	 */
279
-	public function getPageTitle(): string
280
-	{
281
-		return $this->pageTitle;
282
-	}
283
-
284
-	/**
285
-	 * Processes an array of tables making sure everything is all right.
286
-	 * 
287
-	 * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
288
-	 * 
289
-	 * @return array
290
-	 */
291
-	protected function getProcessTables(array $tables): array
292
-	{
293
-		$processTables = [];
294
-
295
-		foreach ($tables as $table) {
296
-			if ( ! $table instanceof Table) {
297
-				continue;
298
-			}
274
+    /**
275
+     * Gets the page title web.
276
+     * 
277
+     * @return string
278
+     */
279
+    public function getPageTitle(): string
280
+    {
281
+        return $this->pageTitle;
282
+    }
283
+
284
+    /**
285
+     * Processes an array of tables making sure everything is all right.
286
+     * 
287
+     * @param  \Syscodes\Components\Contracts\Debug\Table[]  $tables
288
+     * 
289
+     * @return array
290
+     */
291
+    protected function getProcessTables(array $tables): array
292
+    {
293
+        $processTables = [];
294
+
295
+        foreach ($tables as $table) {
296
+            if ( ! $table instanceof Table) {
297
+                continue;
298
+            }
299 299
 			
300
-			$label = $table->getLabel();
301
-
302
-			try {
303
-				$data = (array) $table->getData();
304
-
305
-				if ( ! (is_array($data) || $data instanceof Traversable)) {
306
-					$data = [];
307
-				}
308
-			} catch (Exception $e) {
309
-				$data = [];
310
-			}
311
-
312
-			$processTables[$label] = $data;
313
-		}
314
-
315
-		return $processTables;
316
-	}
317
-
318
-	/**
319
-	 * Finds a resource, by its relative path, in all available search paths.
320
-	 *
321
-	 * @param  string  $resource
322
-	 * 
323
-	 * @return string
324
-	 * 
325
-	 * @throws \RuntimeException
326
-	 */
327
-	protected function getResource($resource)
328
-	{
329
-		if (isset($this->resourceCache[$resource])) {
330
-			return $this->resourceCache[$resource];
331
-		}
332
-
333
-		foreach ($this->searchPaths as $path) {
334
-			$fullPath = $path.DIRECTORY_SEPARATOR.$resource;
335
-
336
-			if (is_file($fullPath)) {
337
-				// Cache:
338
-				$this->resourceCache[$resource] = $fullPath;
339
-
340
-				return $fullPath;
341
-			}
342
-		}
343
-
344
-		throw new RuntimeException( 
345
-				"Could not find resource '{$resource}' in any resource paths.". 
346
-				"(searched: ".join(", ", $this->searchPaths).")");
347
-	}
300
+            $label = $table->getLabel();
301
+
302
+            try {
303
+                $data = (array) $table->getData();
304
+
305
+                if ( ! (is_array($data) || $data instanceof Traversable)) {
306
+                    $data = [];
307
+                }
308
+            } catch (Exception $e) {
309
+                $data = [];
310
+            }
311
+
312
+            $processTables[$label] = $data;
313
+        }
314
+
315
+        return $processTables;
316
+    }
317
+
318
+    /**
319
+     * Finds a resource, by its relative path, in all available search paths.
320
+     *
321
+     * @param  string  $resource
322
+     * 
323
+     * @return string
324
+     * 
325
+     * @throws \RuntimeException
326
+     */
327
+    protected function getResource($resource)
328
+    {
329
+        if (isset($this->resourceCache[$resource])) {
330
+            return $this->resourceCache[$resource];
331
+        }
332
+
333
+        foreach ($this->searchPaths as $path) {
334
+            $fullPath = $path.DIRECTORY_SEPARATOR.$resource;
335
+
336
+            if (is_file($fullPath)) {
337
+                // Cache:
338
+                $this->resourceCache[$resource] = $fullPath;
339
+
340
+                return $fullPath;
341
+            }
342
+        }
343
+
344
+        throw new RuntimeException( 
345
+                "Could not find resource '{$resource}' in any resource paths.". 
346
+                "(searched: ".join(", ", $this->searchPaths).")");
347
+    }
348 348
 	
349
-	/**
350
-	 * Given an exception and status code will display the error to the client.
351
-	 * 
352
-	 * @return int|null
353
-	 */
354
-	public function handle()
355
-	{	
356
-		$templatePath = $this->getResource('views/debug.layout.php');
357
-
358
-		$vars = $this->collectionVars();
349
+    /**
350
+     * Given an exception and status code will display the error to the client.
351
+     * 
352
+     * @return int|null
353
+     */
354
+    public function handle()
355
+    {	
356
+        $templatePath = $this->getResource('views/debug.layout.php');
357
+
358
+        $vars = $this->collectionVars();
359 359
 		
360
-		if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
360
+        if (empty($vars['message'])) $vars['message'] = __('exception.noMessage');
361 361
 		
362
-		$this->template->setVariables($vars);
363
-		$this->template->render($templatePath);
362
+        $this->template->setVariables($vars);
363
+        $this->template->render($templatePath);
364 364
 		
365
-		return MainHandler::QUIT;
366
-	}
367
-
368
-	/**
369
-	 * Set the editor to use to open referenced files, by a string identifier or callable
370
-	 * that will be executed for every file reference. Should return a string.
371
-	 * 
372
-	 * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
373
-	 * @example  $debug->setEditor('vscode');
374
-	 * 
375
-	 * @param  string  $editor
376
-	 * 
377
-	 * @return void
378
-	 * 
379
-	 * @throws \InvalidArgumentException
380
-	 */
381
-	public function setEditor($editor)
382
-	{
383
-		if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
384
-			throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
385
-				implode(', ', array_keys($this->editors))
386
-			);
387
-		}
388
-
389
-		$this->editor = $editor;
390
-	}
391
-
392
-	/**
393
-	 * Given a string file path, and an integer file line,
394
-	 * executes the editor resolver and returns.
395
-	 * 
396
-	 * @param  string  $file
397
-	 * @param  int	   $line
398
-	 * 
399
-	 * @return string|bool
400
-	 * 
401
-	 * @throws \UnexpectedValueException
402
-	 */
403
-	public function getEditorAtHref($file, $line)
404
-	{
405
-		$editor = $this->getEditor($file, $line);
406
-
407
-		if (empty($editor))	{
408
-			return false;
409
-		}
410
-
411
-		if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
412
-			throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
413
-		}
414
-
415
-		$editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
416
-		$editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
417
-
418
-		return $editor['url'];
419
-	}
420
-
421
-	/**
422
-	 * The editor must be a valid callable function/closure.
423
-	 * 
424
-	 * @param  string  $file
425
-	 * @param  int	   $line
426
-	 * 
427
-	 * @return array
428
-	 */
429
-	protected function getEditor($file, $line): array
430
-	{
431
-		if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
432
-			return [];
433
-		}
434
-
435
-		if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
436
-			return ['url' => $this->editors[$this->editor]];
437
-		}
438
-
439
-		if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
440
-			if (is_callable($this->editor)) {
441
-				$callback = call_user_func($this->editor, $file, $line);
442
-			} else {
443
-				$callback = call_user_func($this->editors[$this->editor], $file, $line);
444
-			}
445
-
446
-			if (empty($callback)) {
447
-				return [];
448
-			}
449
-
450
-			if (is_string($callback)) {
451
-				return ['url' => $callback];
452
-			}
365
+        return MainHandler::QUIT;
366
+    }
367
+
368
+    /**
369
+     * Set the editor to use to open referenced files, by a string identifier or callable
370
+     * that will be executed for every file reference. Should return a string.
371
+     * 
372
+     * @example  $debug->setEditor(function($file, $line) { return "file:///{$file}"; });
373
+     * @example  $debug->setEditor('vscode');
374
+     * 
375
+     * @param  string  $editor
376
+     * 
377
+     * @return void
378
+     * 
379
+     * @throws \InvalidArgumentException
380
+     */
381
+    public function setEditor($editor)
382
+    {
383
+        if ( ! is_callable($editor) && ! isset($this->editors[$editor])) {
384
+            throw new InvalidArgumentException("Unknown editor identifier: [{$editor}]. Known editors: " .
385
+                implode(', ', array_keys($this->editors))
386
+            );
387
+        }
388
+
389
+        $this->editor = $editor;
390
+    }
391
+
392
+    /**
393
+     * Given a string file path, and an integer file line,
394
+     * executes the editor resolver and returns.
395
+     * 
396
+     * @param  string  $file
397
+     * @param  int	   $line
398
+     * 
399
+     * @return string|bool
400
+     * 
401
+     * @throws \UnexpectedValueException
402
+     */
403
+    public function getEditorAtHref($file, $line)
404
+    {
405
+        $editor = $this->getEditor($file, $line);
406
+
407
+        if (empty($editor))	{
408
+            return false;
409
+        }
410
+
411
+        if ( ! isset($editor['url']) || ! is_string($editor['url'])) {
412
+            throw new UnexpectedValueException(__METHOD__.'should always resolve to a string or a valid editor array');
413
+        }
414
+
415
+        $editor['url'] = str_replace("%file", rawurldecode($file), $editor['url']);
416
+        $editor['url'] = str_replace("%line", rawurldecode($line), $editor['url']);
417
+
418
+        return $editor['url'];
419
+    }
420
+
421
+    /**
422
+     * The editor must be a valid callable function/closure.
423
+     * 
424
+     * @param  string  $file
425
+     * @param  int	   $line
426
+     * 
427
+     * @return array
428
+     */
429
+    protected function getEditor($file, $line): array
430
+    {
431
+        if ( ! $this->editor || ( ! is_string($this->editor) && ! is_callable($this->editor))) {
432
+            return [];
433
+        }
434
+
435
+        if (is_string($this->editor) && isset($this->editors[$this->editor]) && ! is_callable($this->editors[$this->editor])) {
436
+            return ['url' => $this->editors[$this->editor]];
437
+        }
438
+
439
+        if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
440
+            if (is_callable($this->editor)) {
441
+                $callback = call_user_func($this->editor, $file, $line);
442
+            } else {
443
+                $callback = call_user_func($this->editors[$this->editor], $file, $line);
444
+            }
445
+
446
+            if (empty($callback)) {
447
+                return [];
448
+            }
449
+
450
+            if (is_string($callback)) {
451
+                return ['url' => $callback];
452
+            }
453 453
 			
454
-			return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
455
-		}
454
+            return ['url' => isset($callback['url']) ? $callback['url'] : $callback];
455
+        }
456 456
 		
457
-		return [];
458
-	}
459
-
460
-	/**
461
-	 * Registered the editor.
462
-	 * 
463
-	 * @return string
464
-	 */
465
-	public function getEditorcode(): string
466
-	{
467
-		return $this->editor;
468
-	}
457
+        return [];
458
+    }
459
+
460
+    /**
461
+     * Registered the editor.
462
+     * 
463
+     * @return string
464
+     */
465
+    public function getEditorcode(): string
466
+    {
467
+        return $this->editor;
468
+    }
469 469
 	
470
-	/**
471
-	 * Sets the brand of project.
472
-	 * 
473
-	 * @param  string  $brand
474
-	 * 
475
-	 * @return void
476
-	 */
477
-	public function setBrand($brand): void
478
-	{
479
-		$this->brand = (string) $brand;
480
-	}
470
+    /**
471
+     * Sets the brand of project.
472
+     * 
473
+     * @param  string  $brand
474
+     * 
475
+     * @return void
476
+     */
477
+    public function setBrand($brand): void
478
+    {
479
+        $this->brand = (string) $brand;
480
+    }
481 481
 	
482
-	/**
483
-	 * Sets the page title web.
484
-	 * 
485
-	 * @param  string  $title
486
-	 * 
487
-	 * @return void
488
-	 */
489
-	public function setPageTitle($title): void
490
-	{
491
-		$this->pageTitle = (string) $title;
492
-	}
482
+    /**
483
+     * Sets the page title web.
484
+     * 
485
+     * @param  string  $title
486
+     * 
487
+     * @return void
488
+     */
489
+    public function setPageTitle($title): void
490
+    {
491
+        $this->pageTitle = (string) $title;
492
+    }
493 493
 }
494 494
\ No newline at end of file
Please login to merge, or discard this patch.