Skip to content

Commit

Permalink
Merge pull request #8 from lordfanger/master
Browse files Browse the repository at this point in the history
Incremental shifts feature + tests + fixes
[release]
  • Loading branch information
madskristensen authored Dec 9, 2022
2 parents 3cf7d56 + 2c5e6c4 commit ab15091
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 30 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ Shifter shifts the text under the caret depending on the text. For example, if t

The value being shifted is either the text where the caret is placed, or the selected text range.

![Shifter](art/multiple_carets.gif)

If using multiple carets, each selected text will be shifted separately.

## Keyboard shortcuts

* Shift up: **Ctrl+Alt+Up**
* Shift down: **Ctrl+Alt+Down**
* Incremental Shift up: **Shift+Ctrl+Alt+Up**
* Incremental Shift down: **Shift+Ctrl+Alt+Down**

## Supported shifts

**Numbers**, including decimals, will increase or decrease with a value that depends on the number of decimals.
**Numbers**, including decimals, will increase or decrease with a value that depends on the number of decimals (supports incremental shift).

**Hex colors** will either brighten or darken slightly when shifted.

Expand All @@ -34,6 +40,12 @@ The value being shifted is either the text where the caret is placed, or the sel

**Boolean** values such as `true/false`, `on/off`, `yes/no` will shift to the opposite regardless of shifting direction.

## Incremental shifts

![Shifter](art/incremental_shifter.gif)

With incremental shift each selection will be shifted incrementally in order from top/left to bottom/right.

### How can I help?
If you enjoy using the extension, please give it a ★★★★★ rating on the [Visual Studio Marketplace][marketplace].

Expand Down
Binary file added art/incremental_shifter.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added art/multiple_carets.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/Commands/IncrementalShiftDownCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Shifter.Providers;

namespace Shifter
{
[Command(PackageIds.IncrementalShiftDown)]
internal sealed class IncrementalShiftDownCommand : BaseCommand<IncrementalShiftDownCommand>
{
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();

if (docView?.TextView == null)
{
return;
}

ShiftDownCommand.Shift(docView, ShiftDirection.Down, true);
}
}
}
20 changes: 20 additions & 0 deletions src/Commands/IncrementalShiftUpCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Shifter.Providers;

namespace Shifter
{
[Command(PackageIds.IncrementalShiftUp)]
internal sealed class IncrementalShiftUpCommand : BaseCommand<IncrementalShiftUpCommand>
{
protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
{
DocumentView docView = await VS.Documents.GetActiveDocumentViewAsync();

if (docView?.TextView == null)
{
return;
}

ShiftDownCommand.Shift(docView, ShiftDirection.Up, true);
}
}
}
8 changes: 4 additions & 4 deletions src/Commands/ShiftDownCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
return;
}

Shift(docView, ShiftDirection.Down);
Shift(docView, ShiftDirection.Down, false);
}

public static void Shift(DocumentView docView, ShiftDirection direction)
public static void Shift(DocumentView docView, ShiftDirection direction, bool incremental)
{
IWpfTextView textView = docView.TextView!;
IMultiSelectionBroker multiSelectionBroker = textView.GetMultiSelectionBroker();
Expand Down Expand Up @@ -63,7 +63,7 @@ public static void Shift(DocumentView docView, ShiftDirection direction)
int selectionCaret = end - selection.InsertionPoint.Position.Position;

// Try to shift with each selection
if (ShiftEngine.TryShift(text, selectionCaret, direction, out ShiftResult shiftResult))
if (ShiftEngine.TryShift(text, selectionCaret, direction, incremental ? i : null, out ShiftResult shiftResult))
{
shiftResults[i] = shiftResult;
}
Expand Down Expand Up @@ -124,7 +124,7 @@ public static void Shift(DocumentView docView, ShiftDirection direction)
string text = selection.GetText();
int start = caretPosition - selection.Start;

if (ShiftEngine.TryShift(text, start, direction, out ShiftResult result))
if (ShiftEngine.TryShift(text, start, direction, null, out ShiftResult result))
{
// Update text buffer
Span span = new(result.Start + selection.Start, result.Length);
Expand Down
2 changes: 1 addition & 1 deletion src/Commands/ShiftUpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e)
return;
}

ShiftDownCommand.Shift(docView, ShiftDirection.Up);
ShiftDownCommand.Shift(docView, ShiftDirection.Up, false);
}
}
}
7 changes: 7 additions & 0 deletions src/Providers/IIncrementalProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Shifter.Providers
{
public interface IIncrementalProvider : IProvider
{
bool TryShiftLine(string textIn, int caretPosition, ShiftDirection direction, int sequence, out ShiftResult result);
}
}
64 changes: 47 additions & 17 deletions src/Providers/Implementations/NumberProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

namespace Shifter.Providers
{
public class NumberProvider : RegexProviderBase
public class NumberProvider : IncrementalRegexProviderBase
{
private static readonly Regex _regex = new(@"[-+]?\d*\.?\d+\b", RegexOptions.Compiled);

public override Regex Regex => _regex;

public override bool TryShiftSelection(Match match, ShiftDirection direction, out ShiftResult result)
public override bool TryShiftSelection(Match match, ShiftDirection direction, int sequence, out ShiftResult result)
{
result = null;

Expand All @@ -21,40 +21,70 @@ public override bool TryShiftSelection(Match match, ShiftDirection direction, ou
int dot = match.Value.IndexOf('.');
string format = dot > -1 ? "#.#0" : string.Empty;
int decimalPlaces = NumberDecimalPlaces(match.Value);
float delta = GetDelta(decimalPlaces);
float delta = GetDelta(decimalPlaces, sequence);

if (decimalPlaces > 0)
{
format = "F" + decimalPlaces;
}

string shiftedText;

if (direction == ShiftDirection.Down)
{
shiftedText = (value - delta).ToString(format, CultureInfo.InvariantCulture);
}
else
{
shiftedText = (value + delta).ToString(format, CultureInfo.InvariantCulture);
}
float shiftedValue = direction == ShiftDirection.Down
? value - delta
: value + delta;

string shiftedText = shiftedValue.ToString(format, CultureInfo.InvariantCulture); ;
if (dot == 0 && shiftedText.Length > 1)
{
shiftedText = shiftedText.Substring(1);
}

// Keep leading zeros
if (match.Value[0] == '0')
if (KeepLeadingZeros(match.Value, value))
{
shiftedText = shiftedText.PadLeft(match.Value.Length, '0');
int padLength = GetPadLength(match.Value, value, shiftedValue);
shiftedText = shiftedText.PadLeft(padLength, '0');

if (shiftedValue < 0)
{
shiftedText = $"-{shiftedText.Replace("-", "")}";
}
}

result = new ShiftResult(match.Index, match.Length, shiftedText);
return true;

static bool KeepLeadingZeros(string value, float numValue)
{
// 0.1
if (value[0] == '0' && value.Length > 1 && value[1] == '.') return false;
// -0.1
if (value[0] == '-' && value.Length > 2 && value[1] == '0' && value[2] == '.') return false;
// 0123
if (value[0] == '0' && numValue != 0) return true;
// -001
if (value[0] == '-' && value.Length > 1 && value[1] == '0') return true;
// 0000
if (value.Length > 1 && numValue == 0 && !value.Contains(".")) return true;

return false;
}

static int GetPadLength(string textValue, float oldValue, float newValue)
{
int textLength = textValue.Length;
bool oldNegative = oldValue < 0;
bool newNegative = newValue < 0;
return (oldNegative, newNegative) switch
{
(true, true) => textLength,
(true, false) => textLength - 1,
(false, true) => textLength + 1,
(false, false) => textLength,
};
}
}

private static float GetDelta(int decimalPlaces)
private static float GetDelta(int decimalPlaces, int sequence)
{
return decimalPlaces switch
{
Expand All @@ -69,7 +99,7 @@ private static float GetDelta(int decimalPlaces)
8 => 0.00000001F,
9 => 0.000000001F,
_ => 0.0000000001F,
};
} * (1 + sequence);
}

private static int NumberDecimalPlaces(string value)
Expand Down
36 changes: 36 additions & 0 deletions src/Providers/IncrementalRegexProvderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.RegularExpressions;

namespace Shifter.Providers
{
public abstract class IncrementalRegexProviderBase : RegexProviderBase, IIncrementalProvider
{
/// <inheritdoc />
public bool TryShiftLine(string textIn, int caretPosition, ShiftDirection direction, int sequence, out ShiftResult result)
{
result = null;
MatchCollection matches = Regex.Matches(textIn);

if (matches.Count == 0)
{
return false;
}

foreach (Match match in matches)
{
int start = match.Index;
int end = match.Index + match.Length;

if (caretPosition >= start && caretPosition <= end)
{
return TryShiftSelection(match, direction, sequence, out result);
}
}

return false;
}

public override bool TryShiftSelection(Match match, ShiftDirection direction, out ShiftResult result) => TryShiftSelection(match, direction, 0, out result);

public abstract bool TryShiftSelection(Match match, ShiftDirection direction, int sequence, out ShiftResult result);
}
}
28 changes: 24 additions & 4 deletions src/ShiftEngine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;

namespace Shifter.Providers
{
Expand All @@ -13,15 +14,34 @@ public class ShiftEngine
new BooleanProvider(),
};

public static bool TryShift(string textIn, int caretPosition, ShiftDirection direction, out ShiftResult result)
public static bool TryShift(string textIn, int caretPosition, ShiftDirection direction, int? sequence, out ShiftResult result)
{
result = null;

foreach (IProvider provider in _providers)
bool isIncremental = sequence.HasValue;
if (isIncremental)
{
if (provider.TryShiftLine(textIn, caretPosition, direction, out result))
// Try shift only using incremental providers
IEnumerable<IIncrementalProvider> incrementalProviders = _providers
.Select(static provider => provider as IIncrementalProvider)
.Where(static provider => provider != null);
foreach (IIncrementalProvider incrementalProvider in incrementalProviders)
{
return true;
if (incrementalProvider.TryShiftLine(textIn, caretPosition, direction, sequence.Value, out result))
{
return true;
}

}
}
else
{
foreach (IProvider provider in _providers)
{
if (provider.TryShiftLine(textIn, caretPosition, direction, out result))
{
return true;
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Shifter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Commands\IncrementalShiftDownCommand.cs" />
<Compile Include="Commands\IncrementalShiftUpCommand.cs" />
<Compile Include="Commands\ShiftUpCommand.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Commands\ShiftDownCommand.cs" />
<Compile Include="Providers\Implementations\BooleanProvider.cs" />
<Compile Include="Providers\Implementations\ColorHexProvider.cs" />
<Compile Include="Providers\Implementations\GuidProvider.cs" />
<Compile Include="Providers\IncrementalRegexProvderBase.cs" />
<Compile Include="Providers\IProvider.cs" />
<Compile Include="Providers\IIncrementalProvider.cs" />
<Compile Include="Providers\Implementations\ColorNamesProvider.cs" />
<Compile Include="Providers\RegexProviderBase.cs" />
<Compile Include="Providers\Implementations\NumberProvider.cs" />
Expand Down
2 changes: 2 additions & 0 deletions src/VSCommandTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ internal sealed partial class PackageIds
{
public const int ShiftDown = 0x0100;
public const int ShiftUp = 0x0200;
public const int IncrementalShiftDown = 0x0300;
public const int IncrementalShiftUp = 0x0400;
}
}
24 changes: 24 additions & 0 deletions src/VSCommandTable.vsct
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,47 @@
<LocCanonicalName>.Shifter.ShiftUp</LocCanonicalName>
</Strings>
</Button>

<Button guid="Shifter" id="IncrementalShiftDown" priority="0x0100" type="Button">
<CommandFlag>CommandWellOnly</CommandFlag>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Incremental Shift Down</ButtonText>
<LocCanonicalName>.Shifter.IncrementalShiftDown</LocCanonicalName>
</Strings>
</Button>

<Button guid="Shifter" id="IncrementalShiftUp" priority="0x0100" type="Button">
<CommandFlag>CommandWellOnly</CommandFlag>
<CommandFlag>DynamicVisibility</CommandFlag>
<Strings>
<ButtonText>Incremental Shift Up</ButtonText>
<LocCanonicalName>.Shifter.IncrementalShiftUp</LocCanonicalName>
</Strings>
</Button>
</Buttons>
</Commands>

<KeyBindings>
<KeyBinding guid="Shifter" id="ShiftDown" editor="GUID_TextEditorFactory" key1="VK_DOWN" mod1="Control Alt" />
<KeyBinding guid="Shifter" id="ShiftUp" editor="GUID_TextEditorFactory" key1="VK_UP" mod1="Control Alt" />
<KeyBinding guid="Shifter" id="IncrementalShiftDown" editor="GUID_TextEditorFactory" key1="VK_DOWN" mod1="Shift Control Alt" />
<KeyBinding guid="Shifter" id="IncrementalShiftUp" editor="GUID_TextEditorFactory" key1="VK_UP" mod1="Shift Control Alt" />
</KeyBindings>

<VisibilityConstraints>
<VisibilityItem guid="Shifter" id="ShiftDown" context="GUID_TextEditorFactory" />
<VisibilityItem guid="Shifter" id="ShiftUp" context="GUID_TextEditorFactory" />
<VisibilityItem guid="Shifter" id="IncrementalShiftDown" context="GUID_TextEditorFactory" />
<VisibilityItem guid="Shifter" id="IncrementalShiftUp" context="GUID_TextEditorFactory" />
</VisibilityConstraints>

<Symbols>
<GuidSymbol name="Shifter" value="{a3dc2100-ffab-436a-a3d6-96b5be081484}">
<IDSymbol name="ShiftDown" value="0x0100" />
<IDSymbol name="ShiftUp" value="0x0200" />
<IDSymbol name="IncrementalShiftDown" value="0x0300" />
<IDSymbol name="IncrementalShiftUp" value="0x0400" />
</GuidSymbol>
</Symbols>
</CommandTable>
Loading

0 comments on commit ab15091

Please sign in to comment.