mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
feature<RevisionFiles>: enable to search file in revision files
This commit is contained in:
parent
b452456d9d
commit
acc9840830
2 changed files with 159 additions and 94 deletions
|
@ -21,39 +21,77 @@
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Border Grid.Column="0" Background="{DynamicResource Brush.Contents}" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1">
|
<Grid Grid.Column="0">
|
||||||
<controls:Tree x:Name="treeFiles" SelectionChanged="OnFilesSelectionChanged" ContextMenuOpening="OnFilesContextMenuOpening">
|
<Grid.RowDefinitions>
|
||||||
<controls:Tree.ItemTemplate>
|
<RowDefinition Height="Auto"/>
|
||||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
<RowDefinition Height="*"/>
|
||||||
<StackPanel Orientation="Horizontal" Height="24">
|
</Grid.RowDefinitions>
|
||||||
<Path x:Name="Icon" Width="14" Height="14" Data="{StaticResource Icon.File}"/>
|
|
||||||
<TextBlock Margin="6,0,0,0" Text="{Binding Path, Converter={StaticResource PureFileName}}"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<HierarchicalDataTemplate.Triggers>
|
<Grid Grid.Row="0">
|
||||||
<DataTrigger Binding="{Binding IsFolder}" Value="True">
|
<Grid.ColumnDefinitions>
|
||||||
<Setter TargetName="Icon" Property="Fill" Value="Goldenrod"/>
|
<ColumnDefinition Width="24"/>
|
||||||
<Setter TargetName="Icon" Property="Opacity" Value="1"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</DataTrigger>
|
</Grid.ColumnDefinitions>
|
||||||
<MultiDataTrigger>
|
|
||||||
<MultiDataTrigger.Conditions>
|
<Border
|
||||||
<Condition Binding="{Binding IsFolder}" Value="True"/>
|
Grid.Column="0" Grid.ColumnSpan="2"
|
||||||
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="False"/>
|
BorderBrush="{DynamicResource Brush.Border2}"
|
||||||
</MultiDataTrigger.Conditions>
|
BorderThickness="1"/>
|
||||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
<Path
|
||||||
</MultiDataTrigger>
|
Grid.Column="0"
|
||||||
<MultiDataTrigger>
|
Width="14" Height="14"
|
||||||
<MultiDataTrigger.Conditions>
|
Fill="{DynamicResource Brush.FG2}"
|
||||||
<Condition Binding="{Binding IsFolder}" Value="True"/>
|
Data="{StaticResource Icon.Search}"
|
||||||
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="True"/>
|
IsHitTestVisible="False"/>
|
||||||
</MultiDataTrigger.Conditions>
|
<controls:TextEdit
|
||||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
Grid.Column="1"
|
||||||
</MultiDataTrigger>
|
Height="24"
|
||||||
</HierarchicalDataTemplate.Triggers>
|
Margin="0"
|
||||||
</HierarchicalDataTemplate>
|
Placeholder="{DynamicResource Text.CommitViewer.Changes.Search}"
|
||||||
</controls:Tree.ItemTemplate>
|
BorderThickness="0"
|
||||||
</controls:Tree>
|
TextChanged="OnSearchFilterChanged"/>
|
||||||
</Border>
|
</Grid>
|
||||||
|
|
||||||
|
<Border Grid.Row="1" Margin="0,4,0,0" Background="{DynamicResource Brush.Contents}" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="1">
|
||||||
|
<controls:Tree x:Name="treeFiles" SelectionChanged="OnFilesSelectionChanged">
|
||||||
|
<controls:Tree.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type controls:TreeItem}" BasedOn="{StaticResource Style.TreeItem}">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||||
|
<EventSetter Event="ContextMenuOpening" Handler="OnFilesContextMenuOpening"/>
|
||||||
|
</Style>
|
||||||
|
</controls:Tree.ItemContainerStyle>
|
||||||
|
<controls:Tree.ItemTemplate>
|
||||||
|
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||||
|
<StackPanel Orientation="Horizontal" Height="24">
|
||||||
|
<Path x:Name="Icon" Width="14" Height="14" Data="{StaticResource Icon.File}"/>
|
||||||
|
<TextBlock Margin="6,0,0,0" Text="{Binding Path, Converter={StaticResource PureFileName}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<HierarchicalDataTemplate.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsFolder}" Value="True">
|
||||||
|
<Setter TargetName="Icon" Property="Fill" Value="Goldenrod"/>
|
||||||
|
<Setter TargetName="Icon" Property="Opacity" Value="1"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsFolder}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="False"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsFolder}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="True"/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</HierarchicalDataTemplate.Triggers>
|
||||||
|
</HierarchicalDataTemplate>
|
||||||
|
</controls:Tree.ItemTemplate>
|
||||||
|
</controls:Tree>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" Background="Transparent"/>
|
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ namespace SourceGit.Views.Widgets {
|
||||||
private string repo = null;
|
private string repo = null;
|
||||||
private string sha = null;
|
private string sha = null;
|
||||||
private bool isLFSEnabled = false;
|
private bool isLFSEnabled = false;
|
||||||
|
private List<Models.Object> cached = new List<Models.Object>();
|
||||||
|
private string filter = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件列表树节点
|
/// 文件列表树节点
|
||||||
|
@ -26,6 +28,7 @@ namespace SourceGit.Views.Widgets {
|
||||||
public Models.ObjectType Type { get; set; } = Models.ObjectType.None;
|
public Models.ObjectType Type { get; set; } = Models.ObjectType.None;
|
||||||
public string Path { get; set; } = "";
|
public string Path { get; set; } = "";
|
||||||
public string SHA { get; set; } = null;
|
public string SHA { get; set; } = null;
|
||||||
|
public bool IsExpanded { get; set; } = false;
|
||||||
public bool IsFolder => Type == Models.ObjectType.None;
|
public bool IsFolder => Type == Models.ObjectType.None;
|
||||||
public List<FileNode> Children { get; set; } = new List<FileNode>();
|
public List<FileNode> Children { get; set; } = new List<FileNode>();
|
||||||
}
|
}
|
||||||
|
@ -44,72 +47,89 @@ namespace SourceGit.Views.Widgets {
|
||||||
var objects = cmd.Result();
|
var objects = cmd.Result();
|
||||||
if (cmd.Ctx.IsCancelRequested) return;
|
if (cmd.Ctx.IsCancelRequested) return;
|
||||||
|
|
||||||
var nodes = new List<FileNode>();
|
cached = objects;
|
||||||
var folders = new Dictionary<string, FileNode>();
|
ShowVisibles();
|
||||||
|
|
||||||
foreach (var obj in objects) {
|
|
||||||
var sepIdx = obj.Path.IndexOf('/');
|
|
||||||
if (sepIdx == -1) {
|
|
||||||
nodes.Add(new FileNode() {
|
|
||||||
Type = obj.Type,
|
|
||||||
Path = obj.Path,
|
|
||||||
SHA = obj.SHA,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
FileNode lastFolder = null;
|
|
||||||
var start = 0;
|
|
||||||
|
|
||||||
while (sepIdx != -1) {
|
|
||||||
var folder = obj.Path.Substring(0, sepIdx);
|
|
||||||
if (folders.ContainsKey(folder)) {
|
|
||||||
lastFolder = folders[folder];
|
|
||||||
} else if (lastFolder == null) {
|
|
||||||
lastFolder = new FileNode() {
|
|
||||||
Type = Models.ObjectType.None,
|
|
||||||
Path = folder,
|
|
||||||
SHA = null,
|
|
||||||
};
|
|
||||||
nodes.Add(lastFolder);
|
|
||||||
folders.Add(folder, lastFolder);
|
|
||||||
} else {
|
|
||||||
var cur = new FileNode() {
|
|
||||||
Type = Models.ObjectType.None,
|
|
||||||
Path = folder,
|
|
||||||
SHA = null,
|
|
||||||
};
|
|
||||||
folders.Add(folder, cur);
|
|
||||||
lastFolder.Children.Add(cur);
|
|
||||||
lastFolder = cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
start = sepIdx + 1;
|
|
||||||
sepIdx = obj.Path.IndexOf('/', start);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFolder.Children.Add(new FileNode() {
|
|
||||||
Type = obj.Type,
|
|
||||||
Path = obj.Path,
|
|
||||||
SHA = obj.SHA,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.Path = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
folders.Clear();
|
|
||||||
objects.Clear();
|
|
||||||
|
|
||||||
SortFileNodes(nodes);
|
|
||||||
|
|
||||||
Dispatcher.Invoke(() => {
|
|
||||||
treeFiles.ItemsSource = nodes;
|
|
||||||
GC.Collect();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup() {
|
public void Cleanup() {
|
||||||
treeFiles.ItemsSource = new List<FileNode>();
|
treeFiles.ItemsSource = new List<FileNode>();
|
||||||
|
cached = new List<Models.Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowVisibles() {
|
||||||
|
var nodes = new List<FileNode>();
|
||||||
|
var folders = new Dictionary<string, FileNode>();
|
||||||
|
var visibles = new List<Models.Object>();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(filter)) {
|
||||||
|
visibles.AddRange(cached);
|
||||||
|
} else {
|
||||||
|
foreach (var obj in cached) {
|
||||||
|
if (obj.Path.ToUpper().Contains(filter)) visibles.Add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expanded = visibles.Count <= 50;
|
||||||
|
|
||||||
|
foreach (var obj in visibles) {
|
||||||
|
var sepIdx = obj.Path.IndexOf('/');
|
||||||
|
if (sepIdx == -1) {
|
||||||
|
nodes.Add(new FileNode() {
|
||||||
|
Type = obj.Type,
|
||||||
|
Path = obj.Path,
|
||||||
|
SHA = obj.SHA,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
FileNode lastFolder = null;
|
||||||
|
var start = 0;
|
||||||
|
|
||||||
|
while (sepIdx != -1) {
|
||||||
|
var folder = obj.Path.Substring(0, sepIdx);
|
||||||
|
if (folders.ContainsKey(folder)) {
|
||||||
|
lastFolder = folders[folder];
|
||||||
|
} else if (lastFolder == null) {
|
||||||
|
lastFolder = new FileNode() {
|
||||||
|
Type = Models.ObjectType.None,
|
||||||
|
Path = folder,
|
||||||
|
SHA = null,
|
||||||
|
IsExpanded = expanded,
|
||||||
|
};
|
||||||
|
nodes.Add(lastFolder);
|
||||||
|
folders.Add(folder, lastFolder);
|
||||||
|
} else {
|
||||||
|
var cur = new FileNode() {
|
||||||
|
Type = Models.ObjectType.None,
|
||||||
|
Path = folder,
|
||||||
|
SHA = null,
|
||||||
|
IsExpanded = expanded,
|
||||||
|
};
|
||||||
|
folders.Add(folder, cur);
|
||||||
|
lastFolder.Children.Add(cur);
|
||||||
|
lastFolder = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = sepIdx + 1;
|
||||||
|
sepIdx = obj.Path.IndexOf('/', start);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFolder.Children.Add(new FileNode() {
|
||||||
|
Type = obj.Type,
|
||||||
|
Path = obj.Path,
|
||||||
|
SHA = obj.SHA,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folders.Clear();
|
||||||
|
visibles.Clear();
|
||||||
|
|
||||||
|
SortFileNodes(nodes);
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() => {
|
||||||
|
treeFiles.ItemsSource = nodes;
|
||||||
|
GC.Collect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortFileNodes(List<FileNode> nodes) {
|
private void SortFileNodes(List<FileNode> nodes) {
|
||||||
|
@ -259,7 +279,7 @@ namespace SourceGit.Views.Widgets {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFilesContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
private void OnFilesContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
||||||
var item = treeFiles.FindItem(e.OriginalSource as DependencyObject);
|
var item = sender as Controls.TreeItem;
|
||||||
if (item == null) return;
|
if (item == null) return;
|
||||||
|
|
||||||
var node = item.DataContext as FileNode;
|
var node = item.DataContext as FileNode;
|
||||||
|
@ -322,6 +342,13 @@ namespace SourceGit.Views.Widgets {
|
||||||
private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSearchFilterChanged(object sender, TextChangedEventArgs e) {
|
||||||
|
var edit = sender as Controls.TextEdit;
|
||||||
|
filter = edit.Text.ToUpper();
|
||||||
|
Task.Run(() => ShowVisibles());
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue