Changeset View
Changeset View
Standalone View
Standalone View
src/configuration/ArcanistConfiguration.php
Show First 20 Lines • Show All 138 Lines • ▼ Show 20 Lines | if (count($prefixes) == 1) { | ||||
return $this->buildWorkflow($command); | return $this->buildWorkflow($command); | ||||
} else if (count($prefixes) > 1) { | } else if (count($prefixes) > 1) { | ||||
$this->raiseUnknownCommand($command, $prefixes); | $this->raiseUnknownCommand($command, $prefixes); | ||||
} | } | ||||
// We haven't found a real command, alias, or unique prefix. Try similar | // We haven't found a real command, alias, or unique prefix. Try similar | ||||
// spellings. | // spellings. | ||||
$corrected = self::correctCommandSpelling($command, $all, 2); | $corrected = PhutilArgumentSpellingCorrector::newCommandCorrector() | ||||
->correctSpelling($command, $all); | |||||
if (count($corrected) == 1) { | if (count($corrected) == 1) { | ||||
$console->writeErr( | $console->writeErr( | ||||
pht( | pht( | ||||
"(Assuming '%s' is the British spelling of '%s'.)", | "(Assuming '%s' is the British spelling of '%s'.)", | ||||
$command, | $command, | ||||
head($corrected))."\n"); | head($corrected))."\n"); | ||||
$command = head($corrected); | $command = head($corrected); | ||||
return $this->buildWorkflow($command); | return $this->buildWorkflow($command); | ||||
Show All 22 Lines | foreach ($options as $option) { | ||||
if (strncmp($option, $command, strlen($command)) == 0) { | if (strncmp($option, $command, strlen($command)) == 0) { | ||||
$is_prefix[$option] = true; | $is_prefix[$option] = true; | ||||
} | } | ||||
} | } | ||||
return array_keys($is_prefix); | return array_keys($is_prefix); | ||||
} | } | ||||
public static function correctCommandSpelling( | |||||
$command, | |||||
array $options, | |||||
$max_distance) { | |||||
// Adjust to the scaled edit costs we use below, so "2" roughly means | |||||
// "2 edits". | |||||
$max_distance = $max_distance * 3; | |||||
// These costs are somewhat made up, but the theory is that it is far more | |||||
// likely you will mis-strike a key ("lans" for "land") or press two keys | |||||
// out of order ("alnd" for "land") than omit keys or press extra keys. | |||||
$matrix = id(new PhutilEditDistanceMatrix()) | |||||
->setInsertCost(4) | |||||
->setDeleteCost(4) | |||||
->setReplaceCost(3) | |||||
->setTransposeCost(2); | |||||
return self::correctSpelling($command, $options, $matrix, $max_distance); | |||||
} | |||||
public static function correctArgumentSpelling($command, array $options) { | |||||
$max_distance = 1; | |||||
// We are stricter with arguments - we allow only one inserted or deleted | |||||
// character. It is mainly to handle cases like --no-lint versus --nolint | |||||
// or --reviewer versus --reviewers. | |||||
$matrix = id(new PhutilEditDistanceMatrix()) | |||||
->setInsertCost(1) | |||||
->setDeleteCost(1) | |||||
->setReplaceCost(10); | |||||
return self::correctSpelling($command, $options, $matrix, $max_distance); | |||||
} | |||||
public static function correctSpelling( | |||||
$input, | |||||
array $options, | |||||
PhutilEditDistanceMatrix $matrix, | |||||
$max_distance) { | |||||
$distances = array(); | |||||
$inputv = str_split($input); | |||||
foreach ($options as $option) { | |||||
$optionv = str_split($option); | |||||
$matrix->setSequences($optionv, $inputv); | |||||
$distances[$option] = $matrix->getEditDistance(); | |||||
} | |||||
asort($distances); | |||||
$best = min($max_distance, reset($distances)); | |||||
foreach ($distances as $option => $distance) { | |||||
if ($distance > $best) { | |||||
unset($distances[$option]); | |||||
} | |||||
} | |||||
// Before filtering, check if we have multiple equidistant matches and | |||||
// return them if we do. This prevents us from, e.g., matching "alnd" with | |||||
// both "land" and "amend", then dropping "land" for being too short, and | |||||
// incorrectly completing to "amend". | |||||
if (count($distances) > 1) { | |||||
return array_keys($distances); | |||||
} | |||||
foreach ($distances as $option => $distance) { | |||||
if (strlen($option) < $distance) { | |||||
unset($distances[$option]); | |||||
} | |||||
} | |||||
return array_keys($distances); | |||||
} | |||||
} | } |