Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
index 218b5b82b0..b45de2dce1 100644
--- a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
+++ b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php
@@ -1,264 +1,290 @@
<?php
final class PhabricatorSettingsEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'settings.settings';
private $isSelfEdit;
private $profileURI;
+ private $settingsPanel;
public function setIsSelfEdit($is_self_edit) {
$this->isSelfEdit = $is_self_edit;
return $this;
}
public function getIsSelfEdit() {
return $this->isSelfEdit;
}
public function setProfileURI($profile_uri) {
$this->profileURI = $profile_uri;
return $this;
}
public function getProfileURI() {
return $this->profileURI;
}
+ public function setSettingsPanel($settings_panel) {
+ $this->settingsPanel = $settings_panel;
+ return $this;
+ }
+
+ public function getSettingsPanel() {
+ return $this->settingsPanel;
+ }
+
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Settings');
}
public function getSummaryHeader() {
return pht('Edit Settings Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit settings.');
}
public function getEngineApplicationClass() {
return 'PhabricatorSettingsApplication';
}
protected function newEditableObject() {
return new PhabricatorUserPreferences();
}
protected function newObjectQuery() {
return new PhabricatorUserPreferencesQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Settings');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Settings');
}
protected function getObjectEditTitleText($object) {
$page = $this->getSelectedPage();
if ($page) {
return $page->getLabel();
}
return pht('Settings');
}
protected function getObjectEditShortText($object) {
if (!$object->getUser()) {
return pht('Global Defaults');
} else {
if ($this->getIsSelfEdit()) {
return pht('Personal Settings');
} else {
return pht('Account Settings');
}
}
}
protected function getObjectCreateShortText() {
return pht('Create Settings');
}
protected function getObjectName() {
$page = $this->getSelectedPage();
if ($page) {
return $page->getLabel();
}
return pht('Settings');
}
protected function getPageHeader($object) {
$user = $object->getUser();
if ($user) {
$text = pht('Edit Settings: %s', $user->getUserName());
} else {
$text = pht('Edit Global Settings');
}
$header = id(new PHUIHeaderView())
->setHeader($text);
return $header;
}
protected function getEditorURI() {
throw new PhutilMethodNotImplementedException();
}
protected function getObjectCreateCancelURI($object) {
return '/settings/';
}
protected function getObjectViewURI($object) {
return $object->getEditURI();
}
protected function getCreateNewObjectPolicy() {
return PhabricatorPolicies::POLICY_ADMIN;
}
public function getEffectiveObjectEditCancelURI($object) {
if (!$object->getUser()) {
return '/settings/';
}
if ($this->getIsSelfEdit()) {
return null;
}
if ($this->getProfileURI()) {
return $this->getProfileURI();
}
return parent::getEffectiveObjectEditCancelURI($object);
}
protected function newPages($object) {
$viewer = $this->getViewer();
$user = $object->getUser();
$panels = PhabricatorSettingsPanel::getAllDisplayPanels();
foreach ($panels as $key => $panel) {
if (!($panel instanceof PhabricatorEditEngineSettingsPanel)) {
unset($panels[$key]);
continue;
}
$panel->setViewer($viewer);
if ($user) {
$panel->setUser($user);
}
}
$pages = array();
$uris = array();
foreach ($panels as $key => $panel) {
$uris[$key] = $panel->getPanelURI();
$page = $panel->newEditEnginePage();
if (!$page) {
continue;
}
$pages[] = $page;
}
$more_pages = array(
id(new PhabricatorEditPage())
->setKey('extra')
->setLabel(pht('Extra Settings'))
->setIsDefault(true),
);
foreach ($more_pages as $page) {
$pages[] = $page;
}
return $pages;
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$settings = PhabricatorSetting::getAllEnabledSettings($viewer);
foreach ($settings as $key => $setting) {
$setting = clone $setting;
$setting->setViewer($viewer);
$settings[$key] = $setting;
}
$settings = msortv($settings, 'getSettingOrderVector');
$fields = array();
foreach ($settings as $setting) {
foreach ($setting->newCustomEditFields($object) as $field) {
$fields[] = $field;
}
}
return $fields;
}
protected function getValidationExceptionShortMessage(
PhabricatorApplicationTransactionValidationException $ex,
PhabricatorEditField $field) {
// Settings fields all have the same transaction type so we need to make
// sure the transaction is changing the same setting before matching an
// error to a given field.
$xaction_type = $field->getTransactionType();
if ($xaction_type == PhabricatorUserPreferencesTransaction::TYPE_SETTING) {
$property = PhabricatorUserPreferencesTransaction::PROPERTY_SETTING;
$field_setting = idx($field->getMetadata(), $property);
foreach ($ex->getErrors() as $error) {
if ($error->getType() !== $xaction_type) {
continue;
}
$xaction = $error->getTransaction();
if (!$xaction) {
continue;
}
$xaction_setting = $xaction->getMetadataValue($property);
if ($xaction_setting != $field_setting) {
continue;
}
$short_message = $error->getShortMessage();
if ($short_message !== null) {
return $short_message;
}
}
return null;
}
return parent::getValidationExceptionShortMessage($ex, $field);
}
protected function newEditFormHeadContent(
PhabricatorEditEnginePageState $state) {
+ $content = array();
+
if ($state->getIsSave()) {
- return id(new PHUIInfoView())
+ $content[] = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('Changes saved.'));
}
- return null;
+ $panel = $this->getSettingsPanel();
+ $content[] = $panel->newSettingsPanelEditFormHeadContent($state);
+
+ return $content;
+ }
+
+ protected function newEditFormTailContent(
+ PhabricatorEditEnginePageState $state) {
+
+ $content = array();
+
+ $panel = $this->getSettingsPanel();
+ $content[] = $panel->newSettingsPanelEditFormTailContent($state);
+
+ return $content;
}
}
diff --git a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
index 5aad71785e..a4c9c3b789 100644
--- a/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorEditEngineSettingsPanel.php
@@ -1,74 +1,85 @@
<?php
abstract class PhabricatorEditEngineSettingsPanel
extends PhabricatorSettingsPanel {
final public function processRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$user = $this->getUser();
if ($user && ($user->getPHID() === $viewer->getPHID())) {
$is_self = true;
} else {
$is_self = false;
}
if ($user && $user->getPHID()) {
$profile_uri = '/people/manage/'.$user->getID().'/';
} else {
$profile_uri = null;
}
$engine = id(new PhabricatorSettingsEditEngine())
->setController($this->getController())
->setNavigation($this->getNavigation())
+ ->setSettingsPanel($this)
->setIsSelfEdit($is_self)
->setProfileURI($profile_uri);
$preferences = $this->getPreferences();
$engine->setTargetObject($preferences);
return $engine->buildResponse();
}
final public function isEnabled() {
// Only enable the panel if it has any fields.
$field_keys = $this->getPanelSettingsKeys();
return (bool)$field_keys;
}
final public function newEditEnginePage() {
$field_keys = $this->getPanelSettingsKeys();
if (!$field_keys) {
return null;
}
$key = $this->getPanelKey();
$label = $this->getPanelName();
$panel_uri = $this->getPanelURI();
return id(new PhabricatorEditPage())
->setKey($key)
->setLabel($label)
->setViewURI($panel_uri)
->setFieldKeys($field_keys);
}
final public function getPanelSettingsKeys() {
$viewer = $this->getViewer();
$settings = PhabricatorSetting::getAllEnabledSettings($viewer);
$this_key = $this->getPanelKey();
$panel_settings = array();
foreach ($settings as $setting) {
if ($setting->getSettingPanelKey() == $this_key) {
$panel_settings[] = $setting;
}
}
return mpull($panel_settings, 'getSettingKey');
}
+ public function newSettingsPanelEditFormHeadContent(
+ PhabricatorEditEnginePageState $state) {
+ return null;
+ }
+
+ public function newSettingsPanelEditFormTailContent(
+ PhabricatorEditEnginePageState $state) {
+ return null;
+ }
+
}
diff --git a/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php b/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
index 9e402b1492..d9e8736954 100644
--- a/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
+++ b/src/applications/settings/panel/PhabricatorExternalEditorSettingsPanel.php
@@ -1,24 +1,170 @@
<?php
final class PhabricatorExternalEditorSettingsPanel
extends PhabricatorEditEngineSettingsPanel {
const PANELKEY = 'editor';
public function getPanelName() {
return pht('External Editor');
}
public function getPanelMenuIcon() {
return 'fa-i-cursor';
}
public function getPanelGroupKey() {
return PhabricatorSettingsApplicationsPanelGroup::PANELGROUPKEY;
}
public function isTemplatePanel() {
return true;
}
+ public function newSettingsPanelEditFormHeadContent(
+ PhabricatorEditEnginePageState $state) {
+
+ // The "Editor" setting stored in the database may be invalidated by
+ // configuration or software changes. If a saved URI becomes invalid
+ // (for example, its protocol is removed from the list of allowed
+ // protocols), it will stop working.
+
+ // If the stored value has a problem like this, show a static error
+ // message without requiring the user to save changes.
+
+ if ($state->getIsSubmit()) {
+ return null;
+ }
+
+ $viewer = $this->getViewer();
+ $pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
+
+ if (!strlen($pattern)) {
+ return null;
+ }
+
+ $caught = null;
+ try {
+ id(new PhabricatorEditorURIEngine())
+ ->setPattern($pattern)
+ ->validatePattern();
+ } catch (PhabricatorEditorURIParserException $ex) {
+ $caught = $ex;
+ }
+
+ if (!$caught) {
+ return null;
+ }
+
+ return id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->appendChild($caught->getMessage());
+ }
+
+ public function newSettingsPanelEditFormTailContent(
+ PhabricatorEditEnginePageState $state) {
+ $viewer = $this->getViewer();
+
+ $variables = PhabricatorEditorURIEngine::getVariableDefinitions();
+
+ $rows = array();
+ foreach ($variables as $key => $variable) {
+ $rows[] = array(
+ phutil_tag('tt', array(), '%'.$key),
+ $variable['name'],
+ $variable['example'],
+ );
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('Variable'),
+ pht('Replaced With'),
+ pht('Example'),
+ ))
+ ->setColumnClasses(
+ array(
+ 'center',
+ 'pri',
+ 'wide',
+ ));
+
+ $variables_box = id(new PHUIObjectBoxView())
+ ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
+ ->setHeaderText(pht('External Editor URI Variables'))
+ ->setTable($table);
+
+ $label_map = array(
+ 'http' => pht('Hypertext Transfer Protocol'),
+ 'https' => pht('Hypertext Transfer Protocol over SSL'),
+ 'txmt' => pht('TextMate'),
+ 'mvim' => pht('MacVim'),
+ 'subl' => pht('Sublime Text'),
+ 'vim' => pht('Vim'),
+ 'emacs' => pht('Emacs'),
+ 'vscode' => pht('Visual Studio Code'),
+ 'editor' => pht('Generic Editor'),
+ );
+
+ $default_label = phutil_tag('em', array(), pht('Supported Protocol'));
+
+ $config_key = 'uri.allowed-editor-protocols';
+
+ $protocols = PhabricatorEnv::getEnvConfig($config_key);
+ $protocols = array_keys($protocols);
+ sort($protocols);
+
+ $protocol_rows = array();
+ foreach ($protocols as $protocol) {
+ $label = idx($label_map, $protocol, $default_label);
+
+ $protocol_rows[] = array(
+ $protocol.'://',
+ $label,
+ );
+ }
+
+ $protocol_table = id(new AphrontTableView($protocol_rows))
+ ->setNoDataString(
+ pht(
+ 'Phabricator is not configured to allow any editor protocols.'))
+ ->setHeaders(
+ array(
+ pht('Protocol'),
+ pht('Description'),
+ ))
+ ->setColumnClasses(
+ array(
+ 'pri',
+ 'wide',
+ ));
+
+ $is_admin = $viewer->getIsAdmin();
+
+ $configure_button = id(new PHUIButtonView())
+ ->setTag('a')
+ ->setText(pht('View Configuration'))
+ ->setIcon('fa-sliders')
+ ->setHref(
+ urisprintf(
+ '/config/edit/%s/',
+ $config_key))
+ ->setDisabled(!$is_admin);
+
+ $protocol_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Supported Editor Protocols'))
+ ->addActionLink($configure_button);
+
+ $protocols_box = id(new PHUIObjectBoxView())
+ ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
+ ->setHeader($protocol_header)
+ ->setTable($protocol_table);
+
+ return array(
+ $variables_box,
+ $protocols_box,
+ );
+ }
+
}
diff --git a/src/applications/settings/setting/PhabricatorEditorSetting.php b/src/applications/settings/setting/PhabricatorEditorSetting.php
index fd1fae8e70..6884217a3e 100644
--- a/src/applications/settings/setting/PhabricatorEditorSetting.php
+++ b/src/applications/settings/setting/PhabricatorEditorSetting.php
@@ -1,51 +1,54 @@
<?php
final class PhabricatorEditorSetting
extends PhabricatorStringSetting {
const SETTINGKEY = 'editor';
public function getSettingName() {
return pht('Editor Link');
}
public function getSettingPanelKey() {
return PhabricatorExternalEditorSettingsPanel::PANELKEY;
}
protected function getSettingOrder() {
return 300;
}
protected function getControlInstructions() {
return pht(
"Many text editors can be configured as URI handlers for special ".
- "protocols like `editor://`. If you have such an editor, Phabricator ".
- "can generate links that you can click to open files locally.".
+ "protocols like `editor://`. If you have installed and configured ".
+ "such an editor, Phabricator can generate links that you can click ".
+ "to open files locally.".
"\n\n".
- "These special variables are supported:".
+ "Provide a URI pattern for building external editor URIs in your ".
+ "environment. For example, if you use TextMate on macOS, the pattern ".
+ "for your machine look like this:".
"\n\n".
- "| Value | Replaced With |\n".
- "|-------|---------------|\n".
- "| `%%f` | Filename |\n".
- "| `%%l` | Line Number |\n".
- "| `%%r` | Repository Callsign |\n".
- "| `%%%%` | Literal `%%` |\n".
+ "```name=\"Example: TextMate on macOS\"\n".
+ "%s\n".
+ "```\n".
"\n\n".
"For complete instructions on editor configuration, ".
- "see **[[ %s | %s ]]**.",
+ "see **[[ %s | %s ]]**.".
+ "\n\n".
+ "See the tables below for a list of supported variables and protocols.",
+ 'txmt://open/?url=file:///Users/alincoln/editor_links/%r/%f&line=%l',
PhabricatorEnv::getDoclink('User Guide: Configuring an External Editor'),
pht('User Guide: Configuring an External Editor'));
}
public function validateTransactionValue($value) {
if (!strlen($value)) {
return;
}
id(new PhabricatorEditorURIEngine())
->setPattern($value)
->validatePattern();
}
}
diff --git a/src/docs/user/userguide/external_editor.diviner b/src/docs/user/userguide/external_editor.diviner
index c6f55183c8..07aa9db290 100644
--- a/src/docs/user/userguide/external_editor.diviner
+++ b/src/docs/user/userguide/external_editor.diviner
@@ -1,44 +1,44 @@
@title User Guide: Configuring an External Editor
@group userguide
Setting up an external editor to integrate with Diffusion and Differential.
-= Overview =
+Overview
+========
You can configure a URI handler to allow you to open files from Differential
and Diffusion in your preferred text editor.
-= Configuring Editors =
+Configuring Editors
+===================
To configure an external editor, go to {nav Settings > Application Settings >
External Editor} and set "Editor Link" to a URI pattern (see below). This
will enable an "Open in Editor" link in Differential, and an "Edit" button in
Diffusion.
In general, you'll set this field to something like:
- lang=uri
- editor://open/?file=%f
+```lang=uri
+editor://open/?file=%f
+```
-Some editors support opening multiple files at once when filenames are separated
-by spaces. If your editor supports this feature, set "Edit Multiple Files" to
-"Supported". Otherwise, you can set it to "Not Supported" to disable "Open All"
-buttons in the interface.
-
-== Configuring: TextMate on OS X ==
+Configuring: TextMate on macOS
+==============================
TextMate installs a `txmt://` handler by default, so it's easy to configure
this feature if you use TextMate.
First, create a local directory with symlinks for each repository callsign. For
example, if you're developing Phabricator, it might look like this:
/Users/alincoln/editor_links/ $ ls -l
... ARC -> /Users/alincoln/workspace/arcanist/
... P -> /Users/alincoln/workspace/phabricator/
... PHU -> /Users/alincoln/workspace/libphutil/
Then set your "Editor Link" to:
- lang=uri
- txmt://open/?url=file:///Users/alincoln/editor_links/%r/%f&line=%l
+```lang=uri
+txmt://open/?url=file:///Users/alincoln/editor_links/%r/%f&line=%l
+```
diff --git a/src/infrastructure/editor/PhabricatorEditorURIEngine.php b/src/infrastructure/editor/PhabricatorEditorURIEngine.php
index 43bfc23a5b..6abfa98677 100644
--- a/src/infrastructure/editor/PhabricatorEditorURIEngine.php
+++ b/src/infrastructure/editor/PhabricatorEditorURIEngine.php
@@ -1,335 +1,339 @@
<?php
final class PhabricatorEditorURIEngine
extends Phobject {
private $viewer;
private $repository;
private $pattern;
private $rawTokens;
private $repositoryTokens;
public static function newForViewer(PhabricatorUser $viewer) {
if (!$viewer->isLoggedIn()) {
return null;
}
$pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
if (!strlen(trim($pattern))) {
return null;
}
$engine = id(new self())
->setViewer($viewer)
->setPattern($pattern);
// If there's a problem with the pattern,
try {
$engine->validatePattern();
} catch (PhabricatorEditorURIParserException $ex) {
return null;
}
return $engine;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function getRepository() {
return $this->repository;
}
public function setPattern($pattern) {
$this->pattern = $pattern;
return $this;
}
public function getPattern() {
return $this->pattern;
}
public function validatePattern() {
$this->getRawURITokens();
return true;
}
public function getURIForPath($path, $line) {
$tokens = $this->getURITokensForRepository();
$variables = array(
'f' => $this->escapeToken($path),
'l' => $this->escapeToken($line),
);
$tokens = $this->newTokensWithVariables($tokens, $variables);
return $this->newStringFromTokens($tokens);
}
public function getURITokensForRepository() {
if (!$this->repositoryTokens) {
$this->repositoryTokens = $this->newURITokensForRepository();
}
return $this->repositoryTokens;
}
public static function getVariableDefinitions() {
return array(
'%' => array(
'name' => pht('Literal Percent Symbol'),
+ 'example' => '%',
),
'r' => array(
'name' => pht('Repository Callsign'),
+ 'example' => 'XYZ',
),
'f' => array(
'name' => pht('File Name'),
+ 'example' => pht('path/to/source.c'),
),
'l' => array(
'name' => pht('Line Number'),
+ 'example' => '777',
),
);
}
private function newURITokensForRepository() {
$tokens = $this->getRawURITokens();
$repository = $this->getRepository();
if (!$repository) {
throw new PhutilInvalidStateException('setRepository');
}
$variables = array(
'r' => $this->escapeToken($repository->getCallsign()),
);
return $this->newTokensWithVariables($tokens, $variables);
}
private function getRawURITokens() {
if (!$this->rawTokens) {
$this->rawTokens = $this->newRawURITokens();
}
return $this->rawTokens;
}
private function newRawURITokens() {
$raw_pattern = $this->getPattern();
$raw_tokens = self::newPatternTokens($raw_pattern);
$variable_definitions = self::getVariableDefinitions();
foreach ($raw_tokens as $token) {
if ($token['type'] !== 'variable') {
continue;
}
$value = $token['value'];
if (isset($variable_definitions[$value])) {
continue;
}
throw new PhabricatorEditorURIParserException(
pht(
'Editor pattern "%s" is invalid: the pattern contains an '.
'unrecognized variable ("%s"). Use "%%%%" to encode a literal '.
'percent symbol.',
$raw_pattern,
'%'.$value));
}
$variables = array(
'%' => '%',
);
$tokens = $this->newTokensWithVariables($raw_tokens, $variables);
$first_literal = null;
if ($tokens) {
foreach ($tokens as $token) {
if ($token['type'] === 'literal') {
$first_literal = $token['value'];
}
break;
}
if ($first_literal === null) {
throw new PhabricatorEditorURIParserException(
pht(
'Editor pattern "%s" is invalid: the pattern must begin with '.
'a valid editor protocol, but begins with a variable. This is '.
'very sneaky and also very forbidden.',
$raw_pattern));
}
}
$uri = new PhutilURI($first_literal);
$editor_protocol = $uri->getProtocol();
if (!$editor_protocol) {
throw new PhabricatorEditorURIParserException(
pht(
'Editor pattern "%s" is invalid: the pattern must begin with '.
'a valid editor protocol, but does not begin with a recognized '.
'protocol string.',
$raw_pattern));
}
$allowed_key = 'uri.allowed-editor-protocols';
$allowed_protocols = PhabricatorEnv::getEnvConfig($allowed_key);
if (empty($allowed_protocols[$editor_protocol])) {
throw new PhabricatorEditorURIParserException(
pht(
'Editor pattern "%s" is invalid: the pattern must begin with '.
'a valid editor protocol, but the protocol "%s://" is not allowed.',
$raw_pattern,
$editor_protocol));
}
return $tokens;
}
private function newTokensWithVariables(array $tokens, array $variables) {
// Replace all "variable" tokens that we have replacements for with
// the literal value.
foreach ($tokens as $key => $token) {
$type = $token['type'];
if ($type == 'variable') {
$variable = $token['value'];
if (isset($variables[$variable])) {
$tokens[$key] = array(
'type' => 'literal',
'value' => $variables[$variable],
);
}
}
}
// Now, merge sequences of adjacent "literal" tokens into a single token.
$last_literal = null;
foreach ($tokens as $key => $token) {
$is_literal = ($token['type'] === 'literal');
if (!$is_literal) {
$last_literal = null;
continue;
}
if ($last_literal !== null) {
$tokens[$key]['value'] =
$tokens[$last_literal]['value'].$token['value'];
unset($tokens[$last_literal]);
}
$last_literal = $key;
}
return $tokens;
}
private function escapeToken($token) {
// Paths are user controlled, so a clever user could potentially make
// editor links do surprising things with paths containing "/../".
// Find anything that looks like "/../" and mangle it.
$token = preg_replace('((^|/)\.\.(/|\z))', '\1dot-dot\2', $token);
return phutil_escape_uri($token);
}
private function newStringFromTokens(array $tokens) {
$result = array();
foreach ($tokens as $token) {
$token_type = $token['type'];
$token_value = $token['value'];
$is_literal = ($token_type === 'literal');
if (!$is_literal) {
throw new Exception(
pht(
'Editor pattern token list can not be converted into a string: '.
'it still contains a non-literal token ("%s", of type "%s").',
$token_value,
$token_type));
}
$result[] = $token_value;
}
$result = implode('', $result);
return $result;
}
public static function newPatternTokens($raw_pattern) {
$token_positions = array();
$len = strlen($raw_pattern);
for ($ii = 0; $ii < $len; $ii++) {
$c = $raw_pattern[$ii];
if ($c === '%') {
if (!isset($raw_pattern[$ii + 1])) {
throw new PhabricatorEditorURIParserException(
pht(
'Editor pattern "%s" is invalid: the final character in a '.
'pattern may not be an unencoded percent symbol ("%%"). '.
'Use "%%%%" to encode a literal percent symbol.',
$raw_pattern));
}
$token_positions[] = $ii;
$ii++;
}
}
// Add a final marker past the end of the string, so we'll collect any
// trailing literal bytes.
$token_positions[] = $len;
$tokens = array();
$cursor = 0;
foreach ($token_positions as $pos) {
$token_len = ($pos - $cursor);
if ($token_len > 0) {
$tokens[] = array(
'type' => 'literal',
'value' => substr($raw_pattern, $cursor, $token_len),
);
}
$cursor = $pos;
if ($cursor < $len) {
$tokens[] = array(
'type' => 'variable',
'value' => substr($raw_pattern, $cursor + 1, 1),
);
}
$cursor = $pos + 2;
}
return $tokens;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 12:40 AM (21 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431503
Default Alt Text
(28 KB)

Event Timeline