├── src ├── CodeIndex.Server │ ├── Pages │ │ ├── Lucene.razor │ │ ├── LuceneWithLineNumber.razor │ │ ├── Error.razor │ │ ├── About.razor │ │ ├── _Host.cshtml │ │ ├── TokenTest.razor │ │ ├── Logs.razor │ │ ├── Login.razor │ │ └── SearchDetails.razor │ ├── wwwroot │ │ ├── favicon.ico │ │ ├── css │ │ │ └── open-iconic │ │ │ │ ├── font │ │ │ │ └── fonts │ │ │ │ │ ├── open-iconic.eot │ │ │ │ │ ├── open-iconic.otf │ │ │ │ │ ├── open-iconic.ttf │ │ │ │ │ └── open-iconic.woff │ │ │ │ ├── ICON-LICENSE │ │ │ │ ├── README.md │ │ │ │ └── FONT-LICENSE │ │ └── js │ │ │ └── site.js │ ├── appsettings.Production.json │ ├── appsettings.Development.json │ ├── .config │ │ └── dotnet-tools.json │ ├── Shared │ │ ├── MainLayout.razor │ │ ├── NavMenu.razor │ │ ├── SearchingWithFile.razor │ │ └── SearchingWithLineNumber.razor │ ├── App.razor │ ├── appsettings.json │ ├── Data │ │ ├── ClientLoginModel.cs │ │ ├── CaptchaUtils.cs │ │ └── CaptchaImageUtils.cs │ ├── _Imports.razor │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Utils │ │ └── RazorPageUtils.cs │ ├── nlog.config │ ├── Dockerfile │ └── CodeIndex.Server.csproj ├── CodeIndex.Common │ ├── Status.cs │ ├── CodeWord.cs │ ├── FetchResult.cs │ ├── UserInfo.cs │ ├── IndexStatus.cs │ ├── CodeIndex.Common.csproj │ ├── PendingRetrySource.cs │ ├── IndexStatusInfo.cs │ ├── Storage.cs │ ├── ChangedSource.cs │ ├── CodeSourceWithMatchedLine.cs │ ├── CodeIndexConfiguration.cs │ ├── ExtendMethods.cs │ ├── IndexConfigForView.cs │ ├── SearchRequest.cs │ ├── CodeSource.cs │ ├── ArgumentValidation.cs │ └── IndexConfig.cs ├── CodeIndex.VisualStudioExtension │ ├── Resources │ │ └── CodeIndexSearchWindowCommand.png │ ├── Models │ │ ├── HintWord.cs │ │ ├── BaseViewModel.cs │ │ ├── ConfigHelper.cs │ │ ├── Commands.cs │ │ └── UserSettingsManager.cs │ ├── CodeIndex.Settings.config │ ├── Connected Services │ │ ├── CodeIndexClient.cs │ │ └── CodeIndexService │ │ │ └── CodeIndexClient.nswag │ ├── Controls │ │ ├── SettingsWindow.xaml.cs │ │ └── CodeIndexSearchControl.xaml.cs │ ├── Converts │ │ ├── LocalizationHelper.cs │ │ ├── LocalizeConverter.cs │ │ └── StringToXamlConverter.cs │ ├── CodeIndexSearchWindowControl.xaml.cs │ ├── CodeIndexSearchWindowControl.xaml │ ├── CodeIndex.VisualStudioExtension.sln │ ├── CodeIndexSearchWindow.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Services │ │ └── LocalizationService.cs │ ├── source.extension.vsixmanifest │ ├── CodeIndex.VisualStudioExtensionPackage.cs │ ├── ProvideToolboxControlAttribute.cs │ └── CodeIndexSearchWindowCommand.cs ├── .editorconfig ├── CodeIndex.IndexBuilder │ ├── IndexBuildResults.cs │ ├── CodeTokenUtils │ │ ├── CodeContentProcessing.cs │ │ ├── SimpleSegToken.cs │ │ ├── CodeAnalyzer.cs │ │ └── CodeTokenizer.cs │ ├── Constants.cs │ ├── CodeIndex.IndexBuilder.csproj │ ├── ILucenePool.cs │ ├── ConfigIndexBuilder.cs │ ├── DocumentConverter.cs │ └── IndexBuilderHelper.cs ├── CodeIndex.Files │ ├── CodeIndex.Files.csproj │ ├── FilePathHelper.cs │ ├── FilesContentHelper.cs │ ├── FilesWatcherHelper.cs │ └── FilesFetcher.cs ├── .dockerignore ├── CodeIndex.MaintainIndex │ ├── CodeIndex.MaintainIndex.csproj │ ├── IndexMaintainerWrapper.cs │ └── ConfigIndexMaintainer.cs ├── CodeIndex.Search │ └── CodeIndex.Search.csproj ├── CodeIndex.Test │ ├── Common │ │ ├── CodeWordTest.cs │ │ ├── IndexStatusInfoTest.cs │ │ ├── UserInfoTest.cs │ │ ├── Diff_Match_PatchTest.cs │ │ ├── CodeSourceWithMatchedLineTest.cs │ │ ├── StorageTest.cs │ │ ├── FetchResultTest.cs │ │ ├── ChangedSourceTest.cs │ │ ├── PendingRetrySourceTest.cs │ │ ├── ExtendMethodsTest.cs │ │ ├── SearchRequestTest.cs │ │ ├── CodeIndexConfigurationTest.cs │ │ ├── CodeSourceTest.cs │ │ ├── ArgumentValidationTest.cs │ │ └── IndexConfigTest.cs │ ├── Files │ │ ├── FilePathHelperTest.cs │ │ ├── FilesFetcherTest.cs │ │ ├── FilesContentHelperTest.cs │ │ └── FilesWatcherHelperTest.cs │ ├── BaseTest.cs │ ├── Utils │ │ ├── SearcherForTest.cs │ │ └── DummyLog.cs │ ├── MaintainIndex │ │ ├── IndexMaintainerWrapperTest.cs │ │ └── ConfigIndexMaintainerTest.cs │ ├── CodeIndex.Test.csproj │ ├── BaseTestLight.cs │ ├── Search │ │ └── InitManagement.cs │ └── IndexBuilder │ │ ├── CodeAnalyzerTest.cs │ │ ├── DocumentConverterTest.cs │ │ └── ConfigIndexBuilderTest.cs └── CodeIndex.ConsoleApp │ ├── CodeIndex.ConsoleApp.csproj │ ├── nlog.config │ └── Program.cs ├── doc ├── Extension-Icon.png ├── SearchByFiles.gif ├── SearchByLines.gif ├── UseExtension.gif ├── PhaseQuerySearch.gif └── ConfigAndSearching.gif ├── .github └── workflows │ ├── dotnetcore.yml │ ├── publish.yml │ └── build-vsix.yml └── .gitattributes /src/CodeIndex.Server/Pages/Lucene.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | -------------------------------------------------------------------------------- /doc/Extension-Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/Extension-Icon.png -------------------------------------------------------------------------------- /doc/SearchByFiles.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/SearchByFiles.gif -------------------------------------------------------------------------------- /doc/SearchByLines.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/SearchByLines.gif -------------------------------------------------------------------------------- /doc/UseExtension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/UseExtension.gif -------------------------------------------------------------------------------- /doc/PhaseQuerySearch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/PhaseQuerySearch.gif -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/LuceneWithLineNumber.razor: -------------------------------------------------------------------------------- 1 | @page "/SearchWithLine" 2 | -------------------------------------------------------------------------------- /doc/ConfigAndSearching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/doc/ConfigAndSearching.gif -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.Server/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/CodeIndex.Server/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Debug" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /src/CodeIndex.Common/Status.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class Status 4 | { 5 | public bool Success { get; set; } 6 | public string StatusDesc { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Resources/CodeIndexSearchWindowCommand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuhaotc/CodeIndex/HEAD/src/CodeIndex.VisualStudioExtension/Resources/CodeIndexSearchWindowCommand.png -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Models/HintWord.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.VisualStudioExtension.Models 2 | { 3 | public class HintWord 4 | { 5 | public string Word { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/CodeWord.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class CodeWord 4 | { 5 | public string Word { get; set; } 6 | public string WordLower { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/FetchResult.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class FetchResult 4 | { 5 | public Status Status { get; set; } 6 | public T Result { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | # All files 6 | [*] 7 | indent_style = space -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndex.Settings.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/UserInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public record UserInfo 4 | { 5 | public int Id { get; set; } 6 | public string UserName { get; set; } 7 | public string Password { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/IndexBuildResults.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.IndexBuilder 2 | { 3 | public enum IndexBuildResults 4 | { 5 | Successful, 6 | FailedWithIOException, 7 | FailedWithoutError, 8 | FailedWithError 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "10.0.1", 7 | "commands": [ 8 | "dotnet-ef" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/CodeTokenUtils/CodeContentProcessing.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.IndexBuilder 2 | { 3 | public static class CodeContentProcessing 4 | { 5 | public const string HighLightPrefix = "0ffc7664bb0"; 6 | public const string HighLightSuffix = "b17f5526cc3"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/IndexStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public enum IndexStatus 4 | { 5 | Idle, 6 | Initializing, 7 | Initializing_ComponentInitializeFinished, 8 | Initialized, 9 | Monitoring, 10 | Error, 11 | Disposing, 12 | Disposed 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CodeIndex.Files/CodeIndex.Files.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 6 | 7 |
8 |
9 | About Code Index 10 |
11 | 12 |
13 | @Body 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Connected Services/CodeIndexClient.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.VisualStudioExtension 2 | { 3 | public partial class CodeIndexClient 4 | { 5 | public CodeIndexClient(System.Net.Http.HttpClient httpClient, string baseUrl) : this(httpClient) 6 | { 7 | BaseUrl = baseUrl; 8 | } 9 | 10 | public string BaseUrl { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/CodeIndex.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /src/CodeIndex.Common/PendingRetrySource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodeIndex.Common 4 | { 5 | public class PendingRetrySource : ChangedSource 6 | { 7 | public int RetryTimes { get; set; } 8 | public DateTime LastRetryUTCDate { get; set; } 9 | 10 | public override string ToString() 11 | { 12 | return $"{base.ToString()} {RetryTimes} {LastRetryUTCDate}"; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Controls/SettingsWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using CodeIndex.VisualStudioExtension.Models; 3 | 4 | namespace CodeIndex.VisualStudioExtension.Controls 5 | { 6 | public partial class SettingsWindow : Window 7 | { 8 | public SettingsWindow(SettingsViewModel vm) 9 | { 10 | InitializeComponent(); 11 | DataContext = vm; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CodeIndex.Files/FilePathHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace CodeIndex.Files 4 | { 5 | public static class FilePathHelper 6 | { 7 | public static string[] GetPaths(string[] paths, bool isInLinux) 8 | { 9 | return isInLinux ? paths?.Select(u => u.ToUpperInvariant().Replace('\\', '/')).ToArray() : paths?.Select(u => u.ToUpperInvariant().Replace('/', '\\')).ToArray(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Converts/LocalizationHelper.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.VisualStudioExtension.Services; 2 | 3 | namespace CodeIndex.VisualStudioExtension.Converts 4 | { 5 | /// 6 | /// Helper class to expose LocalizationService for XAML binding 7 | /// 8 | public static class LocalizationHelper 9 | { 10 | public static LocalizationService Instance => LocalizationService.Instance; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/IndexStatusInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class IndexStatusInfo 4 | { 5 | public IndexStatusInfo(IndexStatus indexStatus, IndexConfig indexConfig) 6 | { 7 | indexConfig.RequireNotNull(nameof(indexConfig)); 8 | IndexStatus = indexStatus; 9 | IndexConfig = indexConfig; 10 | } 11 | 12 | public IndexStatus IndexStatus { get; set; } 13 | public IndexConfig IndexConfig { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Sorry, there's nothing at this address.

9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/CodeIndex.Files/FilesContentHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace CodeIndex.Files 5 | { 6 | public static class FilesContentHelper 7 | { 8 | public static string ReadAllText(string fullName) 9 | { 10 | using var fileStream = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 11 | using var streamReader = new StreamReader(fileStream, Encoding.UTF8, true); 12 | 13 | return streamReader.ReadToEnd(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/CodeTokenUtils/SimpleSegToken.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.IndexBuilder 2 | { 3 | public class SimpleSegToken 4 | { 5 | public SimpleSegToken(char[] charArray, int startOffset, int endOffset) 6 | { 7 | CharArray = charArray; 8 | StartOffset = startOffset; 9 | EndOffset = endOffset; 10 | } 11 | 12 | public char[] CharArray { get; set; } 13 | public int StartOffset { get; set; } 14 | public int EndOffset { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "CodeIndex": { 11 | "LuceneIndex": "C:\\TestFolder\\Index", 12 | "IsInLinux": "false", 13 | "MaximumResults": 10000, 14 | "ManagerUsers": [ 15 | { 16 | "Id": 1, 17 | "UserName": "Admin", 18 | "Password": "CodeIndex" 19 | } 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/Constants.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Util; 2 | 3 | namespace CodeIndex 4 | { 5 | public class Constants 6 | { 7 | public const LuceneVersion AppLuceneVersion = LuceneVersion.LUCENE_48; 8 | 9 | public const int ReadWriteLockTimeOutMilliseconds = 60000; // 60 seconds 10 | 11 | public const string NoneTokenizeFieldSuffix = "NoneTokenize"; 12 | 13 | public const string CaseSensitive = "CaseSensitive"; 14 | 15 | public const int DefaultMaxContentHighlightLength = 3000000; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/CodeIndex.MaintainIndex/CodeIndex.MaintainIndex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/CodeIndex.Search/CodeIndex.Search.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Data/ClientLoginModel.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Server 2 | { 3 | public class ClientLoginModel 4 | { 5 | public string UserName { get; set; } 6 | public string Password { get; set; } 7 | public bool Persist { get; set; } 8 | public string Captcha { get; set; } 9 | public LoginStatus Status { get; set; } 10 | public string Message { get; set; } 11 | } 12 | 13 | public enum LoginStatus 14 | { 15 | Succesful, 16 | Failed, 17 | Exception 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/CodeWordTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class CodeWordTest 7 | { 8 | [Test] 9 | public void TestProperties() 10 | { 11 | var word = new CodeWord 12 | { 13 | Word = "ABc", 14 | WordLower = "abc" 15 | }; 16 | 17 | Assert.That(word.Word, Is.EqualTo("ABc")); 18 | Assert.That(word.WordLower, Is.EqualTo("abc")); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Authorization 4 | @using Microsoft.AspNetCore.Components.Authorization 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using Microsoft.Extensions.Configuration 9 | @using Microsoft.JSInterop 10 | @using CodeIndex.Server 11 | @using CodeIndex.Server.Shared 12 | @using CodeIndex.Search; 13 | @using CodeIndex.Common 14 | @using CodeIndex.MaintainIndex -------------------------------------------------------------------------------- /src/CodeIndex.Common/Storage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CodeIndex.Common 4 | { 5 | public class Storage 6 | { 7 | Dictionary Items { get; } = new Dictionary(); 8 | public string UserName { get; set; } 9 | 10 | public object GetValue(string key) 11 | { 12 | return Items.ContainsKey(key) ? Items[key] : null; 13 | } 14 | 15 | public void SetOrUpdate(string key, object value) 16 | { 17 | Items[key] = value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/ChangedSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CodeIndex.Common 5 | { 6 | public class ChangedSource 7 | { 8 | public string FilePath { get; set; } 9 | public string OldPath { get; set; } 10 | public WatcherChangeTypes ChangesType { get; set; } 11 | public DateTime ChangedUTCDate { get; set; } = DateTime.UtcNow; 12 | 13 | public override string ToString() 14 | { 15 | return $"Changed Source: {FilePath} {OldPath} {ChangesType} {ChangedUTCDate}"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/IndexStatusInfoTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | using System; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | class IndexStatusInfoTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | var statusInfo = new IndexStatusInfo(IndexStatus.Initializing, new IndexConfig()); 13 | Assert.That(statusInfo.IndexConfig, Is.Not.Null); 14 | Assert.That(statusInfo.IndexStatus, Is.EqualTo(IndexStatus.Initializing)); 15 | Assert.Throws(() => _ = new IndexStatusInfo(default, null)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/UserInfoTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class UserInfoTest 7 | { 8 | [Test] 9 | public void TestConstructor() 10 | { 11 | var userInfo = new UserInfo 12 | { 13 | Id = 1, 14 | Password = "ABC", 15 | UserName = "EDF" 16 | }; 17 | 18 | Assert.That(userInfo.Id, Is.EqualTo(1)); 19 | Assert.That(userInfo.Password, Is.EqualTo("ABC")); 20 | Assert.That(userInfo.UserName, Is.EqualTo("EDF")); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndexSearchWindowControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace CodeIndex.VisualStudioExtension 4 | { 5 | /// 6 | /// Interaction logic for CodeIndexSearchWindowControl. 7 | /// 8 | public partial class CodeIndexSearchWindowControl : UserControl 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public CodeIndexSearchWindowControl() 14 | { 15 | InitializeComponent(); 16 | 17 | SearchControl.DataContext = new CodeIndexSearchViewModel(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/Diff_Match_PatchTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class Diff_Match_PatchTest 8 | { 9 | [Test] 10 | public void TestDiff() 11 | { 12 | var content1 = "Today is a good day" + Environment.NewLine + "WooHoo"; 13 | var content2 = "Tomorrow is not, what a day" + Environment.NewLine + "WOW"; 14 | 15 | var dmp = new Diff_Match_Patch(); 16 | var differents = dmp.Diff_Main(content1, content2); 17 | dmp.Diff_CleanupSemantic(differents); 18 | Assert.That(differents.Count, Is.EqualTo(6)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/CodeSourceWithMatchedLine.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class CodeSourceWithMatchedLine 4 | { 5 | /// 6 | /// For Json Deserialize only 7 | /// 8 | public CodeSourceWithMatchedLine() {} 9 | 10 | public CodeSourceWithMatchedLine(CodeSource codeSource, int matchedLine, string matchedContent) 11 | { 12 | CodeSource = codeSource; 13 | MatchedLine = matchedLine; 14 | MatchedContent = matchedContent; 15 | } 16 | 17 | public CodeSource CodeSource { get; set; } 18 | public int MatchedLine { get; set; } 19 | public string MatchedContent { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/Error.razor: -------------------------------------------------------------------------------- 1 | @page "/error" 2 | 3 |

Error.

4 |

An error occurred while processing your request.

5 | 6 |

Development Mode

7 |

8 | Swapping to Development environment will display more detailed information about the error that occurred. 9 |

10 |

11 | The Development environment shouldn't be enabled for deployed applications. 12 | It can result in displaying sensitive information from exceptions to end users. 13 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 14 | and restarting the app. 15 |

-------------------------------------------------------------------------------- /src/CodeIndex.Test/Files/FilePathHelperTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Files; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class FilePathHelperTest 7 | { 8 | [Test] 9 | public void TestGetPaths() 10 | { 11 | var results = FilePathHelper.GetPaths(new[] { "\\BIN\\1.txt", "/home/etc" }, true); 12 | Assert.That(results, Is.EquivalentTo(new[] { "/BIN/1.TXT", "/HOME/ETC" })); 13 | 14 | results = FilePathHelper.GetPaths(new[] { "\\BIN\\1.txt", "/home/etc" }, false); 15 | Assert.That(results, Is.EquivalentTo(new[] { "\\BIN\\1.TXT", "\\HOME\\ETC" })); 16 | 17 | Assert.That(FilePathHelper.GetPaths(null, true), Is.Null); 18 | Assert.That(FilePathHelper.GetPaths(null, false), Is.Null); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/CodeSourceWithMatchedLineTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using Newtonsoft.Json; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class CodeSourceWithMatchedLineTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | var codeWithMatchedLine = new CodeSourceWithMatchedLine(new CodeSource() { Content = "ABD" }, 1, "ABC"); 13 | Assert.Multiple(() => 14 | { 15 | Assert.That(codeWithMatchedLine.MatchedLine, Is.EqualTo(1)); 16 | Assert.That(codeWithMatchedLine.MatchedContent, Is.EqualTo("ABC")); 17 | Assert.That(codeWithMatchedLine.CodeSource.Content, Is.EqualTo("ABD")); 18 | }); 19 | 20 | Assert.DoesNotThrow(() => new CodeSourceWithMatchedLine()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CodeIndex.Common; 4 | using CodeIndex.IndexBuilder; 5 | using CodeIndex.MaintainIndex; 6 | using Lucene.Net.QueryParsers.Classic; 7 | using NUnit.Framework; 8 | 9 | namespace CodeIndex.Test 10 | { 11 | public class BaseTest : BaseTestLight 12 | { 13 | public CodeIndexConfiguration Config => new CodeIndexConfiguration 14 | { 15 | LuceneIndex = TempIndexDir 16 | }; 17 | 18 | protected string TempConfigDir => Path.Combine(TempIndexDir, CodeIndexConfiguration.ConfigurationIndexFolder); 19 | 20 | [SetUp] 21 | protected override void SetUp() 22 | { 23 | base.SetUp(); 24 | } 25 | 26 | [TearDown] 27 | protected override void TearDown() 28 | { 29 | base.TearDown(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/CodeIndex.IndexBuilder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/StorageTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class StorageTest 7 | { 8 | [Test] 9 | public void TestConstruct() 10 | { 11 | var storage = new Storage 12 | { 13 | UserName = "ABC" 14 | }; 15 | 16 | Assert.That(storage.UserName, Is.EqualTo("ABC")); 17 | } 18 | 19 | [Test] 20 | public void TestGet_SetOrUpdateValue() 21 | { 22 | var storage = new Storage(); 23 | Assert.That(storage.GetValue("ABC"), Is.Null); 24 | 25 | storage.SetOrUpdate("ABC", 2); 26 | Assert.That(storage.GetValue("ABC"), Is.EqualTo(2)); 27 | 28 | storage.SetOrUpdate("ABC", "ABCD"); 29 | Assert.That(storage.GetValue("ABC"), Is.EqualTo("ABCD")); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/FetchResultTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CodeIndex.Common; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class FetchResultTest 8 | { 9 | [Test] 10 | public void TestConstruct() 11 | { 12 | var result = new FetchResult> 13 | { 14 | Status = new Status 15 | { 16 | StatusDesc = "ABC", 17 | Success = true 18 | }, 19 | Result = new List 20 | { 21 | "1", "2", "3" 22 | } 23 | }; 24 | 25 | Assert.That(result.Status.StatusDesc, Is.EqualTo("ABC")); 26 | Assert.That(result.Status.Success, Is.True); 27 | Assert.That(result.Result, Is.EqualTo(new[] { "1", "2", "3" })); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/ChangedSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class ChangedSourceTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | var source = new ChangedSource 13 | { 14 | ChangedUTCDate = new DateTime(2021,1,1), 15 | ChangesType = System.IO.WatcherChangeTypes.Renamed, 16 | FilePath = "A", 17 | OldPath = "B" 18 | }; 19 | 20 | // NUnit4 constraint syntax 21 | Assert.That(source.ChangedUTCDate, Is.EqualTo(new DateTime(2021, 1, 1))); 22 | Assert.That(source.ChangesType, Is.EqualTo(System.IO.WatcherChangeTypes.Renamed)); 23 | Assert.That(source.FilePath, Is.EqualTo("A")); 24 | Assert.That(source.OldPath, Is.EqualTo("B")); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Utils/SearcherForTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using CodeIndex.Common; 3 | using CodeIndex.IndexBuilder; 4 | using Lucene.Net.Search; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | public static class SearcherForTest 9 | { 10 | public static CodeSource[] SearchCode(this ILucenePool lucenePool, Query searchQuery, int maxResults = int.MaxValue) 11 | { 12 | return lucenePool.Search(searchQuery, maxResults).Select(CodeIndexBuilder.GetCodeSourceFromDocument).ToArray(); 13 | } 14 | 15 | public static CodeWord[] SearchWord(this ILucenePool lucenePool, Query searchQuery, int maxResults = int.MaxValue) 16 | { 17 | return lucenePool.Search(searchQuery, maxResults).Select(u => new CodeWord 18 | { 19 | Word = u.Get(nameof(CodeWord.Word)), 20 | WordLower = u.Get(nameof(CodeWord.WordLower)), 21 | }).ToArray(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using NLog; 4 | using NLog.Web; 5 | 6 | namespace CodeIndex.Server 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | try 13 | { 14 | CreateHostBuilder(args).Build().Run(); 15 | } 16 | finally 17 | { 18 | // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) 19 | LogManager.Shutdown(); 20 | } 21 | } 22 | 23 | public static IHostBuilder CreateHostBuilder(string[] args) => 24 | Host.CreateDefaultBuilder(args) 25 | .ConfigureWebHostDefaults(webBuilder => 26 | { 27 | webBuilder.UseStartup(); 28 | }) 29 | .UseNLog(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/CodeIndexConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace CodeIndex.Common 2 | { 3 | public class CodeIndexConfiguration 4 | { 5 | public const string CodeIndexesFolder = "CodeIndexes"; 6 | public const string ConfigurationIndexFolder = "Configuration"; 7 | public const string CodeIndexFolder = "CodeIndex"; 8 | public const string HintIndexFolder = "HintIndex"; 9 | 10 | public string LuceneIndex { get; set; } = string.Empty; 11 | public bool IsInLinux { get; set; } 12 | public string LocalUrl { get; set; } = string.Empty; 13 | public UserInfo[] ManagerUsers { get; set; } 14 | 15 | public int MaximumResults 16 | { 17 | get => maximumResults; 18 | set 19 | { 20 | value.RequireRange(nameof(maximumResults), 10000000, 100); 21 | 22 | maximumResults = value; 23 | } 24 | } 25 | 26 | int maximumResults = 10000; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/MaintainIndex/IndexMaintainerWrapperTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using CodeIndex.MaintainIndex; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class IndexMaintainerWrapperTest : BaseTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | using var wrapper = new IndexMaintainerWrapper(new IndexConfig 13 | { 14 | IndexName ="AAA" 15 | }, Config, Log); 16 | 17 | Assert.That(wrapper.Status, Is.EqualTo(IndexStatus.Idle)); 18 | Assert.That(wrapper.QueryGenerator, Is.Not.Null); 19 | Assert.That(wrapper.QueryParserNormal, Is.Not.Null); 20 | Assert.That(wrapper.QueryParserNormal.LowercaseExpandedTerms, Is.True); 21 | Assert.That(wrapper.QueryParserCaseSensitive, Is.Not.Null); 22 | Assert.That(wrapper.QueryParserCaseSensitive.LowercaseExpandedTerms, Is.False); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Data/CaptchaUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodeIndex.Server 4 | { 5 | public static class CaptchaUtils 6 | { 7 | static readonly char[] characters = new[] { '2', '3', '4', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; 8 | static readonly Random random = new Random(); 9 | 10 | public static string GenerateCaptcha(int width, int height, out byte[] captchaImages) 11 | { 12 | var captcha = string.Empty; 13 | 14 | for (var i = 0; i < 6; i++) 15 | { 16 | captcha += characters[random.Next(0, characters.Length)]; 17 | } 18 | 19 | captchaImages = CaptchaImageUtils.GenerateCaptchaImage(width, height, captcha, random); 20 | 21 | return captcha; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:14328", 7 | "sslPort": 44391 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "CodeIndex.Server": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | }, 26 | "Docker": { 27 | "commandName": "Docker", 28 | "launchBrowser": true, 29 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 30 | "publishAllPorts": true, 31 | "useSSL": true 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/About.razor: -------------------------------------------------------------------------------- 1 | @page "/About" 2 | 3 |
4 |
5 |
6 |
7 |
Source Code
8 |

Find in github

9 | Github 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Search Syntax
19 | 26 |
27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/CodeIndex.Common/ExtendMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodeIndex.Common 4 | { 5 | public static class ExtendMethods 6 | { 7 | public static string SubStringSafeWithEllipsis(this string str, int startIndex, int length, string ellipsis = "...") 8 | { 9 | var result = str.SubStringSafe(startIndex, length); 10 | return result.Length == str.Length ? result : (startIndex <= 0 ? result + ellipsis : ellipsis + result); 11 | } 12 | 13 | public static string SubStringSafe(this string str, int startIndex, int length) 14 | { 15 | var result = string.Empty; 16 | startIndex = startIndex >= 0 ? startIndex : 0; 17 | 18 | if (!string.IsNullOrEmpty(str)) 19 | { 20 | length = Math.Min(length, str.Length - startIndex); 21 | 22 | if (length > 0) 23 | { 24 | result = str.Substring(startIndex, length); 25 | } 26 | } 27 | 28 | return result; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/CodeIndex.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | false 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/CodeIndex.ConsoleApp/CodeIndex.ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net10.0 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Utils/RazorPageUtils.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using static CodeIndex.Common.IndexConfig; 3 | 4 | namespace CodeIndex.Server 5 | { 6 | public static class RazorPageUtils 7 | { 8 | public static string GetOpenIDEUri(string openIDEUriFormat, string filePath, string monitorFolderRealPath, int line = 0, int column = 0) 9 | { 10 | if (!string.IsNullOrWhiteSpace(openIDEUriFormat)) 11 | { 12 | return openIDEUriFormat.Replace(FilePathPlaceholder, GetPath(filePath, monitorFolderRealPath)).Replace(LinePlaceholder, line.ToString()).Replace(ColumnPlaceholder, column.ToString()); 13 | } 14 | 15 | return filePath; 16 | } 17 | 18 | static string GetPath(string path, string monitorFolderRealPath) 19 | { 20 | if (path != null && path.StartsWith("/monitorfolder") && !string.IsNullOrWhiteSpace(monitorFolderRealPath)) 21 | { 22 | return monitorFolderRealPath + path.SubStringSafe("/monitorfolder".Length, path.Length); 23 | } 24 | 25 | return path; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/IndexConfigForView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodeIndex.Common 4 | { 5 | public class IndexConfigForView 6 | { 7 | public string IndexName { get; set; } 8 | public int MaxContentHighlightLength { get; set; } 9 | public int SaveIntervalSeconds { get; set; } 10 | public string OpenIDEUriFormat { get; set; } 11 | public string MonitorFolderRealPath { get; set; } 12 | public Guid Pk { get; set; } 13 | 14 | public static IndexConfigForView GetIndexConfigForView(IndexConfig indexConfig) 15 | { 16 | indexConfig.RequireNotNull(nameof(indexConfig)); 17 | 18 | return new IndexConfigForView 19 | { 20 | MaxContentHighlightLength = indexConfig.MaxContentHighlightLength, 21 | SaveIntervalSeconds = indexConfig.SaveIntervalSeconds, 22 | OpenIDEUriFormat = indexConfig.OpenIDEUriFormat, 23 | MonitorFolderRealPath = indexConfig.MonitorFolderRealPath, 24 | IndexName = indexConfig.IndexName, 25 | Pk = indexConfig.Pk 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/PendingRetrySourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class PendingRetrySourceTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | var source = new PendingRetrySource 13 | { 14 | LastRetryUTCDate = new DateTime(2022,1,1), 15 | RetryTimes = 2, 16 | ChangedUTCDate = new DateTime(2021, 1, 1), 17 | ChangesType = System.IO.WatcherChangeTypes.Renamed, 18 | FilePath = "A", 19 | OldPath = "B" 20 | }; 21 | 22 | Assert.That(source.ChangedUTCDate, Is.EqualTo(new DateTime(2021, 1, 1))); 23 | Assert.That(source.ChangesType, Is.EqualTo(System.IO.WatcherChangeTypes.Renamed)); 24 | Assert.That(source.FilePath, Is.EqualTo("A")); 25 | Assert.That(source.OldPath, Is.EqualTo("B")); 26 | Assert.That(source.LastRetryUTCDate, Is.EqualTo(new DateTime(2022, 1, 1))); 27 | Assert.That(source.RetryTimes, Is.EqualTo(2)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/CodeIndex.Common/SearchRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace CodeIndex.Common 5 | { 6 | public record SearchRequest 7 | { 8 | public Guid IndexPk { get; set; } 9 | [MaxLength(1000)] 10 | public string Content { get; set; } 11 | [MaxLength(200)] 12 | public string FileName { get; set; } 13 | [MaxLength(20)] 14 | public string FileExtension { get; set; } 15 | [MaxLength(1000)] 16 | public string FilePath { get; set; } 17 | public bool CaseSensitive { get; set; } 18 | public bool PhaseQuery { get; set; } 19 | public int? ShowResults { get; set; } 20 | public bool Preview { get; set; } 21 | public bool NeedReplaceSuffixAndPrefix { get; set; } 22 | public bool ForWeb { get; set; } 23 | [MaxLength(32)] 24 | public string CodePK { get; set; } 25 | public bool IsEmpty => string.IsNullOrWhiteSpace(Content) && string.IsNullOrWhiteSpace(FileName) && string.IsNullOrWhiteSpace(FileExtension) && string.IsNullOrWhiteSpace(FilePath) && string.IsNullOrWhiteSpace(CodePK) 26 | || IndexPk == Guid.Empty; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Models/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.Shell; 6 | 7 | namespace CodeIndex.VisualStudioExtension 8 | { 9 | public class BaseViewModel : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler PropertyChanged; 12 | 13 | public void NotifyPropertyChange([CallerMemberName]string memberName = null) 14 | { 15 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName)); 16 | } 17 | 18 | public void NotifyPropertyChange(Func propertyName) 19 | { 20 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName.Invoke())); 21 | } 22 | 23 | /// 24 | /// 在 UI 线程执行委托(如果可能)。如果当前不在 UI 线程,会切换。 25 | /// 26 | public static async Task InvokeOnUIThreadAsync(Action action) 27 | { 28 | if (action == null) return; 29 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 30 | action(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndexSearchWindowControl.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/ILucenePool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Lucene.Net.Documents; 4 | using Lucene.Net.Index; 5 | using Lucene.Net.Search; 6 | 7 | namespace CodeIndex.IndexBuilder 8 | { 9 | public interface ILucenePool : IDisposable 10 | { 11 | void BuildIndex(IEnumerable documents, bool needCommit, bool triggerMerge = false, bool applyAllDeletes = false); 12 | 13 | Document[] Search(Query query, int maxResults, Filter filter = null); 14 | 15 | Document[] SearchWithSpecificFields(Query query, int maxResults, params string[] fieldsNeedToLoad); 16 | 17 | void DeleteIndex(params Query[] searchQueries); 18 | 19 | void DeleteIndex(params Term[] terms); 20 | 21 | void DeleteIndex(Term term, out Document[] documentsBeenDeleted); 22 | 23 | void DeleteIndex(Query query, out Document[] documentsBeenDeleted); 24 | 25 | void UpdateIndex(Term term, Document document); 26 | 27 | void UpdateIndex(Term term, Document document, out Document[] rawDocuments); 28 | 29 | void DeleteAllIndex(); 30 | 31 | void Commit(); 32 | 33 | bool Exists(Query query); 34 | 35 | string LuceneIndex { get; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/CodeSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CodeIndex.Common 5 | { 6 | public class CodeSource 7 | { 8 | public string FileName { get; set; } 9 | public string FileExtension { get; set; } 10 | public string FilePath { get; set; } 11 | public string Content { get; set; } 12 | public DateTime IndexDate { get; set; } 13 | public DateTime LastWriteTimeUtc { get; set; } 14 | public string CodePK { get; set; } = Guid.NewGuid().ToString("N"); 15 | public string Info => $"FileName: {FileName}{Environment.NewLine}FilePath: {FilePath}{Environment.NewLine}Index Date: {IndexDate.ToLocalTime()}{Environment.NewLine}Last Modify Date:{LastWriteTimeUtc.ToLocalTime()}"; 16 | 17 | public static CodeSource GetCodeSource(FileInfo fileInfo, string content) 18 | { 19 | return new CodeSource 20 | { 21 | FileExtension = fileInfo.Extension.Replace(".", string.Empty), 22 | FileName = fileInfo.Name, 23 | FilePath = fileInfo.FullName, 24 | IndexDate = DateTime.UtcNow, 25 | Content = content, 26 | LastWriteTimeUtc = fileInfo.LastWriteTimeUtc 27 | }; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Converts/LocalizeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using CodeIndex.VisualStudioExtension.Services; 5 | 6 | namespace CodeIndex.VisualStudioExtension.Converts 7 | { 8 | /// 9 | /// Converts a resource key to localized string for XAML binding. 10 | /// Usage: {Binding Converter={StaticResource LocalizeConverter}, ConverterParameter=ResourceKey} 11 | /// 12 | public class LocalizeConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (parameter is string key) 17 | { 18 | return LocalizationService.Instance.GetString(key); 19 | } 20 | 21 | // If value is the key (for direct binding scenarios) 22 | if (value is string keyValue) 23 | { 24 | return LocalizationService.Instance.GetString(keyValue); 25 | } 26 | 27 | return value; 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace CodeIndex.Server.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = null; 6 | } 7 | 8 | 9 | 10 | 11 | 12 | 13 | Code Index Server 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | An error has occurred. This application may no longer respond until reloaded. 26 | 27 | 28 | An unhandled exception has occurred. See browser dev tools for details. 29 | 30 | Reload 31 | 🗙 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/CodeIndex.ConsoleApp/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/ExtendMethodsTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class ExtendMethodsTest 7 | { 8 | [Test] 9 | public void TestSubStringSafe() 10 | { 11 | Assert.That("123".SubStringSafe(3, 4), Is.Empty); 12 | Assert.That("123".SubStringSafe(5, 1), Is.Empty); 13 | Assert.That("123".SubStringSafe(2, 4), Is.EqualTo("3")); 14 | Assert.That("123".SubStringSafe(0, 4), Is.EqualTo("123")); 15 | Assert.That("123".SubStringSafe(0, 2), Is.EqualTo("12")); 16 | Assert.That("123".SubStringSafe(-10, 2), Is.EqualTo("12")); 17 | } 18 | 19 | [Test] 20 | public void TestSubStringSafeWithEllipsis() 21 | { 22 | Assert.That("123".SubStringSafeWithEllipsis(3, 4), Is.EqualTo("...")); 23 | Assert.That("123".SubStringSafeWithEllipsis(5, 1, "###"), Is.EqualTo("###")); 24 | Assert.That("123".SubStringSafeWithEllipsis(2, 4), Is.EqualTo("...3")); 25 | Assert.That("123".SubStringSafeWithEllipsis(0, 4), Is.EqualTo("123")); 26 | Assert.That("123".SubStringSafeWithEllipsis(0, 2), Is.EqualTo("12...")); 27 | Assert.That("123".SubStringSafeWithEllipsis(-10, 2), Is.EqualTo("12...")); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Files/FilesFetcherTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using CodeIndex.Files; 4 | using NUnit.Framework; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | public class FilesFetcherTest : BaseTest 9 | { 10 | [Test] 11 | public void TestFetchFiles() 12 | { 13 | Directory.CreateDirectory(Path.Combine(TempDir, "SubDir")); 14 | Directory.CreateDirectory(Path.Combine(TempDir, "bin\\debug")); 15 | File.Create(Path.Combine(TempDir, "AAA.cs")).Close(); 16 | File.Create(Path.Combine(TempDir, "SubDir", "ddd.txt")).Close(); 17 | File.Create(Path.Combine(TempDir, "SubDir", "ddd.xml")).Close(); 18 | File.Create(Path.Combine(TempDir, "bin\\debug", "ddd.txt")).Close(); 19 | 20 | var files = FilesFetcher.FetchAllFiles(TempDir, new[] { ".xml" }, new[] { "BIN\\" }).ToArray(); 21 | Assert.That(files.Length, Is.EqualTo(2)); 22 | Assert.That(files.Select(u => u.Name), Is.EquivalentTo(new[] { "AAA.cs", "ddd.txt" })); 23 | 24 | files = FilesFetcher.FetchAllFiles(TempDir, new[] { ".xml" }, new[] { "BIN\\" }, "*.cs").ToArray(); 25 | Assert.That(files.Length, Is.EqualTo(1)); 26 | Assert.That(files.Select(u => u.Name), Is.EquivalentTo(new[] { "AAA.cs" })); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/dotnetcore.yml: -------------------------------------------------------------------------------- 1 | name: Publish CodeIndex 2 | 3 | on: 4 | push: 5 | branches: [ master, docker ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup .NET 10 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: 10.0.x 19 | 20 | - name: Restore 21 | run: dotnet restore src/CodeIndex.sln 22 | 23 | - name: Build (Release) 24 | run: dotnet build src/CodeIndex.sln --configuration Release --no-restore 25 | 26 | - name: Test 27 | run: dotnet test src/CodeIndex.sln --no-restore --verbosity normal 28 | 29 | - name: Login to Docker Hub (only docker branch) 30 | if: github.ref == 'refs/heads/docker' 31 | uses: docker/login-action@v3 32 | with: 33 | username: ${{ secrets.DOCKER_USERNAME }} 34 | password: ${{ secrets.DOCKER_PASSWORD }} 35 | 36 | - name: Build & Push Docker image (only docker branch) 37 | if: github.ref == 'refs/heads/docker' 38 | run: | 39 | cd src 40 | IMAGE_TAG=latest 41 | echo "Building image qiuhaotc/codeindex:$IMAGE_TAG from docker branch commit ${GITHUB_SHA}"; 42 | docker build -f CodeIndex.Server/Dockerfile -t qiuhaotc/codeindex:$IMAGE_TAG . 43 | docker push qiuhaotc/codeindex:$IMAGE_TAG 44 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/SearchRequestTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using NUnit.Framework; 4 | 5 | namespace CodeIndex.Test 6 | { 7 | public class SearchRequestTest 8 | { 9 | [Test] 10 | public void TestConstructor() 11 | { 12 | var request = new SearchRequest 13 | { 14 | CaseSensitive = true, 15 | CodePK = "ABC", 16 | Content = "DDD", 17 | FileExtension = "CS", 18 | FileName = "CC", 19 | FilePath = "DD", 20 | ForWeb = true, 21 | IndexPk = Guid.NewGuid(), 22 | NeedReplaceSuffixAndPrefix = true, 23 | PhaseQuery = true, 24 | Preview = true, 25 | ShowResults = 10 26 | }; 27 | 28 | Assert.That(request, Is.EqualTo(request with { })); 29 | Assert.That(request.IsEmpty, Is.False); 30 | 31 | request.IndexPk = Guid.Empty; 32 | Assert.That(request.IsEmpty, Is.True); 33 | 34 | request.IndexPk = Guid.NewGuid(); 35 | request.Content = string.Empty; 36 | request.FileExtension = " "; 37 | request.FilePath = null; 38 | request.FileName = null; 39 | Assert.That(request.IsEmpty, Is.False); 40 | 41 | request.CodePK = null; 42 | Assert.That(request.IsEmpty, Is.True); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/ArgumentValidation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CodeIndex.Common 6 | { 7 | public static class ArgumentValidation 8 | { 9 | public static void RequireContainsElement(this IEnumerable value, string argumentName) 10 | { 11 | if (value == null || !value.Any()) 12 | { 13 | throw new ArgumentException("The collection can't be null or empty", argumentName); 14 | } 15 | } 16 | 17 | public static void RequireNotNullOrEmpty(this string value, string argumentName) 18 | { 19 | if (string.IsNullOrEmpty(value)) 20 | { 21 | throw new ArgumentException("The value can't be null or empty", argumentName); 22 | } 23 | } 24 | 25 | public static void RequireNotNull(this object value, string argumentName) 26 | { 27 | if (value == null) 28 | { 29 | throw new ArgumentException("The value can't be null", argumentName); 30 | } 31 | } 32 | 33 | public static void RequireRange(this int value, string argumentName, int maxValue, int minValue) 34 | { 35 | if (value > maxValue || value < minValue) 36 | { 37 | throw new ArgumentException($"The value should in range ({minValue} - {maxValue})", argumentName); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/BaseTestLight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CodeIndex.Common; 4 | using NUnit.Framework; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | public class BaseTestLight 9 | { 10 | const string TestFolderNamePrefix = "CodeIndex.Test_"; 11 | protected string TempDir { get; set; } 12 | protected string TempIndexDir => Path.Combine(TempDir, "IndexFolder"); 13 | public string MonitorFolder => Path.Combine(TempDir, "CodeFolder"); 14 | protected string TempCodeIndexDir => Path.Combine(TempIndexDir, CodeIndexConfiguration.CodeIndexesFolder, CodeIndexConfiguration.CodeIndexFolder); 15 | protected string TempHintIndexDir => Path.Combine(TempIndexDir, CodeIndexConfiguration.CodeIndexesFolder, CodeIndexConfiguration.HintIndexFolder); 16 | protected DummyLog Log => log ??= new DummyLog(); 17 | DummyLog log; 18 | 19 | [SetUp] 20 | protected virtual void SetUp() 21 | { 22 | TempDir = Path.Combine(Path.GetTempPath(), TestFolderNamePrefix + Guid.NewGuid()); 23 | 24 | Directory.CreateDirectory(TempDir); 25 | Directory.CreateDirectory(MonitorFolder); 26 | } 27 | 28 | [TearDown] 29 | protected virtual void TearDown() 30 | { 31 | DeleteAllFilesInTempDir(TempDir); 32 | } 33 | 34 | void DeleteAllFilesInTempDir(string srcPath) 35 | { 36 | Directory.Delete(srcPath, true); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Utils/DummyLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Text; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | [ExcludeFromCodeCoverage] 9 | public class DummyLog : ILogger 10 | { 11 | readonly StringBuilder logs = new (); 12 | 13 | public string ThrowExceptionWhenLogContains { get; set; } 14 | 15 | public string LogsContent => logs.ToString(); 16 | 17 | public IDisposable BeginScope(TState state) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public void ClearLog() 23 | { 24 | logs.Clear(); 25 | } 26 | 27 | public bool IsEnabled(LogLevel logLevel) 28 | { 29 | return true; 30 | } 31 | 32 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 33 | { 34 | Log(formatter.Invoke(state, exception)); 35 | } 36 | 37 | void Log(string message) 38 | { 39 | if (!string.IsNullOrEmpty(ThrowExceptionWhenLogContains) && message.Contains(ThrowExceptionWhenLogContains)) 40 | { 41 | throw new Exception("Dummy Error"); 42 | } 43 | 44 | logs.AppendLine(message); 45 | } 46 | } 47 | 48 | public class DummyLog : DummyLog, ILogger 49 | { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base 4 | WORKDIR /app 5 | ENV ASPNETCORE_ENVIRONMENT Production 6 | ENV CodeIndex__LuceneIndex /luceneindex 7 | ENV CodeIndex__MonitorFolder /monitorfolder 8 | ENV CodeIndex__IsInLinux true 9 | ENV CodeIndex__LocalUrl http://localhost:80/ 10 | EXPOSE 8080 11 | EXPOSE 8081 12 | 13 | FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build 14 | WORKDIR /src 15 | COPY ["CodeIndex.Server/CodeIndex.Server.csproj", "CodeIndex.Server/"] 16 | COPY ["CodeIndex.Common/CodeIndex.Common.csproj", "CodeIndex.Common/"] 17 | COPY ["CodeIndex.MaintainIndex/CodeIndex.MaintainIndex.csproj", "CodeIndex.MaintainIndex/"] 18 | COPY ["CodeIndex.Files/CodeIndex.Files.csproj", "CodeIndex.Files/"] 19 | COPY ["CodeIndex.IndexBuilder/CodeIndex.IndexBuilder.csproj", "CodeIndex.IndexBuilder/"] 20 | COPY ["CodeIndex.Search/CodeIndex.Search.csproj", "CodeIndex.Search/"] 21 | RUN dotnet restore "CodeIndex.Server/CodeIndex.Server.csproj" 22 | COPY . . 23 | WORKDIR "/src/CodeIndex.Server" 24 | RUN dotnet build "CodeIndex.Server.csproj" -c Release -o /app/build 25 | 26 | FROM build AS publish 27 | RUN dotnet publish "CodeIndex.Server.csproj" -c Release -o /app/publish /p:UseAppHost=false 28 | 29 | FROM base AS final 30 | WORKDIR /app 31 | COPY --from=publish /app/publish . 32 | ENTRYPOINT ["dotnet", "CodeIndex.Server.dll"] -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/TokenTest.razor: -------------------------------------------------------------------------------- 1 | @page "/TokenTest" 2 | @inject HttpClient Client 3 | @inject CodeIndexConfiguration Config 4 | 5 |

TokenTest

6 |
7 |
8 | Content 9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 |
@Result
17 |
18 | 19 | @code { 20 | public string Content { get; set; } 21 | 22 | public bool IsSearching { get; set; } 23 | 24 | public string Result { get; set; } 25 | 26 | private async Task KeyPress(KeyboardEventArgs e) 27 | { 28 | if (e.Key == "Enter") 29 | { 30 | await TokenizeContent(); 31 | } 32 | } 33 | 34 | async Task TokenizeContent() 35 | { 36 | if (IsSearching) 37 | { 38 | return; 39 | } 40 | 41 | IsSearching = true; 42 | Result = (await Client.GetJsonAsync>($"{Config.LocalUrl}api/lucene/GetTokenizeStr?searchStr=" + System.Web.HttpUtility.UrlEncode(Content))).Result; 43 | IsSearching = false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeIndex.Files/FilesWatcherHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CodeIndex.Common; 3 | 4 | namespace CodeIndex.Files 5 | { 6 | public static class FilesWatcherHelper 7 | { 8 | public static FileSystemWatcher StartWatch(string path, FileSystemEventHandler onChangedHandler, RenamedEventHandler onRenameHandler) 9 | { 10 | path.RequireNotNullOrEmpty(nameof(path)); 11 | onChangedHandler.RequireNotNull(nameof(onChangedHandler)); 12 | onRenameHandler.RequireNotNull(nameof(onRenameHandler)); 13 | 14 | var watcher = new FileSystemWatcher(); 15 | watcher.Path = path; 16 | watcher.NotifyFilter = NotifyFilters.DirectoryName | 17 | NotifyFilters.LastWrite | 18 | NotifyFilters.FileName | 19 | NotifyFilters.Size; 20 | //No need: NotifyFilters.LastAccess, NotifyFilters.Attributes 21 | 22 | watcher.Filter = "*.*"; 23 | 24 | // Add event handlers. 25 | watcher.Changed += onChangedHandler; 26 | watcher.Created += onChangedHandler; 27 | watcher.Deleted += onChangedHandler; 28 | watcher.Renamed += onRenameHandler; 29 | 30 | // Include Sub Dir 31 | watcher.IncludeSubdirectories = true; 32 | 33 | // Begin watching. 34 | watcher.EnableRaisingEvents = true; 35 | 36 | return watcher; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndex.VisualStudioExtension.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29911.84 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeIndex.VisualStudioExtension", "CodeIndex.VisualStudioExtension.csproj", "{ED1D3026-8695-4B9B-9FA9-2D68E28EE850}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{44844D57-B2B4-4BE6-8DF1-4D840BCABBF9}" 9 | ProjectSection(SolutionItems) = preProject 10 | ..\.editorconfig = ..\.editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {ED1D3026-8695-4B9B-9FA9-2D68E28EE850}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {ED1D3026-8695-4B9B-9FA9-2D68E28EE850}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {ED1D3026-8695-4B9B-9FA9-2D68E28EE850}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {ED1D3026-8695-4B9B-9FA9-2D68E28EE850}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {E2DA3A3C-BC6C-4922-AA9D-012724225DB2} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/CodeIndexConfigurationTest.cs: -------------------------------------------------------------------------------- 1 | using CodeIndex.Common; 2 | using NUnit.Framework; 3 | 4 | namespace CodeIndex.Test 5 | { 6 | public class CodeIndexConfigurationTest 7 | { 8 | [Test] 9 | public void TestProperties() 10 | { 11 | var config = new CodeIndexConfiguration() 12 | { 13 | IsInLinux = true, 14 | LocalUrl = "http://localhost:1234", 15 | LuceneIndex = "AAA/BBB", 16 | MaximumResults = 234, 17 | ManagerUsers = new[] 18 | { 19 | new UserInfo 20 | { 21 | Id = 1, 22 | Password = "ABC", 23 | UserName = "DEF" 24 | } 25 | } 26 | }; 27 | 28 | Assert.Multiple(() => 29 | { 30 | Assert.That(config.IsInLinux, Is.True); 31 | Assert.That(config.LocalUrl, Is.EqualTo("http://localhost:1234")); 32 | Assert.That(config.LuceneIndex, Is.EqualTo("AAA/BBB")); 33 | Assert.That(config.MaximumResults, Is.EqualTo(234)); 34 | Assert.That(config.ManagerUsers, Is.EquivalentTo(new[] { new UserInfo 35 | { 36 | Id = 1, 37 | Password = "ABC", 38 | UserName = "DEF" 39 | } 40 | })); 41 | }); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndexSearchWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Microsoft.VisualStudio.Shell; 4 | 5 | namespace CodeIndex.VisualStudioExtension 6 | { 7 | /// 8 | /// This class implements the tool window exposed by this package and hosts a user control. 9 | /// 10 | /// 11 | /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 12 | /// usually implemented by the package implementer. 13 | /// 14 | /// This class derives from the ToolWindowPane class provided from the MPF in order to use its 15 | /// implementation of the IVsUIElementPane interface. 16 | /// 17 | /// 18 | [Guid("6d5fde57-9331-4f68-8c3e-2945d8049f40")] 19 | public class CodeIndexSearchWindow : ToolWindowPane 20 | { 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public CodeIndexSearchWindow() : base(null) 25 | { 26 | this.Caption = "Code Index Search"; 27 | 28 | // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, 29 | // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on 30 | // the object returned by the Content property. 31 | this.Content = new CodeIndexSearchWindowControl(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Code Index Search")] 9 | [assembly: AssemblyDescription("Code Index Search Extension For Visual Studio")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CodeIndex.VisualStudioExtension")] 13 | [assembly: AssemblyCopyright("https://github.com/qiuhaotc/CodeIndex")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("0.1.0.0")] 33 | [assembly: AssemblyFileVersion("0.1.0.0")] 34 | [assembly: Guid("8F594ABC-BDED-431E-BF88-23AFE38F9032")] 35 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/CodeSourceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CodeIndex.Common; 4 | using NUnit.Framework; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | public class CodeSourceTest 9 | { 10 | [Test] 11 | public void TestConstructor() 12 | { 13 | var source = new CodeSource(); 14 | Assert.That(source.CodePK, Is.Not.EqualTo(string.Empty)); 15 | Assert.DoesNotThrow(() => new Guid(source.CodePK)); 16 | } 17 | 18 | [Test] 19 | public void TestGetCodeSource() 20 | { 21 | var dateTime = DateTime.UtcNow; 22 | var fileInfo = new FileInfo("AAA.txt"); 23 | var source = CodeSource.GetCodeSource(fileInfo, "ABCD"); 24 | Assert.That(source.FileName, Is.EqualTo("AAA.txt")); 25 | Assert.That(source.Content, Is.EqualTo("ABCD")); 26 | Assert.That(source.FileExtension, Is.EqualTo("txt")); 27 | Assert.That(source.FilePath, Is.EqualTo(fileInfo.FullName)); 28 | Assert.That(source.LastWriteTimeUtc, Is.EqualTo(fileInfo.LastWriteTimeUtc)); 29 | Assert.That(source.IndexDate, Is.GreaterThanOrEqualTo(dateTime)); 30 | Assert.That(source.CodePK, Is.Not.EqualTo(string.Empty)); 31 | Assert.DoesNotThrow(() => new Guid(source.CodePK)); 32 | Assert.That(source.Info, Is.EqualTo($"FileName: {source.FileName}{Environment.NewLine}FilePath: {source.FilePath}{Environment.NewLine}Index Date: {source.IndexDate.ToLocalTime()}{Environment.NewLine}Last Modify Date:{source.LastWriteTimeUtc.ToLocalTime()}")); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/CodeIndex.Files/FilesFetcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CodeIndex.Common; 6 | 7 | namespace CodeIndex.Files 8 | { 9 | public class FilesFetcher 10 | { 11 | public static IEnumerable FetchAllFiles(string path, string[] excludedExtensions, string[] excludedPaths, string includedExtenstion = "*", string[] includedExtensions = null, bool isInLinux = false) 12 | { 13 | path.RequireNotNullOrEmpty(nameof(path)); 14 | excludedExtensions.RequireNotNull(nameof(path)); 15 | excludedPaths.RequireNotNull(nameof(excludedPaths)); 16 | includedExtenstion.RequireNotNullOrEmpty(nameof(includedExtenstion)); 17 | 18 | excludedPaths = FilePathHelper.GetPaths(excludedPaths, isInLinux); 19 | excludedExtensions = excludedExtensions.Select(u => u.ToUpperInvariant()).ToArray(); 20 | includedExtensions = includedExtensions?.Select(u => u.ToUpperInvariant()).ToArray() ?? Array.Empty(); 21 | 22 | return Directory.GetFiles(path, includedExtenstion, SearchOption.AllDirectories) 23 | .Where(f => !excludedExtensions.Any(extenstion => f.EndsWith(extenstion, StringComparison.InvariantCultureIgnoreCase)) 24 | && !excludedPaths.Any(filePath => f.ToUpperInvariant().Contains(filePath)) 25 | && (includedExtensions.Length == 0 || includedExtensions.Any(extension => f.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase)))) 26 | .Select(u => new FileInfo(u)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/CodeIndex.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0 5 | latest 6 | 869cc96a-d1b8-4328-9b99-bc356c26a33a 7 | Linux 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/Logs.razor: -------------------------------------------------------------------------------- 1 | @page "/Logs" 2 | @inject HttpClient Client 3 | @inject IJSRuntime JSRuntime 4 | @inject CodeIndexConfiguration Config 5 | 6 |

Logs

7 |
8 | 9 | 10 |
11 |
12 | 13 |
14 | 15 | @code { 16 | FetchResult Result { get; set; } 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | await base.OnInitializedAsync(); 21 | Result = await GetLogs(); 22 | } 23 | 24 | protected override async Task OnAfterRenderAsync(bool firstRender) 25 | { 26 | await base.OnAfterRenderAsync(firstRender); 27 | await JSRuntime.InvokeVoidAsync("ScrollTextAreaToBottom", "textAreaForLogs"); 28 | } 29 | 30 | bool refreshing; 31 | 32 | async Task RefreshLogs() 33 | { 34 | try 35 | { 36 | if (refreshing) 37 | { 38 | return; 39 | } 40 | 41 | if (Result != null) 42 | { 43 | Result.Result = "Refreshing..."; 44 | } 45 | 46 | refreshing = true; 47 | Result = await GetLogs(); 48 | } 49 | finally 50 | { 51 | refreshing = false; 52 | } 53 | } 54 | 55 | async Task> GetLogs() 56 | { 57 | return await Client.GetJsonAsync>($"{Config.LocalUrl}api/lucene/GetLogs"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | function ScrollTextAreaToBottom(itemID) { 2 | var textarea = document.getElementById(itemID); 3 | textarea.scrollTop = textarea.scrollHeight; 4 | } 5 | 6 | function DoAjaxPost(url, data) { 7 | var result = null; 8 | 9 | $.ajax({ 10 | type: "POST", 11 | url: url, 12 | data: JSON.stringify(data), 13 | headers: { 14 | 'Accept': 'application/json', 15 | 'Content-Type': 'application/json' 16 | }, 17 | success: function (data) { 18 | result = data; 19 | }, 20 | async: false, 21 | error: function (err) { 22 | result = { 23 | Status: { 24 | Success: false, 25 | StatusDesc: err.status === 401 ? "401" : "Unknow Error" 26 | } 27 | }; 28 | } 29 | }) 30 | 31 | return result; 32 | } 33 | 34 | function DoAjaxGet(url, parameters) { 35 | var result = null; 36 | 37 | $.ajax({ 38 | type: "GET", 39 | url: url, 40 | headers: { 41 | 'Accept': 'application/json', 42 | 'Content-Type': 'application/json' 43 | }, 44 | data: parameters, 45 | success: function (data) { 46 | result = data; 47 | }, 48 | async: false, 49 | error: function (err) { 50 | result = { 51 | Status: { 52 | Success: false, 53 | StatusDesc: err.status === 401 ? "401" : "Unknow Error" 54 | } 55 | }; 56 | } 57 | }) 58 | 59 | return result; 60 | } 61 | 62 | function ShowConfirm(message) { 63 | return confirm(message); 64 | } -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/ArgumentValidationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using CodeIndex.Common; 5 | using NUnit.Framework; 6 | 7 | namespace CodeIndex.Test 8 | { 9 | [ExcludeFromCodeCoverage] 10 | public class ArgumentValidationTest 11 | { 12 | [Test] 13 | public void TestRequireContainsElement() 14 | { 15 | Assert.Throws(() => ArgumentValidation.RequireContainsElement>(null, "A")); 16 | Assert.Throws(() => Array.Empty().RequireContainsElement("A")); 17 | Assert.DoesNotThrow(() => new[] { 1, 2, 3 }.RequireContainsElement("A")); 18 | } 19 | 20 | [Test] 21 | public void TestRequireNotNullOrEmpty() 22 | { 23 | Assert.Throws(() => ArgumentValidation.RequireNotNullOrEmpty(null, "A")); 24 | Assert.Throws(() => string.Empty.RequireNotNullOrEmpty("A")); 25 | Assert.DoesNotThrow(() => "ABC".RequireNotNullOrEmpty("A")); 26 | } 27 | 28 | [Test] 29 | public void TestRequireNotNull() 30 | { 31 | Assert.Throws(() => ArgumentValidation.RequireNotNull(null, "A")); 32 | Assert.DoesNotThrow(() => string.Empty.RequireNotNull("A")); 33 | } 34 | 35 | [Test] 36 | public void TestRequireRange() 37 | { 38 | Assert.Throws(() => 123.RequireRange("A", 100, 1)); 39 | Assert.Throws(() => 0.RequireRange("A", 100, 1)); 40 | Assert.DoesNotThrow(() => 12.RequireRange("A", 100, 1)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/CodeIndex.MaintainIndex/IndexMaintainerWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using CodeIndex.IndexBuilder; 4 | using Lucene.Net.QueryParsers.Classic; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace CodeIndex.MaintainIndex 8 | { 9 | public class IndexMaintainerWrapper : IDisposable 10 | { 11 | public IndexMaintainerWrapper(IndexConfig indexConfig, CodeIndexConfiguration codeIndexConfiguration, ILogger log) 12 | { 13 | indexConfig.RequireNotNull(nameof(indexConfig)); 14 | codeIndexConfiguration.RequireNotNull(nameof(codeIndexConfiguration)); 15 | log.RequireNotNull(nameof(log)); 16 | 17 | Maintainer = new IndexMaintainer(indexConfig, codeIndexConfiguration, log); 18 | IndexConfig = indexConfig; 19 | } 20 | 21 | public IndexMaintainer Maintainer { get; } 22 | 23 | public bool IsDisposing { get; private set; } 24 | 25 | public IndexStatus Status => Maintainer.Status; 26 | 27 | public IndexConfig IndexConfig { get; } 28 | 29 | public void Dispose() 30 | { 31 | if (!IsDisposing) 32 | { 33 | IsDisposing = true; 34 | Maintainer.Dispose(); 35 | } 36 | } 37 | 38 | QueryParser queryParserNormal; 39 | public QueryParser QueryParserNormal => queryParserNormal ??= LucenePoolLight.GetQueryParser(); 40 | 41 | QueryParser queryParserCaseSensitive; 42 | public QueryParser QueryParserCaseSensitive => queryParserCaseSensitive ??= LucenePoolLight.GetQueryParser(false); 43 | 44 | QueryGenerator queryGenerator; 45 | public QueryGenerator QueryGenerator => queryGenerator ??= new QueryGenerator(QueryParserNormal, QueryParserCaseSensitive); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Files/FilesContentHelperTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using CodeIndex.Files; 5 | using NUnit.Framework; 6 | 7 | namespace CodeIndex.Test 8 | { 9 | public class FilesContentHelperTest : BaseTest 10 | { 11 | [Test] 12 | public void TestGetContent_ReadContentUsedByAnotherProcess() 13 | { 14 | using var stream1 = File.Create(Path.Combine(TempDir, "AAA.cs")); 15 | stream1.Write(Encoding.UTF8.GetBytes("这是一个例句")); 16 | stream1.Close(); 17 | 18 | using var stream2 = new FileStream(Path.Combine(TempDir, "AAA.cs"), FileMode.Open, FileAccess.Write); 19 | var content = FilesContentHelper.ReadAllText(Path.Combine(TempDir, "AAA.cs")); 20 | Assert.That(content, Is.EqualTo("这是一个例句"), "Can read file content used by another process"); 21 | } 22 | 23 | [Test] 24 | [TestCaseSource(nameof(EncodingTestCases))] 25 | public void TestGetContent_EncodingCorrect((Encoding Encoding, string Name) encodingWithName) 26 | { 27 | var filePath = Path.Combine(TempDir, "AAA.cs"); 28 | File.WriteAllText(filePath, "这是一个例句", encodingWithName.Encoding); 29 | 30 | var content = FilesContentHelper.ReadAllText(Path.Combine(TempDir, "AAA.cs")); 31 | Assert.That(content, Is.EqualTo("这是一个例句"), $"Test Under {encodingWithName.Name}"); 32 | } 33 | 34 | static IEnumerable<(Encoding Encoding, string Name)> EncodingTestCases() 35 | { 36 | yield return (Encoding.UTF8, "UTF-8"); 37 | yield return (Encoding.Unicode, "UTF-16LE"); 38 | yield return (Encoding.BigEndianUnicode, "UTF-16BE"); 39 | yield return (Encoding.UTF32, "UTF-32"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/CodeTokenUtils/CodeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using CodeIndex.Common; 4 | using Lucene.Net.Analysis; 5 | using Lucene.Net.Analysis.Core; 6 | using Lucene.Net.Analysis.Miscellaneous; 7 | using Lucene.Net.Util; 8 | 9 | namespace CodeIndex.IndexBuilder 10 | { 11 | public class CodeAnalyzer : Analyzer 12 | { 13 | readonly LuceneVersion luceneVersion; 14 | readonly bool lowerCase; 15 | 16 | public CodeAnalyzer(LuceneVersion luceneVersion, bool lowerCase) 17 | { 18 | this.luceneVersion = luceneVersion; 19 | this.lowerCase = lowerCase; 20 | } 21 | 22 | protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) 23 | { 24 | var tokenizer = new CodeTokenizer(reader); 25 | 26 | if (lowerCase) 27 | { 28 | return new TokenStreamComponents(tokenizer, new LowerCaseFilter(luceneVersion, tokenizer)); 29 | } 30 | 31 | return new TokenStreamComponents(tokenizer); 32 | } 33 | 34 | public static Analyzer GetCaseSensitiveAndInsesitiveCodeAnalyzer(params string[] fieldNamesNeedCaseSensitive) 35 | { 36 | fieldNamesNeedCaseSensitive.RequireContainsElement(nameof(fieldNamesNeedCaseSensitive)); 37 | 38 | Dictionary analyzerPerField = new(); 39 | var caseSensitiveAnalyzer = new CodeAnalyzer(Constants.AppLuceneVersion, false); 40 | 41 | foreach (var fieldNameNeedCaseSensitive in fieldNamesNeedCaseSensitive) 42 | { 43 | analyzerPerField.Add(fieldNameNeedCaseSensitive, caseSensitiveAnalyzer); 44 | } 45 | 46 | var analyzer = new PerFieldAnalyzerWrapper(new CodeAnalyzer(Constants.AppLuceneVersion, true), analyzerPerField); 47 | return analyzer; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Common/IndexConfigTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CodeIndex.Common; 4 | using NUnit.Framework; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | class IndexConfigTest 9 | { 10 | [Test] 11 | public void TestConstructor() 12 | { 13 | var config = new IndexConfig 14 | { 15 | ExcludedExtensions = "A|B|C", 16 | ExcludedPaths = "B|C|D", 17 | IncludedExtensions = "E|F", 18 | IndexName = "ABC", 19 | MaxContentHighlightLength = 100, 20 | MonitorFolder = "BCA", 21 | MonitorFolderRealPath = "AAA", 22 | OpenIDEUriFormat = "BBB", 23 | SaveIntervalSeconds = 10 24 | }; 25 | 26 | Assert.That(config.Pk, Is.Not.EqualTo(Guid.Empty)); 27 | Assert.That(config.ExcludedExtensions, Is.EqualTo("A|B|C")); 28 | Assert.That(config.ExcludedPaths, Is.EqualTo("B|C|D")); 29 | Assert.That(config.IncludedExtensions, Is.EqualTo("E|F")); 30 | Assert.That(config.ExcludedExtensionsArray, Is.EquivalentTo(new[] { "A", "B", "C" })); 31 | Assert.That(config.ExcludedPathsArray, Is.EquivalentTo(new[] { "B", "C", "D" })); 32 | Assert.That(config.IncludedExtensionsArray, Is.EquivalentTo(new[] { "E", "F" })); 33 | Assert.That(config.IndexName, Is.EqualTo("ABC")); 34 | Assert.That(config.MaxContentHighlightLength, Is.EqualTo(100)); 35 | Assert.That(config.MonitorFolder, Is.EqualTo("BCA")); 36 | Assert.That(config.MonitorFolderRealPath, Is.EqualTo("AAA")); 37 | Assert.That(config.OpenIDEUriFormat, Is.EqualTo("BBB")); 38 | Assert.That(config.SaveIntervalSeconds, Is.EqualTo(10)); 39 | 40 | config.IncludedExtensions = null; 41 | Assert.That(config.IncludedExtensionsArray.Count(), Is.EqualTo(0)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Models/ConfigHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.IO; 3 | 4 | namespace CodeIndex.VisualStudioExtension 5 | { 6 | public static class ConfigHelper 7 | { 8 | static Configuration configuration; 9 | public static Configuration Configuration 10 | { 11 | get 12 | { 13 | if (configuration == null) 14 | { 15 | var location = System.Reflection.Assembly.GetExecutingAssembly().Location; 16 | var fileInfo = new FileInfo(location); 17 | if (fileInfo.Exists) 18 | { 19 | var configFileMap = new ExeConfigurationFileMap(); 20 | configFileMap.ExeConfigFilename = Path.Combine(fileInfo.DirectoryName, "CodeIndex.Settings.config"); 21 | 22 | configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); 23 | } 24 | } 25 | 26 | return configuration; 27 | } 28 | } 29 | 30 | public static bool SetConfiguration(string key, string value) 31 | { 32 | if(Configuration == null) 33 | { 34 | return false; 35 | } 36 | 37 | try 38 | { 39 | if (Configuration.AppSettings.Settings[key] != null) 40 | { 41 | Configuration.AppSettings.Settings[key].Value = value; 42 | } 43 | else 44 | { 45 | Configuration.AppSettings.Settings.Add(key, value); 46 | } 47 | 48 | Configuration.Save(ConfigurationSaveMode.Modified); 49 | ConfigurationManager.RefreshSection("appSettings"); 50 | return true; 51 | } 52 | catch 53 | { 54 | return false; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 | 34 |
35 | 36 | @code { 37 | private bool collapseNavMenu = true; 38 | 39 | private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 40 | 41 | private void ToggleNavMenu() 42 | { 43 | collapseNavMenu = !collapseNavMenu; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Shared/SearchingWithFile.razor: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @if (FetchResult?.Result != null && FetchResult.Result.Any()) 14 | { 15 | foreach (var item in FetchResult.Result) 16 | { 17 | 18 | 19 | 22 | 26 | 27 | 30 | 31 | } 32 | } 33 | else 34 | { 35 | 36 | 37 | 38 | } 39 | 40 |
File NameMatch InfoFile PathFile ExtensionDetails
@item.FileName 20 |
@((MarkupString)item.Content)
21 |
23 | 24 | @item.FilePath?.SubStringSafeWithEllipsis(item.FilePath.Length - 50, 50) 25 | @item.FileExtension 28 | Details 29 |
Empty
41 |
42 | 43 | @code{ 44 | [Parameter] 45 | public FetchResult> FetchResult { get; set; } 46 | 47 | [Parameter] 48 | public Guid IndexPk { get; set; } 49 | 50 | [Parameter] 51 | public IndexConfigForView CurrentIndexConfig { get; set; } 52 | 53 | [Parameter] 54 | public string ContentQuery { get; set; } 55 | 56 | [Parameter] 57 | public bool CaseSensitive { get; set; } 58 | 59 | [Parameter] 60 | public bool PhaseQuery { get; set; } 61 | } -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/CodeTokenUtils/CodeTokenizer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.IO; 4 | using ICU4N.Text; 5 | using Lucene.Net.Analysis.TokenAttributes; 6 | using Lucene.Net.Analysis.Util; 7 | 8 | namespace CodeIndex.IndexBuilder 9 | { 10 | /// 11 | /// Reference the SmartCn Tokenizer 12 | /// 13 | internal sealed class CodeTokenizer : SegmentingTokenizerBase 14 | { 15 | static CodeTokenizer() 16 | { 17 | CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; // Workaround to fix the bug of ICN4N, refer https://github.com/NightOwl888/ICU4N/issues/29 18 | sentenceProto = BreakIterator.GetSentenceInstance(CultureInfo.InvariantCulture); 19 | } 20 | 21 | static readonly BreakIterator sentenceProto; 22 | readonly WordSegmenter wordSegmenter = new WordSegmenter(); 23 | 24 | readonly ICharTermAttribute termAtt; 25 | readonly IOffsetAttribute offsetAtt; 26 | 27 | IEnumerator tokens; 28 | 29 | public CodeTokenizer(TextReader reader) : base(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY, reader, (BreakIterator)sentenceProto.Clone()) 30 | { 31 | termAtt = AddAttribute(); 32 | offsetAtt = AddAttribute(); 33 | } 34 | 35 | protected override void SetNextSentence(int sentenceStart, int sentenceEnd) 36 | { 37 | var sentence = new string(m_buffer, sentenceStart, sentenceEnd - sentenceStart); 38 | tokens = wordSegmenter.SegmentSentence(sentence, m_offset + sentenceStart).GetEnumerator(); 39 | } 40 | 41 | protected override bool IncrementWord() 42 | { 43 | if (tokens == null || !tokens.MoveNext()) 44 | { 45 | return false; 46 | } 47 | else 48 | { 49 | var token = tokens.Current; 50 | ClearAttributes(); 51 | termAtt.CopyBuffer(token.CharArray, 0, token.CharArray.Length); 52 | offsetAtt.SetOffset(CorrectOffset(token.StartOffset), CorrectOffset(token.EndOffset)); 53 | return true; 54 | } 55 | } 56 | 57 | public override void Reset() 58 | { 59 | base.Reset(); 60 | tokens = null; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Search/InitManagement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using CodeIndex.Common; 5 | using CodeIndex.MaintainIndex; 6 | using CodeIndex.Search; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace CodeIndex.Test 10 | { 11 | public class InitManagement : IDisposable 12 | { 13 | readonly IndexManagement management; 14 | readonly IndexConfig indexConfig; 15 | readonly ILogger log1 = new DummyLog(); 16 | readonly ILogger log2 = new DummyLog(); 17 | 18 | public InitManagement(string monitorFolder, CodeIndexConfiguration codeIndexConfiguration, bool initFiles = false, int maxContentHighlightLength = Constants.DefaultMaxContentHighlightLength) 19 | { 20 | indexConfig = new IndexConfig 21 | { 22 | IndexName = "ABC", 23 | MonitorFolder = monitorFolder, 24 | MaxContentHighlightLength = maxContentHighlightLength 25 | }; 26 | 27 | if (initFiles) 28 | { 29 | var fileName1 = Path.Combine(monitorFolder, "A.txt"); 30 | var fileName2 = Path.Combine(monitorFolder, "B.txt"); 31 | var fileName3 = Path.Combine(monitorFolder, "C.TXT"); 32 | File.AppendAllText(fileName1, "ABCD"); 33 | File.AppendAllText(fileName2, "ABCD EFGH"); 34 | File.AppendAllText(fileName3, "ABCD EFGH IJKL" + Environment.NewLine + "ABCD ABCE"); 35 | } 36 | 37 | management = new IndexManagement(codeIndexConfiguration, log1); 38 | management.AddIndex(indexConfig); 39 | var maintainer = management.GetIndexMaintainerWrapperAndInitializeIfNeeded(indexConfig.Pk); 40 | 41 | // Wait initialized finished 42 | while (maintainer.Result.Status == IndexStatus.Initializing_ComponentInitializeFinished || maintainer.Result.Status == IndexStatus.Initialized) 43 | { 44 | Thread.Sleep(100); 45 | } 46 | } 47 | 48 | public CodeIndexSearcher GetIndexSearcher() 49 | { 50 | return new CodeIndexSearcher(management, log2); 51 | } 52 | 53 | public Guid IndexPk => indexConfig.Pk; 54 | 55 | public void Dispose() 56 | { 57 | management.Dispose(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Shared/SearchingWithLineNumber.razor: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @if (FetchResult?.Result != null && FetchResult.Result.Any()) 15 | { 16 | foreach (var item in FetchResult.Result) 17 | { 18 | 19 | 20 | 21 | 24 | 27 | 28 | 31 | 32 | } 33 | } 34 | else 35 | { 36 | 37 | 38 | 39 | } 40 | 41 |
File NameMatch LineMatch InfoFile PathFile ExtensionDetails
@item.CodeSource.FileName@item.MatchedLine 22 |
@((MarkupString)item.MatchedContent)
23 |
25 | @item.CodeSource.FilePath?.SubStringSafeWithEllipsis(item.CodeSource.FilePath.Length - 50, 50) 26 | @item.CodeSource.FileExtension 29 | Details 30 |
Empty
42 |
43 | 44 | @code{ 45 | [Parameter] 46 | public FetchResult> FetchResult { get; set; } 47 | 48 | [Parameter] 49 | public Guid IndexPk { get; set; } 50 | 51 | [Parameter] 52 | public IndexConfigForView CurrentIndexConfig { get; set; } 53 | 54 | [Parameter] 55 | public string ContentQuery { get; set; } 56 | 57 | [Parameter] 58 | public bool CaseSensitive { get; set; } 59 | 60 | [Parameter] 61 | public bool PhaseQuery { get; set; } 62 | } -------------------------------------------------------------------------------- /src/CodeIndex.ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeIndex.Common; 3 | using CodeIndex.MaintainIndex; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using NLog; 8 | using NLog.Extensions.Logging; 9 | 10 | namespace CodeIndex.ConsoleApp 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | var config = new ConfigurationBuilder() 17 | .SetBasePath(System.IO.Directory.GetCurrentDirectory()) 18 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 19 | .Build(); 20 | var servicesProvider = BuildDi(config); 21 | using (servicesProvider as IDisposable) 22 | { 23 | using var management = servicesProvider.GetRequiredService(); 24 | 25 | var indexLists = management.GetIndexList(); 26 | 27 | if (indexLists.Status.Success) 28 | { 29 | if (indexLists.Result.Length == 0) 30 | { 31 | var indexConfig = new IndexConfig 32 | { 33 | IndexName = "Test", 34 | MonitorFolder = @"D:\TestFolder\CodeFolder", 35 | }; 36 | 37 | management.AddIndex(indexConfig); 38 | management.StartIndex(indexConfig.Pk); 39 | } 40 | else 41 | { 42 | management.StartIndex(indexLists.Result[0].IndexConfig.Pk); 43 | } 44 | } 45 | 46 | Console.WriteLine("Press any key to stop"); 47 | Console.ReadLine(); 48 | Console.WriteLine("Stop"); 49 | } 50 | 51 | LogManager.Shutdown(); 52 | } 53 | 54 | static IServiceProvider BuildDi(IConfiguration config) 55 | { 56 | return new ServiceCollection() 57 | .AddSingleton() // Runner is the custom class 58 | .AddLogging(loggingBuilder => 59 | { 60 | // configure Logging with NLog 61 | loggingBuilder.ClearProviders(); 62 | loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); 63 | loggingBuilder.AddNLog(config); 64 | }) 65 | .AddSingleton(new CodeIndexConfiguration { LuceneIndex = @"D:\\TestFolder\\Index" }) 66 | .BuildServiceProvider(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Services/LocalizationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Resources; 3 | 4 | namespace CodeIndex.VisualStudioExtension.Services 5 | { 6 | /// 7 | /// Provides localization services for the extension. 8 | /// Automatically detects Visual Studio's UI language and loads the appropriate resource file. 9 | /// 10 | public class LocalizationService 11 | { 12 | private static readonly Lazy instance = 13 | new Lazy(() => new LocalizationService()); 14 | 15 | private readonly ResourceManager resourceManager; 16 | 17 | private LocalizationService() 18 | { 19 | // Initialize ResourceManager pointing to default resource file 20 | resourceManager = new ResourceManager( 21 | "CodeIndex.VisualStudioExtension.Resources.Strings", 22 | typeof(LocalizationService).Assembly); 23 | } 24 | 25 | /// 26 | /// Gets the singleton instance of LocalizationService. 27 | /// 28 | public static LocalizationService Instance => instance.Value; 29 | 30 | /// 31 | /// Indexer to access localized strings by key. 32 | /// 33 | /// Resource key 34 | /// Localized string, or key if not found 35 | public string this[string key] => GetString(key); 36 | 37 | /// 38 | /// Gets a localized string by key. 39 | /// 40 | /// Resource key 41 | /// Localized string, or key if not found 42 | public string GetString(string key) 43 | { 44 | if (string.IsNullOrWhiteSpace(key)) 45 | return string.Empty; 46 | 47 | try 48 | { 49 | var result = resourceManager.GetString(key); 50 | return result ?? key; // Fallback to key if resource not found 51 | } 52 | catch 53 | { 54 | return key; // Fallback to key on error 55 | } 56 | } 57 | 58 | /// 59 | /// Gets a formatted localized string by key with arguments. 60 | /// 61 | /// Resource key 62 | /// Format arguments 63 | /// Formatted localized string 64 | public string GetString(string key, params object[] args) 65 | { 66 | var format = GetString(key); 67 | try 68 | { 69 | return string.Format(format, args); 70 | } 71 | catch 72 | { 73 | return format; // Return unformatted string if formatting fails 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | @inject HttpClient Client 3 | @inject CodeIndexConfiguration Config 4 | @inject NavigationManager NavigationManager 5 | @inject IJSRuntime JSRuntime 6 | 7 |

Login

8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 23 | 24 |
25 |
26 | 27 | 28 |
29 | @if (LoginModel.Status != LoginStatus.Succesful) 30 | { 31 | 34 | } 35 | 36 |
37 | @code { 38 | public ClientLoginModel LoginModel { get; set; } = new ClientLoginModel(); 39 | 40 | public bool OnLogin { get; set; } 41 | 42 | public string Picture { get; set; } 43 | 44 | protected override async Task OnInitializedAsync() 45 | { 46 | await base.OnInitializedAsync(); 47 | 48 | SetPicture(); 49 | } 50 | 51 | async Task DoLogin() 52 | { 53 | if (OnLogin) 54 | { 55 | return; 56 | } 57 | 58 | try 59 | { 60 | OnLogin = true; 61 | 62 | LoginModel = await JSRuntime.InvokeAsync("DoAjaxPost", "api/management/login", LoginModel); 63 | 64 | if (LoginModel.Status == LoginStatus.Succesful) 65 | { 66 | NavigationManager.NavigateTo("/Management"); 67 | } 68 | else 69 | { 70 | SetPicture(); 71 | } 72 | } 73 | catch (Exception ex) 74 | { 75 | LoginModel.Message = ex.Message; 76 | LoginModel.Status = LoginStatus.Exception; 77 | } 78 | finally 79 | { 80 | OnLogin = false; 81 | } 82 | } 83 | 84 | void SetPicture() 85 | { 86 | Picture = "api/management/GenerateCaptcha?timestamp=" + DateTime.Now.Ticks; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Code Index Search 6 | Code index search extension for visual studio 7 | CodeIndex: a fast code searching tools based on Lucene.Net 8 | More details see: https://github.com/qiuhaotc/CodeIndex 9 | https://github.com/qiuhaotc/CodeIndex 10 | Resources\Extension-Icon.png 11 | Resources\Extension-Icon.png 12 | Code full-text search 13 | 14 | 15 | 16 | 17 | x86 18 | 19 | 20 | x86 21 | 22 | 23 | x86 24 | 25 | 26 | 27 | amd64 28 | 29 | 30 | amd64 31 | 32 | 33 | amd64 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Converts/StringToXamlConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Data; 6 | using System.Windows.Documents; 7 | using System.Windows.Media; 8 | using CodeIndex.IndexBuilder; 9 | 10 | namespace CodeIndex.VisualStudioExtension 11 | { 12 | class StringToXamlConverter : IValueConverter 13 | { 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | var input = value as string; 17 | 18 | if (input != null) 19 | { 20 | var textBlock = new TextBlock 21 | { 22 | TextWrapping = TextWrapping.Wrap 23 | }; 24 | 25 | var escapedXml = input; // TODO: a good way to use SecurityElement.Escape(input); 26 | 27 | while (escapedXml.IndexOf(CodeContentProcessing.HighLightPrefix) != -1) 28 | { 29 | var startIndex = escapedXml.IndexOf(CodeContentProcessing.HighLightPrefix); 30 | var endIndex = escapedXml.IndexOf(CodeContentProcessing.HighLightSuffix); 31 | 32 | if(startIndex < endIndex) 33 | { 34 | //up to start is normal 35 | textBlock.Inlines.Add(new Run(escapedXml.Substring(0, startIndex))); 36 | 37 | //between start and end is highlighted 38 | textBlock.Inlines.Add(new Run(escapedXml.Substring(startIndex + CodeContentProcessing.HighLightPrefix.Length, endIndex - startIndex - CodeContentProcessing.HighLightPrefix.Length)) 39 | 40 | { 41 | FontWeight = FontWeights.Bold, 42 | Background = Brushes.OrangeRed 43 | }); 44 | 45 | //the rest of the string (after the end) 46 | escapedXml = escapedXml.Substring(endIndex + CodeContentProcessing.HighLightSuffix.Length); 47 | } 48 | else 49 | { 50 | escapedXml = escapedXml.Replace(CodeContentProcessing.HighLightPrefix, string.Empty).Replace(CodeContentProcessing.HighLightSuffix, string.Empty); 51 | break; 52 | } 53 | // TODO: can high light the one like "AAA BBB VCC DDDD" 54 | } 55 | 56 | if (escapedXml.Length > 0) 57 | { 58 | textBlock.Inlines.Add(new Run(escapedXml)); 59 | } 60 | 61 | return textBlock; 62 | } 63 | 64 | return null; 65 | } 66 | 67 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 68 | { 69 | throw new NotImplementedException("This converter cannot be used in two-way binding."); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/Pages/SearchDetails.razor: -------------------------------------------------------------------------------- 1 | @page "/Details/{CodePK}/{IndexPK}" 2 | @page "/Details/{CodePK}/{IndexPK}/{ContentQuery}" 3 | @page "/Details/{CodePK}/{IndexPK}/{CaseSensitive:bool}/{PhaseQuery:bool}" 4 | @page "/Details/{CodePK}/{IndexPK}/{ContentQuery}/{CaseSensitive:bool}/{PhaseQuery:bool}" 5 | @inject HttpClient Client 6 | @inject CodeIndexConfiguration Config 7 | @inject IndexManagement IndexManagement 8 | 9 |
10 |

Details For @Source?.FileName

11 |

Extension: @Source?.FileExtension | Index Date: @Source?.IndexDate | Last Write Time Utc: @Source?.LastWriteTimeUtc

12 |
13 |

File Path: @Source?.FilePath

14 |
15 |

Content:

16 |

17 |

@((MarkupString)Source?.Content)
18 |

19 |
20 | 21 | @code { 22 | [Parameter] 23 | public string CodePK { get; set; } 24 | 25 | [Parameter] 26 | public string ContentQuery { get; set; } 27 | 28 | [Parameter] 29 | public string IndexPK { get; set; } 30 | 31 | [Parameter] 32 | public bool? CaseSensitive { get; set; } 33 | 34 | [Parameter] 35 | public bool? PhaseQuery { get; set; } 36 | 37 | IndexConfigForView currentIndexConfig; 38 | public IndexConfigForView CurrentIndexConfig 39 | { 40 | get 41 | { 42 | if (currentIndexConfig == null) 43 | { 44 | currentIndexConfig = IndexManagement.GetIndexView(new Guid(IndexPK)); 45 | } 46 | 47 | return currentIndexConfig; 48 | } 49 | } 50 | 51 | public CodeSource Source { get; set; } 52 | 53 | protected override async Task OnInitializedAsync() 54 | { 55 | try 56 | { 57 | var result = await Client.PostAsJsonAsync($"{Config.LocalUrl}api/lucene/GetCodeSources", 58 | new SearchRequest 59 | { 60 | IndexPk = new Guid(IndexPK), 61 | CodePK = CodePK, 62 | Content = System.Web.HttpUtility.UrlDecode(ContentQuery), 63 | CaseSensitive = CaseSensitive ?? false, 64 | PhaseQuery = PhaseQuery ?? false 65 | }); 66 | 67 | if (result.IsSuccessStatusCode) 68 | { 69 | Source = (await result.Content.ReadFromJsonAsync>>()).Result.FirstOrDefault(); 70 | } 71 | else 72 | { 73 | Source = new CodeSource 74 | { 75 | Content = await result.Content.ReadAsStringAsync() 76 | }; 77 | } 78 | } 79 | catch (Exception ex) 80 | { 81 | Source = new CodeSource 82 | { 83 | Content = ex.Message 84 | }; 85 | } 86 | 87 | await base.OnInitializedAsync(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/CodeIndex.MaintainIndex/ConfigIndexMaintainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using CodeIndex.Common; 5 | using CodeIndex.IndexBuilder; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace CodeIndex.MaintainIndex 9 | { 10 | public class ConfigIndexMaintainer : IDisposable 11 | { 12 | public ConfigIndexMaintainer(CodeIndexConfiguration codeIndexConfiguration, ILogger log) 13 | { 14 | codeIndexConfiguration.RequireNotNull(nameof(codeIndexConfiguration)); 15 | log.RequireNotNull(nameof(log)); 16 | 17 | CodeIndexConfiguration = codeIndexConfiguration; 18 | Log = log; 19 | 20 | var folder = Path.Combine(codeIndexConfiguration.LuceneIndex, CodeIndexConfiguration.ConfigurationIndexFolder); 21 | 22 | if (!Directory.Exists(folder)) 23 | { 24 | Log.LogInformation($"Create Configuraion index folder {folder}"); 25 | 26 | try 27 | { 28 | Directory.CreateDirectory(folder); 29 | } 30 | catch (Exception ex) 31 | { 32 | var newFolder = Path.Combine(AppContext.BaseDirectory, CodeIndexConfiguration.ConfigurationIndexFolder); 33 | 34 | Log.LogWarning(ex, $"Create Configuraion index folder {folder} failed, fallback to create index folder under {newFolder}"); 35 | 36 | try 37 | { 38 | Directory.CreateDirectory(newFolder); 39 | CodeIndexConfiguration.LuceneIndex = AppContext.BaseDirectory; 40 | folder = newFolder; 41 | } 42 | catch (Exception ex2) 43 | { 44 | Log.LogError(ex2, $"Create Configuraion index folder {folder} failed"); 45 | } 46 | } 47 | } 48 | 49 | ConfigIndexBuilder = new ConfigIndexBuilder(folder); 50 | } 51 | 52 | public IEnumerable GetConfigs() 53 | { 54 | return ConfigIndexBuilder.GetConfigs(); 55 | } 56 | 57 | public void AddIndexConfig(IndexConfig indexConfig) 58 | { 59 | ConfigIndexBuilder.AddIndexConfig(indexConfig); 60 | } 61 | 62 | public void DeleteIndexConfig(Guid pk) 63 | { 64 | ConfigIndexBuilder.DeleteIndexConfig(pk); 65 | } 66 | 67 | public void EditIndexConfig(IndexConfig indexConfig) 68 | { 69 | ConfigIndexBuilder.EditIndexConfig(indexConfig); 70 | } 71 | 72 | public bool IsDisposing { get; private set; } 73 | public CodeIndexConfiguration CodeIndexConfiguration { get; } 74 | public ILogger Log { get; } 75 | ConfigIndexBuilder ConfigIndexBuilder { get; } 76 | 77 | public void Dispose() 78 | { 79 | if (!IsDisposing) 80 | { 81 | IsDisposing = true; 82 | ConfigIndexBuilder.Dispose(); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/ConfigIndexBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CodeIndex.Common; 5 | using Lucene.Net.Documents; 6 | using Lucene.Net.Index; 7 | using Lucene.Net.Search; 8 | 9 | namespace CodeIndex.IndexBuilder 10 | { 11 | public class ConfigIndexBuilder : IDisposable 12 | { 13 | public ConfigIndexBuilder(string configIndex) 14 | { 15 | configIndex.RequireNotNullOrEmpty(nameof(configIndex)); 16 | 17 | ConfigPool = new LucenePoolLight(configIndex); 18 | } 19 | 20 | public IEnumerable GetConfigs() 21 | { 22 | return ConfigPool.Search(new MatchAllDocsQuery(), int.MaxValue).Select(u => u.GetObject()); 23 | } 24 | 25 | public void AddIndexConfig(IndexConfig indexConfig) 26 | { 27 | ConfigPool.BuildIndex(new[] { GetDocument(indexConfig) }, true); 28 | } 29 | 30 | public void DeleteIndexConfig(Guid pk) 31 | { 32 | ConfigPool.DeleteIndex(new Term(nameof(IndexConfig.Pk), pk.ToString())); 33 | ConfigPool.Commit(); 34 | } 35 | 36 | public void EditIndexConfig(IndexConfig indexConfig) 37 | { 38 | ConfigPool.UpdateIndex(new Term(nameof(IndexConfig.Pk), indexConfig.Pk.ToString()), GetDocument(indexConfig)); 39 | ConfigPool.Commit(); 40 | } 41 | 42 | public bool IsDisposing { get; private set; } 43 | public LucenePoolLight ConfigPool { get; } 44 | 45 | public void Dispose() 46 | { 47 | if (!IsDisposing) 48 | { 49 | IsDisposing = true; 50 | ConfigPool.Dispose(); 51 | } 52 | } 53 | 54 | public static Document GetDocument(IndexConfig indexConfig) 55 | { 56 | return new Document 57 | { 58 | new StringField(nameof(IndexConfig.Pk), indexConfig.Pk.ToString(), Field.Store.YES), 59 | new StringField(nameof(IndexConfig.IndexName), indexConfig.IndexName.ToStringSafe(), Field.Store.YES), 60 | new StringField(nameof(IndexConfig.MonitorFolder), indexConfig.MonitorFolder.ToStringSafe(), Field.Store.YES), 61 | new Int32Field(nameof(IndexConfig.MaxContentHighlightLength), indexConfig.MaxContentHighlightLength, Field.Store.YES), 62 | new Int32Field(nameof(IndexConfig.SaveIntervalSeconds), indexConfig.SaveIntervalSeconds, Field.Store.YES), 63 | new StringField(nameof(IndexConfig.OpenIDEUriFormat), indexConfig.OpenIDEUriFormat.ToStringSafe(), Field.Store.YES), 64 | new StringField(nameof(IndexConfig.MonitorFolderRealPath), indexConfig.MonitorFolderRealPath.ToStringSafe(), Field.Store.YES), 65 | new StringField(nameof(IndexConfig.ExcludedPaths), indexConfig.ExcludedPaths.ToStringSafe(), Field.Store.YES), 66 | new StringField(nameof(IndexConfig.IncludedExtensions), indexConfig.IncludedExtensions.ToStringSafe(), Field.Store.YES), 67 | new StringField(nameof(IndexConfig.ExcludedExtensions), indexConfig.ExcludedExtensions.ToStringSafe(), Field.Store.YES), 68 | }; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Models/Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using System.Windows.Input; 5 | 6 | namespace CodeIndex.VisualStudioExtension 7 | { 8 | public class CommonCommand : ICommand 9 | { 10 | readonly Action execute; 11 | readonly Predicate canExecute; 12 | 13 | public CommonCommand(Action execute) : this(execute, null) 14 | { 15 | } 16 | 17 | public CommonCommand(Action execute, Predicate canExecute) 18 | { 19 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 20 | this.canExecute = canExecute; 21 | } 22 | 23 | [DebuggerStepThrough] 24 | public bool CanExecute(object parameters) 25 | { 26 | return canExecute == null || canExecute(parameters); 27 | } 28 | 29 | public event EventHandler CanExecuteChanged 30 | { 31 | add => CommandManager.RequerySuggested += value; 32 | remove => CommandManager.RequerySuggested -= value; 33 | } 34 | 35 | public void Execute(object parameters) 36 | { 37 | execute(parameters); 38 | } 39 | } 40 | 41 | public class AsyncCommand : ICommand 42 | { 43 | public event EventHandler CanExecuteChanged; 44 | bool isExecuting; 45 | readonly Func execute; 46 | readonly Func canExecute; 47 | readonly Action errorHandler; 48 | 49 | public AsyncCommand( 50 | Func execute, 51 | Func canExecute, 52 | Action errorHandler) 53 | { 54 | this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); 55 | this.canExecute = canExecute; 56 | this.errorHandler = errorHandler; 57 | } 58 | 59 | public bool CanExecute() 60 | { 61 | return !isExecuting && (canExecute?.Invoke() ?? true); 62 | } 63 | 64 | public async Task ExecuteAsync() 65 | { 66 | if (CanExecute()) 67 | { 68 | try 69 | { 70 | isExecuting = true; 71 | await execute(); 72 | } 73 | finally 74 | { 75 | isExecuting = false; 76 | } 77 | } 78 | 79 | RaiseCanExecuteChanged(); 80 | } 81 | 82 | public void RaiseCanExecuteChanged() 83 | { 84 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 85 | } 86 | 87 | public bool CanExecute(object parameter) 88 | { 89 | return CanExecute(); 90 | } 91 | 92 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100", Justification = "ICommand.Execute 必须为 void;内部已捕获异常,且不需切回 UI 线程")] 93 | public async void Execute(object parameter) 94 | { 95 | try 96 | { 97 | await ExecuteAsync(); 98 | } 99 | catch (Exception ex) 100 | { 101 | errorHandler?.Invoke(ex); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/IndexBuilder/CodeAnalyzerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using CodeIndex.IndexBuilder; 5 | using Lucene.Net.Analysis; 6 | using Lucene.Net.Analysis.TokenAttributes; 7 | using NUnit.Framework; 8 | 9 | namespace CodeIndex.Test 10 | { 11 | [ExcludeFromCodeCoverage] 12 | public class CodeAnalyzerTest 13 | { 14 | [Test] 15 | public void TestAnalyzer() 16 | { 17 | var content = " LucenePool.SaveResultsAndClearLucenePool(TempIndexDir);"; 18 | var result = GetTokens(new CodeAnalyzer(Constants.AppLuceneVersion, false), content); 19 | Assert.That(result, Is.EquivalentTo(new[] 20 | { 21 | "LucenePool", 22 | ".", 23 | "SaveResultsAndClearLucenePool", 24 | "(", 25 | "TempIndexDir", 26 | ")", 27 | ";" 28 | })); 29 | 30 | result = GetTokens(new CodeAnalyzer(Constants.AppLuceneVersion, true), content); 31 | Assert.That(result, Is.EquivalentTo(new[] 32 | { 33 | "lucenepool", 34 | ".", 35 | "saveresultsandclearlucenepool", 36 | "(", 37 | "tempindexdir", 38 | ")", 39 | ";" 40 | })); 41 | 42 | result = GetTokens(new CodeAnalyzer(Constants.AppLuceneVersion, false), @"Line One 43 | Line Two 44 | 45 | Line Four"); 46 | 47 | Assert.That(result, Is.EquivalentTo(new[] 48 | { 49 | "Line", 50 | "One", 51 | "Line", 52 | "Two", 53 | "Line", 54 | "Four" 55 | })); 56 | } 57 | 58 | [Test] 59 | public void TestGetWords() 60 | { 61 | var content = "It's a content for test" + Environment.NewLine + "这是一个例句,我知道了"; 62 | Assert.That(WordSegmenter.GetWords(content), Is.EquivalentTo(new[] { "It", "s", "a", "content", "for", "test", "这是一个例句", "我知道了" })); 63 | Assert.That(WordSegmenter.GetWords(content, 2, 4), Is.EquivalentTo(new[] { "It", "for", "test", "我知道了" })); 64 | Assert.That(WordSegmenter.GetWords("a".PadRight(201, 'b')), Is.Empty); 65 | 66 | Assert.Throws(() => WordSegmenter.GetWords(null)); 67 | Assert.Throws(() => WordSegmenter.GetWords(content, 0)); 68 | Assert.Throws(() => WordSegmenter.GetWords(content, 200)); 69 | Assert.Throws(() => WordSegmenter.GetWords(content, 3, 1)); 70 | Assert.Throws(() => WordSegmenter.GetWords(content, 3, -1)); 71 | Assert.Throws(() => WordSegmenter.GetWords(content, 3, 1001)); 72 | } 73 | 74 | List GetTokens(Analyzer analyzer, string content) 75 | { 76 | var tokens = new List(); 77 | using var tokenStream = analyzer.GetTokenStream("dummy", content); 78 | var termAttribute = tokenStream.AddAttribute(); 79 | tokenStream.Reset(); 80 | while (tokenStream.IncrementToken()) 81 | { 82 | tokens.Add(termAttribute.ToString()); 83 | } 84 | tokenStream.End(); 85 | return tokens; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Controls/CodeIndexSearchControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows.Controls; 4 | using System.Windows.Input; 5 | using CodeIndex.VisualStudioExtension.Resources; 6 | using EnvDTE; 7 | using Microsoft.VisualStudio.Shell; 8 | using Microsoft.VisualStudio.Shell.Interop; 9 | 10 | namespace CodeIndex.VisualStudioExtension 11 | { 12 | /// 13 | /// Interaction logic for CodeIndexSearchControl.xaml. 14 | /// 15 | [ProvideToolboxControl("CodeIndex.VisualStudioExtension.CodeIndexSearchControl", true)] 16 | public partial class CodeIndexSearchControl : UserControl 17 | { 18 | public CodeIndexSearchControl() 19 | { 20 | InitializeComponent(); 21 | } 22 | 23 | void ContentTextBox_KeyUp(object sender, KeyEventArgs e) 24 | { 25 | if (e.Key == Key.Enter) 26 | { 27 | TextBox_KeyDown(sender, e); 28 | return; 29 | } 30 | 31 | if (SearchViewModel == null) 32 | { 33 | return; 34 | } 35 | 36 | // 调度提示词获取(由 ViewModel 内部使用 JoinableTaskCollection 追踪,避免 VSSDK007 警告) 37 | SearchViewModel.ScheduleGetHintWords(); 38 | } 39 | 40 | void TextBox_KeyDown(object sender, KeyEventArgs e) 41 | { 42 | if (e.Key == Key.Enter) 43 | { 44 | if (e.KeyboardDevice.Modifiers != ModifierKeys.Control) 45 | { 46 | SearchButton.Command?.Execute(null); 47 | } 48 | } 49 | } 50 | 51 | CodeIndexSearchViewModel SearchViewModel => DataContext as CodeIndexSearchViewModel; 52 | 53 | void Row_DoubleClick(object sender, MouseButtonEventArgs e) 54 | { 55 | ThreadHelper.ThrowIfNotOnUIThread(); 56 | 57 | // Some operations with this row 58 | if (sender is DataGridRow row && row.Item is CodeSourceWithMatchedLine codeSourceWithMatchedLine) 59 | { 60 | if (File.Exists(codeSourceWithMatchedLine.CodeSource.FilePath)) 61 | { 62 | var dte = (DTE)Package.GetGlobalService(typeof(DTE)); 63 | var window = dte.ItemOperations.OpenFile(codeSourceWithMatchedLine.CodeSource.FilePath); 64 | (window.Document.Selection as TextSelection)?.GotoLine(codeSourceWithMatchedLine.MatchedLine, true); 65 | } 66 | else 67 | { 68 | // TODO: Download to local to open 69 | var result = VsShellUtilities.ShowMessageBox( 70 | ServiceProvider.GlobalProvider, 71 | Strings.Message_FileNotLocal, 72 | Strings.Message_FileNotLocalTitle, 73 | OLEMSGICON.OLEMSGICON_QUERY, 74 | OLEMSGBUTTON.OLEMSGBUTTON_YESNO, 75 | OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); 76 | 77 | if (result == 6) // IDYES 78 | { 79 | System.Diagnostics.Process.Start($"{SearchViewModel.ServiceUrl}/Details/{codeSourceWithMatchedLine.CodeSource.CodePK}/{SearchViewModel.IndexPk}/{System.Web.HttpUtility.UrlEncode(SearchViewModel.Content)}/{SearchViewModel.CaseSensitive}/{SearchViewModel.PhaseQuery}"); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndex.VisualStudioExtensionPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using Microsoft.VisualStudio.Shell; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace CodeIndex.VisualStudioExtension 8 | { 9 | /// 10 | /// This is the class that implements the package exposed by this assembly. 11 | /// 12 | /// 13 | /// 14 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 15 | /// is to implement the IVsPackage interface and register itself with the shell. 16 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 17 | /// to do it: it derives from the Package class that provides the implementation of the 18 | /// IVsPackage interface and uses the registration attributes defined in the framework to 19 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 20 | /// utility what data to put into .pkgdef file. 21 | /// 22 | /// 23 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 24 | /// 25 | /// 26 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 27 | [Guid(PackageGuidString)] 28 | [ProvideMenuResource("Menus.ctmenu", 1)] 29 | [ProvideToolWindow(typeof(CodeIndex.VisualStudioExtension.CodeIndexSearchWindow))] 30 | public sealed class VisualStudioExtensionPackage : AsyncPackage 31 | { 32 | /// 33 | /// CodeIndex.VisualStudioExtensionPackage GUID string. 34 | /// 35 | public const string PackageGuidString = "1eaddb39-c1f2-42e2-807d-234d73e8ef2a"; 36 | 37 | #region Package Members 38 | 39 | /// 40 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 41 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 42 | /// 43 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 44 | /// A provider for progress updates. 45 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 46 | UserSettings loadedSettings; 47 | 48 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 49 | { 50 | // 后台线程:可以做不需要 UI 的初始化 51 | loadedSettings = UserSettingsManager.Load(); 52 | // 注册实例(引用 + 确保启动) 53 | await Models.LocalServerLauncher.RegisterInstanceAsync(loadedSettings); 54 | 55 | // 切换到 UI 线程再注册命令 56 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 57 | await CodeIndexSearchWindowCommand.InitializeAsync(this); 58 | } 59 | 60 | protected override void Dispose(bool disposing) 61 | { 62 | try 63 | { 64 | if (disposing && loadedSettings != null) 65 | { 66 | Models.LocalServerLauncher.UnregisterInstance(loadedSettings); 67 | } 68 | } 69 | catch { } 70 | base.Dispose(disposing); 71 | } 72 | 73 | #endregion 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/CodeIndex.Common/IndexConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace CodeIndex.Common 5 | { 6 | public record IndexConfig 7 | { 8 | public const char SplitChar = '|'; 9 | public const string FilePathPlaceholder = "{FilePath}"; 10 | public const string LinePlaceholder = "{Line}"; 11 | public const string ColumnPlaceholder = "{Column}"; 12 | public Guid Pk { get; set; } = Guid.NewGuid(); 13 | public string IndexName { get; set; } = string.Empty; 14 | public string MonitorFolder { get; set; } = string.Empty; 15 | public int MaxContentHighlightLength { get; set; } = 3000000; 16 | public int SaveIntervalSeconds { get; set; } = 3; 17 | public string OpenIDEUriFormat { get; set; } = $"vscode://file/{FilePathPlaceholder}:{LinePlaceholder}:{ColumnPlaceholder}"; 18 | public string MonitorFolderRealPath { get; set; } = string.Empty; 19 | 20 | public string ExcludedPaths 21 | { 22 | get => excludedPaths; 23 | set 24 | { 25 | excludedPaths = value; 26 | excludedPathsArray = null; 27 | } 28 | } 29 | 30 | public string IncludedExtensions 31 | { 32 | get => includedExtensions; 33 | set 34 | { 35 | includedExtensions = value; 36 | includedExtensionsArray = null; 37 | } 38 | } 39 | 40 | public string ExcludedExtensions 41 | { 42 | get => excludedExtensions; 43 | set 44 | { 45 | excludedExtensions = value; 46 | excludedExtensionsArray = null; 47 | } 48 | } 49 | 50 | public string[] ExcludedPathsArray => excludedPathsArray ??= GetSplitStringArray(ExcludedPaths); 51 | 52 | public string[] IncludedExtensionsArray => includedExtensionsArray ??= GetSplitStringArray(IncludedExtensions); 53 | 54 | public string[] ExcludedExtensionsArray => excludedExtensionsArray ??= GetSplitStringArray(ExcludedExtensions); 55 | 56 | public (string CodeIndexFolder, string HintIndexFolder) GetFolders(string parentFolder) 57 | { 58 | var rootFolder = GetRootFolder(parentFolder); 59 | return (Path.Combine(rootFolder, CodeIndexConfiguration.CodeIndexFolder), Path.Combine(rootFolder, CodeIndexConfiguration.HintIndexFolder)); 60 | } 61 | 62 | public string GetRootFolder(string parentFolder) 63 | { 64 | return Path.Combine(parentFolder, CodeIndexConfiguration.CodeIndexesFolder, Pk.ToString()); 65 | } 66 | 67 | public void TrimValues() 68 | { 69 | IndexName = IndexName?.Trim(); 70 | MonitorFolder = MonitorFolder?.Trim(); 71 | OpenIDEUriFormat = OpenIDEUriFormat?.Trim(); 72 | MonitorFolderRealPath = MonitorFolderRealPath?.Trim(); 73 | ExcludedPaths = ExcludedPaths?.Trim(); 74 | IncludedExtensions = IncludedExtensions?.Trim(); 75 | ExcludedExtensions = ExcludedExtensions?.Trim(); 76 | } 77 | 78 | string[] GetSplitStringArray(string value) 79 | { 80 | if (string.IsNullOrEmpty(value)) 81 | { 82 | return Array.Empty(); 83 | } 84 | 85 | return value.Split(SplitChar, StringSplitOptions.RemoveEmptyEntries); 86 | } 87 | 88 | string[] excludedPathsArray; 89 | string[] includedExtensionsArray; 90 | string[] excludedExtensionsArray; 91 | string excludedPaths = "\\RELEASES\\|\\BIN\\|\\OBJ\\|\\DEBUGPUBLIC\\|\\PACKAGES\\|\\.GIT\\"; 92 | string includedExtensions = ".CS|.XML|.XAML|.JS|.TXT|.SQL|.CSPROJ|.SLN|.CSHTML|.RAZOR"; 93 | string excludedExtensions = ".DLL|.PBD"; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/DocumentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using CodeIndex.Common; 7 | using Lucene.Net.Documents; 8 | 9 | namespace CodeIndex.IndexBuilder 10 | { 11 | public static class DocumentConverter 12 | { 13 | static readonly ConcurrentDictionary propertiesDictionary = new ConcurrentDictionary(); 14 | 15 | public static T GetObject(this Document document) where T : new() 16 | { 17 | var type = typeof(T); 18 | var result = new T(); 19 | 20 | if (!propertiesDictionary.TryGetValue(type.FullName ?? type.Name, out var propertyInfos)) 21 | { 22 | propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite).ToArray(); 23 | 24 | propertiesDictionary.TryAdd(nameof(T), propertyInfos); 25 | } 26 | 27 | foreach (var property in propertyInfos) 28 | { 29 | property.SetValue(result, GetValue(property, document)); 30 | } 31 | 32 | return result; 33 | } 34 | 35 | static object GetValue(PropertyInfo property, Document document) 36 | { 37 | var propertyType = property.PropertyType; 38 | 39 | var value = GetValue(propertyType, document.Get(property.Name)); 40 | 41 | if (value != null) 42 | { 43 | return value; 44 | } 45 | 46 | if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) 47 | { 48 | var genericType = propertyType.GetGenericArguments().First(); 49 | var instance = Activator.CreateInstance(typeof(List<>).MakeGenericType(genericType)); 50 | var collectionValues = document.Get(property.Name).Split(IndexConfig.SplitChar).Where(u => !string.IsNullOrEmpty(u)); 51 | var method = instance.GetType().GetMethod("Add"); 52 | 53 | foreach (var sub in collectionValues) 54 | { 55 | var subValue = GetValue(genericType, sub); 56 | 57 | if (subValue == null) 58 | { 59 | throw new NotImplementedException($"Not able to set value for {property.Name}, type: {property.PropertyType}"); 60 | } 61 | 62 | method?.Invoke(instance, new[] { subValue }); 63 | } 64 | 65 | return instance; 66 | } 67 | 68 | throw new NotImplementedException($"Not able to set value for {property.Name}, type: {property.PropertyType}"); 69 | } 70 | 71 | static object GetValue(Type type, string value) 72 | { 73 | if (type == typeof(string)) 74 | { 75 | return value; 76 | } 77 | 78 | if (type == typeof(int)) 79 | { 80 | return Convert.ToInt32(value); 81 | } 82 | 83 | if (type == typeof(DateTime)) 84 | { 85 | return new DateTime(long.Parse(value)); 86 | } 87 | 88 | if (type == typeof(Guid)) 89 | { 90 | return new Guid(value); 91 | } 92 | 93 | if (type == typeof(double)) 94 | { 95 | return Convert.ToDouble(value); 96 | } 97 | 98 | if (type == typeof(float)) 99 | { 100 | return Convert.ToSingle(value); 101 | } 102 | 103 | return null; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/ProvideToolboxControlAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.VisualStudio.Shell; 5 | 6 | namespace CodeIndex.VisualStudioExtension 7 | { 8 | /// 9 | /// This attribute adds a ToolboxControlsInstaller key for the assembly to install toolbox controls from the assembly. 10 | /// 11 | /// 12 | /// For example 13 | /// [$(Rootkey)\ToolboxControlsInstaller\$FullAssemblyName$] 14 | /// "Codebase"="$path$" 15 | /// "WpfControls"="1" 16 | /// 17 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 18 | [ComVisible(false)] 19 | public sealed class ProvideToolboxControlAttribute : RegistrationAttribute 20 | { 21 | private const string ToolboxControlsInstallerPath = "ToolboxControlsInstaller"; 22 | private readonly string name; 23 | private readonly bool areWPFControls; 24 | 25 | /// 26 | /// Creates a new ProvideToolboxControl attribute to register the assembly for toolbox controls installer. 27 | /// 28 | /// The name of the toolbox controls. 29 | /// Indicates whether the toolbox controls are WPF controls. 30 | public ProvideToolboxControlAttribute(string name, bool areWPFControls) 31 | { 32 | if (name == null) 33 | { 34 | throw new ArgumentNullException(nameof(name)); 35 | } 36 | 37 | this.name = name; 38 | this.areWPFControls = areWPFControls; 39 | } 40 | 41 | /// 42 | /// Called to register this attribute with the given context. The context 43 | /// contains the location where the registration information should be placed. 44 | /// It also contains other information such as the type being registered and path information. 45 | /// 46 | /// Given context to register in. 47 | public override void Register(RegistrationContext context) 48 | { 49 | if (context == null) 50 | { 51 | throw new ArgumentNullException(nameof(context)); 52 | } 53 | 54 | using (Key key = context.CreateKey(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", 55 | ToolboxControlsInstallerPath, 56 | context.ComponentType.Assembly.FullName))) 57 | { 58 | key.SetValue(string.Empty, this.name); 59 | key.SetValue("Codebase", context.CodeBase); 60 | if (this.areWPFControls) 61 | { 62 | key.SetValue("WPFControls", "1"); 63 | } 64 | } 65 | } 66 | 67 | /// 68 | /// Called to unregister this attribute with the given context. 69 | /// 70 | /// A registration context provided by an external registration tool. 71 | /// The context can be used to remove registry keys, log registration activity, and obtain information 72 | /// about the component being registered. 73 | public override void Unregister(RegistrationContext context) 74 | { 75 | if (context != null) 76 | { 77 | context.RemoveKey(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", 78 | ToolboxControlsInstallerPath, 79 | context.ComponentType.Assembly.FullName)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/Files/FilesWatcherHelperTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using CodeIndex.Files; 6 | using NUnit.Framework; 7 | 8 | namespace CodeIndex.Test 9 | { 10 | public class FilesWatcherHelperTest : BaseTest 11 | { 12 | [Test] 13 | public void TestStartWatch() 14 | { 15 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 16 | { 17 | var renameHit = 0; 18 | var changeHit = 0; 19 | var waitMS = 100; 20 | Directory.CreateDirectory(Path.Combine(TempDir, "SubDir")); 21 | 22 | using var watcher = FilesWatcherHelper.StartWatch(TempDir, OnChangedHandler, OnRenameHandler); 23 | 24 | File.Create(Path.Combine(TempDir, "AAA.cs")).Close(); 25 | Thread.Sleep(waitMS); 26 | Assert.That(changeHit, Is.EqualTo(1)); 27 | Assert.That(renameHit, Is.EqualTo(0)); 28 | 29 | File.AppendAllText(Path.Combine(TempDir, "AAA.cs"), "12345"); 30 | Thread.Sleep(waitMS); 31 | Assert.That(changeHit, Is.EqualTo(2)); 32 | Assert.That(renameHit, Is.EqualTo(0)); 33 | 34 | File.Move(Path.Combine(TempDir, "AAA.cs"), Path.Combine(TempDir, "BBB.cs")); 35 | Thread.Sleep(waitMS); 36 | Assert.That(changeHit, Is.EqualTo(2)); 37 | Assert.That(renameHit, Is.EqualTo(1)); 38 | 39 | File.Delete(Path.Combine(TempDir, "BBB.cs")); 40 | Thread.Sleep(waitMS); 41 | Assert.That(changeHit, Is.EqualTo(3)); 42 | Assert.That(renameHit, Is.EqualTo(1)); 43 | 44 | File.Create(Path.Combine(TempDir, "SubDir", "AAA.cs")).Close(); 45 | Thread.Sleep(waitMS); 46 | Assert.That(changeHit, Is.EqualTo(4).Or.EqualTo(5), "Different behavior under different machines, not important due to logic doesn't care about the folder change events"); 47 | Assert.That(renameHit, Is.EqualTo(1)); 48 | 49 | File.AppendAllText(Path.Combine(TempDir, "SubDir", "AAA.cs"), "AA BB"); 50 | Thread.Sleep(waitMS); 51 | Assert.That(changeHit, Is.EqualTo(6), "One for folder, one for file"); 52 | Assert.That(renameHit, Is.EqualTo(1)); 53 | 54 | Directory.Move(Path.Combine(TempDir, "SubDir"), Path.Combine(TempDir, "SubDir2")); 55 | Thread.Sleep(waitMS); 56 | Assert.That(changeHit, Is.EqualTo(6)); 57 | Assert.That(renameHit, Is.EqualTo(2)); 58 | 59 | Directory.CreateDirectory(Path.Combine(TempDir, "SubDir3")); 60 | Thread.Sleep(waitMS); 61 | Assert.That(changeHit, Is.EqualTo(7)); 62 | Assert.That(renameHit, Is.EqualTo(2)); 63 | 64 | File.Create(Path.Combine(TempDir, "CCCC")).Close(); 65 | Thread.Sleep(waitMS); 66 | Assert.That(changeHit, Is.EqualTo(8)); 67 | Assert.That(renameHit, Is.EqualTo(2)); 68 | 69 | File.SetLastAccessTime(Path.Combine(TempDir, "CCCC"), DateTime.Now.AddDays(1)); 70 | Thread.Sleep(waitMS); 71 | Assert.That(changeHit, Is.EqualTo(8), "Do not watch last access time for file"); 72 | Assert.That(renameHit, Is.EqualTo(2)); 73 | 74 | void OnRenameHandler(object sender, RenamedEventArgs e) 75 | { 76 | renameHit++; 77 | } 78 | 79 | void OnChangedHandler(object sender, FileSystemEventArgs e) 80 | { 81 | changeHit++; 82 | } 83 | } 84 | else 85 | { 86 | Assert.Pass(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish CodeIndex.Server 2 | 3 | # 仅在推送以 v 开头的 tag(例如 v1.2.3)时触发 4 | on: 5 | push: 6 | tags: 7 | - v* 8 | 9 | jobs: 10 | test: 11 | name: Run Tests 12 | runs-on: windows-latest 13 | permissions: 14 | contents: write 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup .NET 10 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: '10.0.x' 23 | 24 | - name: Restore 25 | run: dotnet restore src/CodeIndex.sln 26 | 27 | - name: Build (Debug for tests) 28 | run: dotnet build src/CodeIndex.sln -c Debug --no-restore 29 | 30 | - name: Test (Debug) 31 | id: teststep 32 | run: dotnet test src/CodeIndex.Test/CodeIndex.Test.csproj -c Debug --verbosity normal 33 | 34 | - name: Delete tag on failure 35 | if: failure() 36 | shell: pwsh 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | run: | 40 | $tag = $env:GITHUB_REF_NAME 41 | Write-Host "Tests failed. Deleting tag $tag (local & remote) ..." 42 | git config user.name github-actions 43 | git config user.email github-actions@github.com 44 | git tag -d $tag 2>$null || Write-Host "Local tag $tag already absent." 45 | git push origin :refs/tags/$tag 2>$null || Write-Host "Remote tag deletion returned non-zero (maybe already gone)." 46 | Write-Host "Tag $tag deleted to prevent release." 47 | 48 | build: 49 | name: Build & Package 50 | needs: test 51 | runs-on: windows-latest 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | 56 | - name: Setup .NET 10 57 | uses: actions/setup-dotnet@v4 58 | with: 59 | dotnet-version: '10.0.x' 60 | 61 | - name: Restore (solution) 62 | run: dotnet restore src/CodeIndex.sln 63 | 64 | - name: Publish (framework-dependent) 65 | run: >- 66 | dotnet publish src/CodeIndex.Server/CodeIndex.Server.csproj -c Release 67 | -p:PublishSingleFile=false -p:SelfContained=false -o artifacts/publish-any 68 | 69 | - name: Package zip 70 | shell: pwsh 71 | run: | 72 | New-Item -ItemType Directory -Path artifacts/zips -Force | Out-Null 73 | Compress-Archive -Path artifacts/publish-any/* -DestinationPath artifacts/zips/CodeIndex.Server.zip -Force 74 | 75 | - name: Upload artifact 76 | uses: actions/upload-artifact@v4 77 | with: 78 | name: CodeIndex.Server 79 | path: artifacts/zips/CodeIndex.Server.zip 80 | if-no-files-found: error 81 | 82 | release: 83 | name: Create / Update Release 84 | needs: build 85 | runs-on: ubuntu-latest 86 | permissions: 87 | contents: write 88 | steps: 89 | - name: Download artifact 90 | uses: actions/download-artifact@v4 91 | with: 92 | name: CodeIndex.Server 93 | path: downloaded 94 | 95 | - name: List downloaded files 96 | run: ls -R downloaded 97 | 98 | - name: Create or update release 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | shell: bash 102 | run: | 103 | tag="$GITHUB_REF_NAME" 104 | asset="downloaded/CodeIndex.Server.zip" 105 | echo "Publishing release $tag with asset $asset" 106 | if gh release create "$tag" "$asset" --title "CodeIndex.Server $tag" --notes "Framework-dependent build (.exe) for $tag" -R "$GITHUB_REPOSITORY"; then 107 | echo "Release created" 108 | else 109 | echo "Release exists, updating asset..." 110 | gh release upload "$tag" "$asset" --clobber -R "$GITHUB_REPOSITORY" 111 | fi 112 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/IndexBuilder/DocumentConverterTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CodeIndex.Common; 4 | using CodeIndex.IndexBuilder; 5 | using Lucene.Net.Documents; 6 | using NUnit.Framework; 7 | 8 | namespace CodeIndex.Test 9 | { 10 | public class DocumentConverterTest 11 | { 12 | [Test] 13 | public void TestConvert() 14 | { 15 | var document = new Document 16 | { 17 | new StringField(nameof(DummyForTest.Pk), Guid.NewGuid().ToString(), Field.Store.YES), 18 | new StringField(nameof(DummyForTest.AAA), "AAA", Field.Store.YES), 19 | new StringField(nameof(DummyForTest.BBB), "32", Field.Store.YES), 20 | new StringField(nameof(DummyForTest.CCC), "32.3", Field.Store.YES), 21 | new StringField(nameof(DummyForTest.DDD), "120.0", Field.Store.YES), 22 | new Int64Field(nameof(DummyForTest.EEE), DateTime.Now.Ticks, Field.Store.YES), 23 | new StringField(nameof(DummyForTest.FFF), "A|B|C|D|E", Field.Store.YES), 24 | new StringField(nameof(DummyForTest.ReadonlyProperty), "ReadonlyProperty", Field.Store.YES), 25 | }; 26 | 27 | var dummyForTest = document.GetObject(); 28 | Assert.That(dummyForTest.Pk, Is.Not.EqualTo(Guid.Empty)); 29 | Assert.That(dummyForTest.AAA, Is.EqualTo("AAA")); 30 | Assert.That(dummyForTest.BBB, Is.EqualTo(32)); 31 | Assert.That(dummyForTest.CCC, Is.EqualTo(32.3)); 32 | Assert.That(dummyForTest.DDD, Is.EqualTo(120.0f)); 33 | Assert.That(dummyForTest.EEE, Is.Not.EqualTo(new DateTime())); 34 | Assert.That(dummyForTest.FFF, Is.EquivalentTo(new[] { "A", "B", "C", "D", "E" })); 35 | Assert.That(dummyForTest.ReadonlyProperty, Is.Null, "Don't set readonly property"); 36 | 37 | var config = new IndexConfig 38 | { 39 | ExcludedExtensions = "ABC", 40 | ExcludedPaths = "CDF", 41 | IncludedExtensions = "QQQ", 42 | IndexName = "AAA", 43 | MaxContentHighlightLength = 100, 44 | MonitorFolder = "BCD", 45 | MonitorFolderRealPath = "SSS", 46 | OpenIDEUriFormat = "DDDD", 47 | SaveIntervalSeconds = 22, 48 | Pk = Guid.NewGuid() 49 | }; 50 | 51 | document = ConfigIndexBuilder.GetDocument(config); 52 | Assert.That(document.GetObject().ToString(), Is.EqualTo(config.ToString())); 53 | } 54 | 55 | [Test] 56 | public void TestThrowException() 57 | { 58 | var document = new Document 59 | { 60 | new StringField(nameof(DummyForTest2.BlaBla), "10", Field.Store.YES), 61 | new StringField(nameof(DummyForTest3.BlaBlaEnum), "32|12", Field.Store.YES), 62 | }; 63 | 64 | Assert.That(() => document.GetObject(), Throws.TypeOf()); 65 | Assert.That(() => document.GetObject(), Throws.TypeOf()); 66 | } 67 | 68 | class DummyForTest 69 | { 70 | public Guid Pk { get; set; } 71 | public string AAA { get; set; } 72 | public int BBB { get; set; } 73 | public double CCC { get; set; } 74 | public float DDD { get; set; } 75 | public DateTime EEE { get; set; } 76 | public IEnumerable FFF { get; set; } 77 | public string ReadonlyProperty { get; } 78 | } 79 | 80 | class DummyForTest2 81 | { 82 | public decimal BlaBla { get; set; } 83 | } 84 | 85 | class DummyForTest3 86 | { 87 | public IEnumerable BlaBlaEnum { get; set; } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/MaintainIndex/ConfigIndexMaintainerTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CodeIndex.Common; 3 | using CodeIndex.MaintainIndex; 4 | using NUnit.Framework; 5 | 6 | namespace CodeIndex.Test 7 | { 8 | public class ConfigIndexMaintainerTest : BaseTest 9 | { 10 | [Test] 11 | public void TestConstructor() 12 | { 13 | using var maintainer = new ConfigIndexMaintainer(Config, Log); 14 | Assert.That(Directory.Exists(Path.Combine(Config.LuceneIndex, CodeIndexConfiguration.ConfigurationIndexFolder)), Is.True); 15 | } 16 | 17 | [Test] 18 | public void TestGetConfigs() 19 | { 20 | using var maintainer = new ConfigIndexMaintainer(Config, Log); 21 | Assert.That(maintainer.GetConfigs(), Is.Empty); 22 | 23 | var indexConfig = new IndexConfig 24 | { 25 | IndexName = "ABC" 26 | }; 27 | 28 | maintainer.AddIndexConfig(indexConfig); 29 | 30 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig })); 31 | } 32 | 33 | [Test] 34 | public void TestAddConfig() 35 | { 36 | using var maintainer = new ConfigIndexMaintainer(Config, Log); 37 | Assert.That(maintainer.GetConfigs(), Is.Empty); 38 | 39 | var indexConfig1 = new IndexConfig 40 | { 41 | IndexName = "ABC" 42 | }; 43 | 44 | var indexConfig2 = new IndexConfig 45 | { 46 | IndexName = "BCD" 47 | }; 48 | 49 | maintainer.AddIndexConfig(indexConfig1); 50 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig1 })); 51 | 52 | maintainer.AddIndexConfig(indexConfig2); 53 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig1, indexConfig2 })); 54 | } 55 | 56 | [Test] 57 | public void TestEditConfig() 58 | { 59 | using var maintainer = new ConfigIndexMaintainer(Config, Log); 60 | var indexConfig = new IndexConfig 61 | { 62 | IndexName = "ABC" 63 | }; 64 | 65 | maintainer.AddIndexConfig(indexConfig); 66 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig })); 67 | 68 | maintainer.EditIndexConfig(indexConfig with { IndexName = "EFG" }); 69 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig with { IndexName = "EFG" } })); 70 | } 71 | 72 | [Test] 73 | public void TestDeleteConfig() 74 | { 75 | using var maintainer = new ConfigIndexMaintainer(Config, Log); 76 | var indexConfig = new IndexConfig 77 | { 78 | IndexName = "ABC" 79 | }; 80 | 81 | maintainer.AddIndexConfig(indexConfig); 82 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig })); 83 | 84 | maintainer.DeleteIndexConfig(indexConfig.Pk); 85 | Assert.That(maintainer.GetConfigs(), Is.Empty); 86 | } 87 | 88 | [Test] 89 | public void TestIndexPersistent() 90 | { 91 | var indexConfig = new IndexConfig 92 | { 93 | IndexName = "ABC" 94 | }; 95 | 96 | using (var maintainer = new ConfigIndexMaintainer(Config, Log)) 97 | { 98 | maintainer.AddIndexConfig(indexConfig); 99 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig })); 100 | } 101 | 102 | using (var maintainer = new ConfigIndexMaintainer(Config, Log)) 103 | { 104 | Assert.That(maintainer.GetConfigs(), Is.EquivalentTo(new[] { indexConfig })); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.github/workflows/build-vsix.yml: -------------------------------------------------------------------------------- 1 | name: Publish VSIX 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build: 10 | name: Build VSIX 11 | runs-on: windows-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Setup MSBuild (add to PATH) 16 | uses: microsoft/setup-msbuild@v2 17 | - name: Locate MSBuild 18 | id: msbuild 19 | shell: pwsh 20 | run: | 21 | $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" 22 | if(-not (Test-Path $vswhere)) { throw "vswhere not found at $vswhere" } 23 | $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 24 | if(-not $msbuild){ throw 'MSBuild not found' } 25 | "path=$msbuild" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 26 | - name: Restore (solution) 27 | shell: pwsh 28 | run: | 29 | dotnet restore src/CodeIndex.VisualStudioExtension/CodeIndex.VisualStudioExtension.sln 30 | - name: Build VSIX (Release) 31 | shell: pwsh 32 | run: | 33 | & '${{ steps.msbuild.outputs.path }}' src/CodeIndex.VisualStudioExtension/CodeIndex.VisualStudioExtension.csproj /t:Build /p:Configuration=Release /nologo 34 | - name: Locate VSIX 35 | id: locate_vsix 36 | shell: pwsh 37 | run: | 38 | $outDir = "src/CodeIndex.VisualStudioExtension/bin/Release" 39 | if(-not (Test-Path $outDir)) { throw "Output directory not found: $outDir" } 40 | Write-Host "Listing VSIX output directory contents:"; Get-ChildItem -Path $outDir | Format-Table Name,Length,LastWriteTime 41 | $vsix = Get-ChildItem -Path $outDir -Filter *.vsix -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1 42 | if(-not $vsix) { 43 | Write-Host 'No VSIX found; dumping recursive tree:' 44 | Get-ChildItem -Path $outDir -Recurse | Select-Object FullName,Length,LastWriteTime | Format-Table -AutoSize 45 | throw 'VSIX file not generated.' 46 | } 47 | Write-Host "Found VSIX: $($vsix.FullName)" 48 | if(-not (Test-Path 'artifacts')) { New-Item -ItemType Directory -Path artifacts | Out-Null } 49 | $dest = Join-Path -Path (Resolve-Path 'artifacts').Path -ChildPath $vsix.Name 50 | Copy-Item -Path $vsix.FullName -Destination $dest -Force -ErrorAction Stop 51 | Write-Host "Copied to $dest" 52 | "path=$dest" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: CodeIndex.VSIX 57 | path: ${{ steps.locate_vsix.outputs.path }} 58 | if-no-files-found: error 59 | 60 | release: 61 | name: Create / Update Release 62 | needs: build 63 | runs-on: ubuntu-latest 64 | permissions: 65 | contents: write 66 | steps: 67 | - name: Download artifact 68 | uses: actions/download-artifact@v4 69 | with: 70 | name: CodeIndex.VSIX 71 | path: downloaded 72 | - name: List downloaded files 73 | run: ls -R downloaded 74 | - name: Create or update release 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | shell: bash 78 | run: | 79 | tag="$GITHUB_REF_NAME" 80 | asset=$(ls downloaded/*.vsix | head -n1) 81 | if [ -z "$asset" ]; then 82 | echo "No VSIX asset found" >&2 83 | exit 1 84 | fi 85 | echo "Publishing release $tag with asset $asset" 86 | if gh release create "$tag" "$asset" --title "VSIX $tag" --notes "VSIX build for $tag" -R "$GITHUB_REPOSITORY"; then 87 | echo "Release created" 88 | else 89 | echo "Release exists, updating asset..." 90 | gh release upload "$tag" "$asset" --clobber -R "$GITHUB_REPOSITORY" 91 | fi 92 | -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/CodeIndexSearchWindowCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using Task = System.Threading.Tasks.Task; 5 | 6 | namespace CodeIndex.VisualStudioExtension 7 | { 8 | /// 9 | /// Command handler 10 | /// 11 | internal sealed class CodeIndexSearchWindowCommand 12 | { 13 | /// 14 | /// Command ID. 15 | /// 16 | public const int CommandId = 4129; 17 | 18 | /// 19 | /// Command menu group (command set GUID). 20 | /// 21 | public static readonly Guid CommandSet = new Guid("c025b4ef-e6ed-42b4-9c22-2d6835421d25"); 22 | 23 | /// 24 | /// VS Package that provides this command, not null. 25 | /// 26 | private readonly AsyncPackage package; 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// Adds our command handlers for menu (commands must exist in the command table file) 31 | /// 32 | /// Owner package, not null. 33 | /// Command service to add command to, not null. 34 | private CodeIndexSearchWindowCommand(AsyncPackage package, OleMenuCommandService commandService) 35 | { 36 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 37 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 38 | 39 | var menuCommandID = new CommandID(CommandSet, CommandId); 40 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 41 | commandService.AddCommand(menuItem); 42 | 43 | } 44 | 45 | /// 46 | /// Gets the instance of the command. 47 | /// 48 | public static CodeIndexSearchWindowCommand Instance 49 | { 50 | get; 51 | private set; 52 | } 53 | 54 | /// 55 | /// Gets the service provider from the owner package. 56 | /// 57 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider 58 | { 59 | get 60 | { 61 | return this.package; 62 | } 63 | } 64 | 65 | /// 66 | /// Initializes the singleton instance of the command. 67 | /// 68 | /// Owner package, not null. 69 | public static async Task InitializeAsync(AsyncPackage package) 70 | { 71 | // Switch to the main thread - the call to AddCommand in CodeIndexSearchWindowCommand's constructor requires 72 | // the UI thread. 73 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 74 | 75 | OleMenuCommandService commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService; 76 | Instance = new CodeIndexSearchWindowCommand(package, commandService); 77 | } 78 | 79 | /// 80 | /// Shows the tool window when the menu item is clicked. 81 | /// 82 | /// The event sender. 83 | /// The event args. 84 | private void Execute(object sender, EventArgs e) 85 | { 86 | this.package.JoinableTaskFactory.RunAsync(async delegate 87 | { 88 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 89 | ToolWindowPane window = await this.package.ShowToolWindowAsync(typeof(CodeIndexSearchWindow), 0, true, this.package.DisposalToken); 90 | if (window?.Frame == null) 91 | { 92 | throw new NotSupportedException("Cannot create tool window"); 93 | } 94 | }).FileAndForget("CodeIndex/ShowToolWindow"); 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Connected Services/CodeIndexService/CodeIndexClient.nswag: -------------------------------------------------------------------------------- 1 | { 2 | "runtime": "NetCore21", 3 | "defaultVariables": null, 4 | "documentGenerator": { 5 | "fromDocument": { 6 | "url": "https://localhost:5001/swagger/v1/swagger.json", 7 | "output": "CodeIndexClient.nswag.json", 8 | "newLineBehavior": "Auto" 9 | } 10 | }, 11 | "codeGenerators": { 12 | "openApiToCSharpClient": { 13 | "clientBaseClass": null, 14 | "configurationClass": null, 15 | "generateClientClasses": true, 16 | "generateClientInterfaces": false, 17 | "clientBaseInterface": null, 18 | "injectHttpClient": true, 19 | "disposeHttpClient": true, 20 | "protectedMethods": [], 21 | "generateExceptionClasses": true, 22 | "exceptionClass": "ApiException", 23 | "wrapDtoExceptions": true, 24 | "useHttpClientCreationMethod": false, 25 | "httpClientType": "System.Net.Http.HttpClient", 26 | "useHttpRequestMessageCreationMethod": false, 27 | "useBaseUrl": true, 28 | "generateBaseUrlProperty": false, 29 | "generateSyncMethods": false, 30 | "exposeJsonSerializerSettings": false, 31 | "clientClassAccessModifier": "public", 32 | "typeAccessModifier": "public", 33 | "generateContractsOutput": false, 34 | "contractsNamespace": null, 35 | "contractsOutputFilePath": null, 36 | "parameterDateTimeFormat": "s", 37 | "parameterDateFormat": "yyyy-MM-dd", 38 | "generateUpdateJsonSerializerSettingsMethod": true, 39 | "useRequestAndResponseSerializationSettings": false, 40 | "serializeTypeInformation": false, 41 | "queryNullValue": "", 42 | "className": "CodeIndexClient", 43 | "operationGenerationMode": "SingleClientFromPathSegments", 44 | "additionalNamespaceUsages": [], 45 | "additionalContractNamespaceUsages": [], 46 | "generateOptionalParameters": false, 47 | "generateJsonMethods": false, 48 | "enforceFlagEnums": false, 49 | "parameterArrayType": "System.Collections.Generic.IEnumerable", 50 | "parameterDictionaryType": "System.Collections.Generic.IDictionary", 51 | "responseArrayType": "System.Collections.Generic.ICollection", 52 | "responseDictionaryType": "System.Collections.Generic.IDictionary", 53 | "wrapResponses": false, 54 | "wrapResponseMethods": [], 55 | "generateResponseClasses": true, 56 | "responseClass": "SwaggerResponse", 57 | "namespace": "CodeIndex.VisualStudioExtension", 58 | "requiredPropertiesMustBeDefined": true, 59 | "dateType": "System.DateTimeOffset", 60 | "jsonConverters": null, 61 | "anyType": "object", 62 | "dateTimeType": "System.DateTimeOffset", 63 | "timeType": "System.TimeSpan", 64 | "timeSpanType": "System.TimeSpan", 65 | "arrayType": "System.Collections.Generic.ICollection", 66 | "arrayInstanceType": "System.Collections.ObjectModel.Collection", 67 | "dictionaryType": "System.Collections.Generic.IDictionary", 68 | "dictionaryInstanceType": "System.Collections.Generic.Dictionary", 69 | "arrayBaseType": "System.Collections.ObjectModel.Collection", 70 | "dictionaryBaseType": "System.Collections.Generic.Dictionary", 71 | "classStyle": "Poco", 72 | "generateDefaultValues": true, 73 | "generateDataAnnotations": true, 74 | "excludedTypeNames": [], 75 | "excludedParameterNames": [], 76 | "handleReferences": false, 77 | "generateImmutableArrayProperties": false, 78 | "generateImmutableDictionaryProperties": false, 79 | "jsonSerializerSettingsTransformationMethod": null, 80 | "inlineNamedArrays": false, 81 | "inlineNamedDictionaries": false, 82 | "inlineNamedTuples": true, 83 | "inlineNamedAny": false, 84 | "generateDtoTypes": true, 85 | "generateOptionalPropertiesAsNullable": false, 86 | "generateNullableReferenceTypes": false, 87 | "templateDirectory": null, 88 | "typeNameGeneratorType": null, 89 | "propertyNameGeneratorType": null, 90 | "enumNameGeneratorType": null, 91 | "serviceHost": null, 92 | "serviceSchemes": null, 93 | "output": "CodeIndexClient.cs", 94 | "newLineBehavior": "Auto" 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/CodeIndex.Server/Data/CaptchaImageUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using SixLabors.Fonts; 5 | using SixLabors.ImageSharp; 6 | using SixLabors.ImageSharp.Drawing.Processing; 7 | using SixLabors.ImageSharp.PixelFormats; 8 | using SixLabors.ImageSharp.Processing; 9 | 10 | namespace CodeIndex.Server 11 | { 12 | public class CaptchaImageUtils 13 | { 14 | public static byte[] GenerateCaptchaImage(int width, int height, string captchaCode, Random random) 15 | { 16 | var fontSize = GetFontSize(width, captchaCode.Length); 17 | var fondFamily = SystemFonts.Collection.Families.FirstOrDefault(u => u.Name == "Consolas"); 18 | fondFamily = fondFamily == default ? SystemFonts.Collection.Families.Last() : fondFamily; 19 | var font = SystemFonts.CreateFont(fondFamily.Name, fontSize); 20 | 21 | using var image = new Image(width, height, GetRandomLightColor(random)); 22 | DrawCaptchaCode(height, captchaCode, fontSize, font, random, image); 23 | DrawDisorderLine(width, height, image, random); 24 | 25 | using var ms = new MemoryStream(); 26 | image.SaveAsPng(ms); 27 | return ms.ToArray(); 28 | } 29 | 30 | static void DrawCaptchaCode(int height, string captchaCode, int fontSize, Font font, Random random, Image image) 31 | { 32 | for (int i = 0; i < captchaCode.Length; i++) 33 | { 34 | var shiftPx = fontSize / 6; 35 | var x = random.Next(-shiftPx, shiftPx) + random.Next(-shiftPx, shiftPx); 36 | if (x < 0 && i == 0) 37 | { 38 | x = 0; 39 | } 40 | 41 | x += i * fontSize; 42 | 43 | var maxY = height - fontSize; 44 | if (maxY < 0) 45 | { 46 | maxY = 0; 47 | } 48 | 49 | var y = random.Next(0, maxY); 50 | 51 | image.Mutate(operation => operation.DrawText(captchaCode[i].ToString(), font, GetRandomDeepColor(random), new PointF(x, y))); 52 | } 53 | } 54 | 55 | static Color GetRandomDeepColor(Random random) 56 | { 57 | var redlow = 160; 58 | var greenLow = 100; 59 | var blueLow = 160; 60 | return Color.FromRgb((byte)random.Next(redlow), (byte)random.Next(greenLow), (byte)random.Next(blueLow)); 61 | } 62 | 63 | static Color GetRandomLightColor(Random random) 64 | { 65 | const int low = 200; 66 | const int high = 255; 67 | 68 | var nRend = random.Next(high) % (high - low) + low; 69 | var nGreen = random.Next(high) % (high - low) + low; 70 | var nBlue = random.Next(high) % (high - low) + low; 71 | 72 | return Color.FromRgb((byte)nRend, (byte)nGreen, (byte)nBlue); 73 | } 74 | 75 | static int GetFontSize(int imageWidth, int captchCodeCount) 76 | { 77 | var averageSize = imageWidth / captchCodeCount; 78 | 79 | return Convert.ToInt32(averageSize); 80 | } 81 | 82 | static void DrawDisorderLine(int width, int height, Image graphics, Random random) 83 | { 84 | for (int i = 0; i < random.Next(3, 5); i++) 85 | { 86 | var linePen = new SolidPen(new SolidBrush(GetRandomLightColor(random)), 3); 87 | var startPoint = new Point(random.Next(0, width), random.Next(0, height)); 88 | var endPoint = new Point(random.Next(0, width), random.Next(0, height)); 89 | graphics.Mutate(operation => operation.DrawLine(linePen, startPoint, endPoint)); 90 | 91 | //var bezierPoint1 = new Point(random.Next(0, width), random.Next(0, height)); 92 | //var bezierPoint2 = new Point(random.Next(0, width), random.Next(0, height)); 93 | //var bezierPoint3 = new Point(random.Next(0, width), random.Next(0, height)); 94 | //var bezierPoint4 = new Point(random.Next(0, width), random.Next(0, height)); 95 | //graphics.Mutate(operation => operation.DrawBeziers(linePen, bezierPoint1, bezierPoint2, bezierPoint3, bezierPoint4)); 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/CodeIndex.Server/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /src/CodeIndex.Test/IndexBuilder/ConfigIndexBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CodeIndex.Common; 4 | using CodeIndex.IndexBuilder; 5 | using NUnit.Framework; 6 | 7 | namespace CodeIndex.Test 8 | { 9 | public class ConfigIndexBuilderTest : BaseTest 10 | { 11 | [Test] 12 | public void TestGetConfigs() 13 | { 14 | using var configBuilder = new ConfigIndexBuilder(TempConfigDir); 15 | configBuilder.AddIndexConfig(new IndexConfig 16 | { 17 | IndexName = "ABC" 18 | }); 19 | 20 | Assert.That(configBuilder.GetConfigs().First().IndexName, Is.EqualTo("ABC")); 21 | } 22 | 23 | [Test] 24 | public void TestAddIndexConfig() 25 | { 26 | using var configBuilder = new ConfigIndexBuilder(TempConfigDir); 27 | configBuilder.AddIndexConfig(new IndexConfig 28 | { 29 | IndexName = "ABC" 30 | }); 31 | 32 | Assert.That(configBuilder.GetConfigs().First().IndexName, Is.EqualTo("ABC")); 33 | 34 | configBuilder.AddIndexConfig(new IndexConfig 35 | { 36 | IndexName = "EFG" 37 | }); 38 | 39 | Assert.That(configBuilder.GetConfigs().Select(u => u.IndexName), Is.EquivalentTo(new[] { "ABC", "EFG" })); 40 | } 41 | 42 | [Test] 43 | public void TestDeleteIndexConfig() 44 | { 45 | using var configBuilder = new ConfigIndexBuilder(TempConfigDir); 46 | 47 | var config1 = new IndexConfig 48 | { 49 | IndexName = "ABC" 50 | }; 51 | 52 | var config2 = new IndexConfig 53 | { 54 | IndexName = "EFG" 55 | }; 56 | 57 | configBuilder.AddIndexConfig(config1); 58 | configBuilder.AddIndexConfig(config2); 59 | Assert.That(configBuilder.GetConfigs().Count(), Is.EqualTo(2)); 60 | 61 | configBuilder.DeleteIndexConfig(config2.Pk); 62 | Assert.That(configBuilder.GetConfigs().Select(u => u.IndexName), Is.EquivalentTo(new[] { "ABC" })); 63 | 64 | configBuilder.DeleteIndexConfig(config1.Pk); 65 | Assert.That(configBuilder.GetConfigs().Count(), Is.EqualTo(0)); 66 | } 67 | 68 | [Test] 69 | public void TestEditIndexConfig() 70 | { 71 | using var configBuilder = new ConfigIndexBuilder(TempConfigDir); 72 | 73 | var config1 = new IndexConfig 74 | { 75 | IndexName = "ABC" 76 | }; 77 | 78 | var config2 = new IndexConfig 79 | { 80 | IndexName = "EFG" 81 | }; 82 | 83 | configBuilder.AddIndexConfig(config1); 84 | configBuilder.AddIndexConfig(config2); 85 | Assert.That(configBuilder.GetConfigs().Count(), Is.EqualTo(2)); 86 | 87 | config1.IndexName = "NEW"; 88 | configBuilder.EditIndexConfig(config1); 89 | Assert.That(configBuilder.GetConfigs().Select(u => u.IndexName), Is.EquivalentTo(new[] { "NEW", "EFG" })); 90 | 91 | config2.IndexName = "NEW NEW"; 92 | configBuilder.EditIndexConfig(config2); 93 | Assert.That(configBuilder.GetConfigs().Select(u => u.IndexName), Is.EquivalentTo(new[] { "NEW", "NEW NEW" })); 94 | } 95 | 96 | [Test] 97 | public void TestGetDocumet() 98 | { 99 | var config = new IndexConfig 100 | { 101 | ExcludedExtensions = "ABC", 102 | ExcludedPaths = "CDF", 103 | IncludedExtensions = "QQQ", 104 | IndexName = "AAA", 105 | MaxContentHighlightLength = 100, 106 | MonitorFolder = "BCD", 107 | MonitorFolderRealPath = "SSS", 108 | OpenIDEUriFormat = "DDDD", 109 | SaveIntervalSeconds = 22, 110 | Pk = Guid.NewGuid() 111 | }; 112 | 113 | var document = ConfigIndexBuilder.GetDocument(config); 114 | Assert.That(document.Fields.Count, Is.EqualTo(10)); 115 | 116 | var configConvertBack = document.GetObject(); 117 | Assert.That(configConvertBack.ToString(), Is.EqualTo(config.ToString())); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/CodeIndex.IndexBuilder/IndexBuilderHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CodeIndex.Common; 3 | using Lucene.Net.Documents; 4 | using Lucene.Net.Index; 5 | using Lucene.Net.Store; 6 | 7 | namespace CodeIndex.IndexBuilder 8 | { 9 | public static class IndexBuilderHelper 10 | { 11 | public static string ToStringSafe(this string value) 12 | { 13 | return value ?? string.Empty; 14 | } 15 | 16 | public static string ToLowerSafe(this string value) 17 | { 18 | return value?.ToLowerInvariant() ?? string.Empty; 19 | } 20 | 21 | public static Document GetDocumentFromSource(CodeSource source) 22 | { 23 | return new Document 24 | { 25 | new TextField(nameof(source.FileName), source.FileName.ToStringSafe(), Field.Store.YES), 26 | // StringField indexes but doesn't tokenize 27 | new StringField(nameof(source.FileExtension), source.FileExtension.ToLowerSafe(), Field.Store.YES), 28 | new StringField(nameof(source.FilePath) + Constants.NoneTokenizeFieldSuffix, source.FilePath.ToStringSafe(), Field.Store.YES), 29 | new TextField(nameof(source.FilePath), source.FilePath.ToStringSafe(), Field.Store.YES), 30 | new TextField(nameof(source.Content), source.Content.ToStringSafe(), Field.Store.YES), 31 | new TextField(CodeIndexBuilder.GetCaseSensitiveField(nameof(source.Content)), source.Content.ToStringSafe(), Field.Store.YES), 32 | new Int64Field(nameof(source.IndexDate), source.IndexDate.Ticks, Field.Store.YES), 33 | new Int64Field(nameof(source.LastWriteTimeUtc), source.LastWriteTimeUtc.Ticks, Field.Store.YES), 34 | new StringField(nameof(source.CodePK), source.CodePK, Field.Store.YES) 35 | }; 36 | } 37 | 38 | public static bool IndexExists(string luceneIndex) 39 | { 40 | luceneIndex.RequireNotNullOrEmpty(nameof(luceneIndex)); 41 | 42 | using var dir = FSDirectory.Open(luceneIndex); 43 | var indexExist = DirectoryReader.IndexExists(dir); 44 | 45 | return indexExist; 46 | } 47 | 48 | public static Document RenameIndexForFile(this Document document, string nowFilePath) 49 | { 50 | document.RemoveField(nameof(CodeSource.FilePath)); 51 | document.RemoveField(nameof(CodeSource.FilePath) + Constants.NoneTokenizeFieldSuffix); 52 | document.Add(new TextField(nameof(CodeSource.FilePath), nowFilePath.ToStringSafe(), Field.Store.YES)); 53 | document.Add(new StringField(nameof(CodeSource.FilePath) + Constants.NoneTokenizeFieldSuffix, nowFilePath.ToStringSafe(), Field.Store.YES)); 54 | 55 | var oldExtension = document.Get(nameof(CodeSource.FileExtension)); 56 | var fileInfo = new FileInfo(nowFilePath); 57 | var nowExtension = fileInfo.Extension?.Replace(".", string.Empty).ToLowerInvariant() ?? string.Empty; 58 | 59 | if (oldExtension != nowExtension) 60 | { 61 | document.RemoveField(nameof(CodeSource.FileExtension)); 62 | document.Add(new StringField(nameof(CodeSource.FileExtension), nowExtension.ToLowerSafe(), Field.Store.YES)); 63 | } 64 | 65 | var oldFileName = document.Get(nameof(CodeSource.FileName)); 66 | if (oldFileName != fileInfo.Name) 67 | { 68 | document.RemoveField(nameof(CodeSource.FileName)); 69 | document.Add(new TextField(nameof(CodeSource.FileName), fileInfo.Name.ToStringSafe(), Field.Store.YES)); 70 | } 71 | 72 | return document; 73 | } 74 | 75 | public static Document RenameIndexForFolder(this Document document, string oldFolderPath, string nowFolderPath) 76 | { 77 | var pathField = document.Get(nameof(CodeSource.FilePath)); 78 | var nowPath = pathField.Replace(oldFolderPath, nowFolderPath); 79 | document.RemoveField(nameof(CodeSource.FilePath)); 80 | document.RemoveField(nameof(CodeSource.FilePath) + Constants.NoneTokenizeFieldSuffix); 81 | document.Add(new TextField(nameof(CodeSource.FilePath), nowPath.ToStringSafe(), Field.Store.YES)); 82 | document.Add(new StringField(nameof(CodeSource.FilePath) + Constants.NoneTokenizeFieldSuffix, nowPath.ToStringSafe(), Field.Store.YES)); 83 | 84 | return document; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/CodeIndex.VisualStudioExtension/Models/UserSettingsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | 5 | namespace CodeIndex.VisualStudioExtension 6 | { 7 | public enum ServerMode 8 | { 9 | Remote = 0, 10 | Local = 1 11 | } 12 | 13 | public class UserSettings 14 | { 15 | // 旧版本字段:仍然保留用于向后兼容(Remote 模式 URL) 16 | public string ServiceUrl { get; set; } = "http://localhost:5000"; 17 | 18 | // 新增:区分当前服务器模式 19 | public ServerMode Mode { get; set; } = ServerMode.Remote; 20 | 21 | // 远程服务器 URL(如果与旧 ServiceUrl 不同,可独立设置) 22 | public string RemoteServiceUrl { get; set; } = "http://localhost:5000"; 23 | 24 | // 本地服务器监听 URL(下载/启动后可固定,如 http://localhost:58080) 25 | public string LocalServiceUrl { get; set; } = "http://localhost:58080"; 26 | 27 | // 本地服务器安装根目录(下载、解压存放位置) 28 | public string LocalServerInstallPath { get; set; } = string.Empty; 29 | 30 | // 本地服务器数据目录(索引数据) 31 | public string LocalServerDataDirectory { get; set; } = string.Empty; 32 | 33 | // 记录最近一次成功安装的服务器版本(用于判断是否需要重新下载) 34 | public string LocalServerVersion { get; set; } = string.Empty; 35 | } 36 | 37 | internal static class UserSettingsManager 38 | { 39 | static readonly object Locker = new(); 40 | static UserSettings cached; 41 | internal static string SettingsDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CodeIndex.VisualStudioExtension"); 42 | internal static string SettingsFile => Path.Combine(SettingsDirectory, "codeindex.user.settings.json"); 43 | 44 | public static UserSettings Load() 45 | { 46 | lock (Locker) 47 | { 48 | if (cached != null) 49 | { 50 | return cached; 51 | } 52 | 53 | try 54 | { 55 | if (File.Exists(SettingsFile)) 56 | { 57 | cached = JsonConvert.DeserializeObject(File.ReadAllText(SettingsFile)) ?? new UserSettings(); 58 | PostLoadBackFill(cached); 59 | } 60 | else 61 | { 62 | cached = new UserSettings(); 63 | // 兼容旧版本:尝试迁移原有配置文件中的 ServiceUrl 64 | try 65 | { 66 | var legacy = ConfigHelper.Configuration?.AppSettings?.Settings?[nameof(UserSettings.ServiceUrl)]?.Value; 67 | if (!string.IsNullOrWhiteSpace(legacy)) 68 | { 69 | cached.ServiceUrl = legacy; 70 | cached.RemoteServiceUrl = legacy; // 同步到新字段 71 | Save(cached); // 立即保存迁移 72 | } 73 | } 74 | catch { /* 忽略迁移异常 */ } 75 | PostLoadBackFill(cached); 76 | } 77 | } 78 | catch 79 | { 80 | cached = new UserSettings(); 81 | PostLoadBackFill(cached); 82 | } 83 | 84 | return cached; 85 | } 86 | } 87 | 88 | public static void Save(UserSettings settings) 89 | { 90 | lock (Locker) 91 | { 92 | try 93 | { 94 | Directory.CreateDirectory(SettingsDirectory); 95 | File.WriteAllText(SettingsFile, JsonConvert.SerializeObject(settings, Formatting.Indented)); 96 | cached = settings; 97 | } 98 | catch 99 | { 100 | // 记录日志可选:当前扩展无集中日志设施 101 | } 102 | } 103 | } 104 | 105 | static void PostLoadBackFill(UserSettings s) 106 | { 107 | // Back-fill 逻辑:旧版本只有 ServiceUrl 108 | if (string.IsNullOrWhiteSpace(s.RemoteServiceUrl)) 109 | { 110 | s.RemoteServiceUrl = s.ServiceUrl; 111 | } 112 | if (string.IsNullOrWhiteSpace(s.LocalServiceUrl)) 113 | { 114 | s.LocalServiceUrl = "http://localhost:58080"; 115 | } 116 | } 117 | } 118 | } --------------------------------------------------------------------------------