From 76535342872790159639ef5e6a7141905ed162e6 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Fri, 30 Jun 2017 22:27:09 -0700 Subject: [PATCH 01/19] Fix template zip paths for VisualRust --- src/VisualRust/VisualRust.projitems | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualRust/VisualRust.projitems b/src/VisualRust/VisualRust.projitems index 8751b0d1..d6b4ac24 100644 --- a/src/VisualRust/VisualRust.projitems +++ b/src/VisualRust/VisualRust.projitems @@ -9,15 +9,15 @@ VisualRust - + ProjectTemplates\ApplicationProject.zip true - + ProjectTemplates\LibraryProject.zip true - + ItemTemplates\Module.zip true From 6323a84b3f329a131dabac36547acdc0315913f1 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Fri, 30 Jun 2017 22:33:57 -0700 Subject: [PATCH 02/19] Not all cargo build JSON lines are messages. Add some null checks instead of crashing on non-messages. This fixes a crash for me on cargo 0.19.0 (28d1d60d4 2017-05-16), example output follows: I:\home\projects\test\testrust>cargo build --message-format=json 2>NUL {"features":["default","use_std"],"filenames":["I:\\home\\projects\\test\\testrust\\target\\debug\\deps\\liblibc-f4ccf2fa58092d85.rlib"],"fresh":true,"package_id":"libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)","profile":{"debug_assertions":true,"debuginfo":2,"opt_level":"0","overflow_checks":true,"test":false},"reason":"compiler-artifact","target":{"crate_types":["lib"],"kind":["lib"],"name":"libc","src_path":"C:\\Users\\MikeR\\.cargo\\registry\\src\\github.colasdn.workers.dev-1ecc6299db9ec823\\libc-0.2.24\\src\\lib.rs"}} {"features":[],"filenames":["I:\\home\\projects\\test\\testrust\\target\\debug\\deps\\librand-76456d761c2cb637.rlib"],"fresh":true,"package_id":"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)","profile":{"debug_assertions":true,"debuginfo":2,"opt_level":"0","overflow_checks":true,"test":false},"reason":"compiler-artifact","target":{"crate_types":["lib"],"kind":["lib"],"name":"rand","src_path":"C:\\Users\\MikeR\\.cargo\\registry\\src\\github.colasdn.workers.dev-1ecc6299db9ec823\\rand-0.3.15\\src\\lib.rs"}} {"message":{"children":[],"code":{"code":"E0425","explanation":"\nAn unresolved name was used.\n\nErroneous code examples:\n\n```compile_fail,E0425\nsomething_that_doesnt_exist::foo;\n// error: unresolved name `something_that_doesnt_exist::foo`\n\n// or:\n\ntrait Foo {\n fn bar() {\n Self; // error: unresolved name `Self`\n }\n}\n\n// or:\n\nlet x = unknown_variable; // error: unresolved name `unknown_variable`\n```\n\nPlease verify that the name wasn't misspelled and ensure that the\nidentifier being referred to is valid for the given situation. Example:\n\n```\nenum something_that_does_exist {\n Foo,\n}\n```\n\nOr:\n\n```\nmod something_that_does_exist {\n pub static foo : i32 = 0i32;\n}\n\nsomething_that_does_exist::foo; // ok!\n```\n\nOr:\n\n```\nlet unknown_variable = 12u32;\nlet x = unknown_variable; // ok!\n```\n\nIf the item is not defined in the current module, it must be imported using a\n`use` statement, like so:\n\n```ignore\nuse foo::bar;\nbar();\n```\n\nIf the item you are importing is not defined in some super-module of the\ncurrent module, then it must also be declared as public (e.g., `pub fn`).\n"},"level":"error","message":"cannot find value `guess_no2` in this scope","rendered":null,"spans":[{"byte_end":604,"byte_start":595,"column_end":23,"column_start":14,"expansion":null,"file_name":"src\\main.rs","is_primary":true,"label":"did you mean `guess_no`?","line_end":24,"line_start":24,"suggested_replacement":null,"text":[{"highlight_end":23,"highlight_start":14,"text":"\t\t\t\t\tif guess_no2 < target { println!(\"Guess too low\"); }"}]}]},"package_id":"testrust 0.1.0 (path+file:///I:/home/projects/test/testrust)","reason":"compiler-message","target":{"crate_types":["bin"],"kind":["bin"],"name":"testrust","src_path":"I:\\home\\projects\\test\\testrust\\src\\main.rs"}} {"message":{"children":[],"code":null,"level":"error","message":"aborting due to previous error","rendered":null,"spans":[]},"package_id":"testrust 0.1.0 (path+file:///I:/home/projects/test/testrust)","reason":"compiler-message","target":{"crate_types":["bin"],"kind":["bin"],"name":"testrust","src_path":"I:\\home\\projects\\test\\testrust\\src\\main.rs"}} --- src/VisualRust.Build/CargoBuild.cs | 2 +- src/VisualRust.Build/Rustc.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VisualRust.Build/CargoBuild.cs b/src/VisualRust.Build/CargoBuild.cs index b4849cf3..d66657b2 100644 --- a/src/VisualRust.Build/CargoBuild.cs +++ b/src/VisualRust.Build/CargoBuild.cs @@ -78,7 +78,7 @@ protected override bool ExecuteCargo(Cargo cargo) { var reader = new JsonTextReader(new StringReader(e.Data)); var message = Cargo.JsonSerializer.Deserialize(reader); - Rustc.LogRustcMessage(message.message, Manifest.Directory.FullName, Log); + if (message != null) Rustc.LogRustcMessage(message.message, Manifest.Directory.FullName, Log); } }; diff --git a/src/VisualRust.Build/Rustc.cs b/src/VisualRust.Build/Rustc.cs index b93ce2af..4876a224 100644 --- a/src/VisualRust.Build/Rustc.cs +++ b/src/VisualRust.Build/Rustc.cs @@ -369,6 +369,7 @@ private void LogRustcMessage(RustcMessageHuman msg) public static void LogRustcMessage(RustcMessageJson msg, string rootPath, TaskLoggingHelper log) { + if (msg == null) return; // todo multi span // todo all other fields // todo mb help key word is code.explanation From e070ed2e1104a049d5a6119761d760b1b7d8a879 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Fri, 30 Jun 2017 23:24:58 -0700 Subject: [PATCH 03/19] Project and item templates were only included in Debug vsix builds. Add them to Release vsix builds. --- src/VisualRust/VisualRust.projitems | 2 +- src/VisualRust/source.extension.release.2015.vsixmanifest | 2 ++ src/VisualRust/source.extension.release.2017.vsixmanifest | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VisualRust/VisualRust.projitems b/src/VisualRust/VisualRust.projitems index d6b4ac24..43d8f45c 100644 --- a/src/VisualRust/VisualRust.projitems +++ b/src/VisualRust/VisualRust.projitems @@ -8,7 +8,7 @@ VisualRust - + ProjectTemplates\ApplicationProject.zip true diff --git a/src/VisualRust/source.extension.release.2015.vsixmanifest b/src/VisualRust/source.extension.release.2015.vsixmanifest index 20e8b4ab..b4fbd0e5 100644 --- a/src/VisualRust/source.extension.release.2015.vsixmanifest +++ b/src/VisualRust/source.extension.release.2015.vsixmanifest @@ -17,6 +17,8 @@ + + diff --git a/src/VisualRust/source.extension.release.2017.vsixmanifest b/src/VisualRust/source.extension.release.2017.vsixmanifest index f7946672..27c28709 100644 --- a/src/VisualRust/source.extension.release.2017.vsixmanifest +++ b/src/VisualRust/source.extension.release.2017.vsixmanifest @@ -17,6 +17,8 @@ + + From 764984619c9baf313360f384f65b45a643f11912 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Sat, 1 Jul 2017 03:28:53 -0700 Subject: [PATCH 04/19] Add original source path to racer command line for better inrellisense. I've observed that in practice this allows the completion of basic static methods, such as "rand::thread_rng().gen_range(1,11);" --- src/VisualRust/RustCompletionSource.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/VisualRust/RustCompletionSource.cs b/src/VisualRust/RustCompletionSource.cs index e5948497..1638d021 100644 --- a/src/VisualRust/RustCompletionSource.cs +++ b/src/VisualRust/RustCompletionSource.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; using System.Linq; using System.Windows.Media; using Antlr4.Runtime; @@ -229,10 +230,13 @@ private static string RunRacer(ITextSnapshot snapshot, SnapshotPoint point) { using (var tmpFile = new TemporaryFile(snapshot.GetText())) { - // Build racer command line: "racer.exe complete lineNo columnNo rustfile + // Build racer command line: "racer.exe complete lineNo columnNo \"originalFile\" \"substituteFile\" + ITextDocument document = null; + snapshot?.TextBuffer?.Properties?.TryGetProperty(typeof(ITextDocument), out document); + var origPath = document?.FilePath ?? tmpFile.Path; int lineNumber = point.GetContainingLine().LineNumber; int charNumber = GetColumn(point); - string args = string.Format("complete {0} {1} {2}", lineNumber + 1, charNumber, tmpFile.Path); + string args = string.Format("complete {0} {1} \"{2}\" \"{3}\"", lineNumber + 1, charNumber, origPath, tmpFile.Path); return Racer.RacerSingleton.Run(args); } } From dd6719538a34c0636a54dd4f5f894c1dcd9fab6d Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Sat, 1 Jul 2017 14:11:38 -0700 Subject: [PATCH 05/19] Add docs to completions via racer's complete-with-snippet command line. --- src/VisualRust/RustCompletionSource.cs | 49 ++++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/VisualRust/RustCompletionSource.cs b/src/VisualRust/RustCompletionSource.cs index 1638d021..65ab4473 100644 --- a/src/VisualRust/RustCompletionSource.cs +++ b/src/VisualRust/RustCompletionSource.cs @@ -9,6 +9,8 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; +using System.Text.RegularExpressions; +using System.Windows; namespace VisualRust { @@ -236,7 +238,7 @@ private static string RunRacer(ITextSnapshot snapshot, SnapshotPoint point) var origPath = document?.FilePath ?? tmpFile.Path; int lineNumber = point.GetContainingLine().LineNumber; int charNumber = GetColumn(point); - string args = string.Format("complete {0} {1} \"{2}\" \"{3}\"", lineNumber + 1, charNumber, origPath, tmpFile.Path); + string args = string.Format("complete-with-snippet {0} {1} \"{2}\" \"{3}\"", lineNumber + 1, charNumber, origPath, tmpFile.Path); return Racer.RacerSingleton.Run(args); } } @@ -252,29 +254,54 @@ private IEnumerable GetCompletions(string racerResponse, out string return GetCompletions(lines); } + static readonly Regex ReDocsCodeSection = new Regex(@"(?<=^|\n)```\n(?.*?)\n```(?=\n|$)", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline); + static readonly Regex ReDocsIdentifier = new Regex(@"`(?.*?)`", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + static readonly Regex ReDocsEscape = new Regex(@"\\.", RegexOptions.Compiled); + + static readonly Dictionary ReUnescape = new Dictionary() { + { @"\\", "\\" }, + { @"\n", "\n" }, + { @"\;", ";" }, + { @"\""","\"" }, + { @"\'", "\'" }, + }; + private IEnumerable GetCompletions(string[] lines) - { + { + // Emitting code: https://github.com/racer-rust/racer/blob/4d694e1e17f58bbf01e52fc152065d4bc06157e2/src/bin/main.rs#L216-L254 + var matches = lines.Where(l => l.StartsWith("MATCH")).Distinct(StringComparer.Ordinal); foreach (var matchLine in matches) { - var tokens = matchLine.Substring(6).Split(','); - var text = tokens[0]; - var langElemText = tokens[4]; - var descriptionStartIndex = tokens[0].Length + tokens[1].Length + tokens[2].Length + tokens[3].Length + tokens[4].Length + 11; - var description = matchLine.Substring(descriptionStartIndex); - CompletableLanguageElement elType; + var tokens = matchLine.Substring(6).Split(new[]{';'}, 8); + var identifier = tokens[0]; // e.g. "read_line" + var shortSig = tokens[1]; // e.g. "read_line(${1:buf})" + var lineNo = tokens[2]; // e.g. "280" + var charNo = tokens[3]; // e.g. "11" + var path = tokens[4]; // e.g. "C:\Users\User\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\io\stdio.rs" + var langElemText = tokens[5]; // e.g. "Function" + var fullSig = tokens[6]; // e.g. "pub fn read_line(&self, buf: &mut String) -> io::Result" + var escapedDocs = tokens[7]; // e.g. "\"Locks this handle and reads a line of input into the specified buffer.\n\nFor detailed semantics of this method, ...\"" + + var docs = escapedDocs; + if (docs.StartsWith("\"") && docs.EndsWith("\"")) docs = docs.Substring(1, docs.Length-2); // strip quotes + docs = ReDocsCodeSection.Replace(docs, match => match.Groups["code"].Value); + docs = ReDocsIdentifier.Replace(docs, match => match.Groups["code"].Value); + docs = ReDocsEscape.Replace(docs, match => { string replacement; return ReUnescape.TryGetValue(match.Value, out replacement) ? replacement : match.Value; }); + CompletableLanguageElement elType; if (!Enum.TryParse(langElemText, out elType)) { Utils.DebugPrintToOutput("Failed to parse language element found in racer autocomplete response: {0}", langElemText); continue; } - var insertionText = text; + var displayText = identifier; + var insertionText = identifier; + var description = fullSig+"\n"+docs; var icon = GetCompletionIcon(elType); - - yield return new Completion(text, insertionText, description, icon, ""); + yield return new Completion(displayText, insertionText, description, icon, ""); } } From 9675cddb1fc6b5c70936677089b78b735590c5bf Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Sun, 2 Jul 2017 18:46:36 -0700 Subject: [PATCH 06/19] Only add a newline between signature and docs if docs actually exist --- src/VisualRust/RustCompletionSource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VisualRust/RustCompletionSource.cs b/src/VisualRust/RustCompletionSource.cs index 65ab4473..06521f7e 100644 --- a/src/VisualRust/RustCompletionSource.cs +++ b/src/VisualRust/RustCompletionSource.cs @@ -172,6 +172,7 @@ public void AugmentCompletionSession(ICompletionSession session, IList GetCompletions(string[] lines) var displayText = identifier; var insertionText = identifier; - var description = fullSig+"\n"+docs; + var description = string.IsNullOrWhiteSpace(docs) ? fullSig : fullSig+"\n"+docs; var icon = GetCompletionIcon(elType); yield return new Completion(displayText, insertionText, description, icon, ""); } From 4f2402c492239ead93df60ab22d1dae34c14d6f2 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Sun, 2 Jul 2017 18:06:03 -0700 Subject: [PATCH 07/19] Move RustGoToDefinitionCommandHandler's temp files out of the project directory. This was presumably originally done to improve "racer find-definition"'s results. However, it takes an optional [substitute_file] parameter we can use instead: racer find-definition [substitute_file] This change eliminates the following churn in the "Visual Rust" output tab: Apply change: File created: I:\home\projects\test\testrust\src\main.rs~RFa2df0b.TMP Apply change: File created: I:\home\projects\test\testrust\src\main.rs~RFa2df0b.TMP Failed to apply change 'File created: I:\home\projects\test\testrust\src\main.rs~RFa2df0b.TMP':System.ComponentModel.Win32Exception (0x80004005): Access is denied at VisualRust.ProjectSystem.FileSystem.ToShortPath(String path) at ... Apply recovery change: Directory created: I:\home\projects\test\testrust\ --- src/VisualRust/RustGoToDefinitionCommandHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualRust/RustGoToDefinitionCommandHandler.cs b/src/VisualRust/RustGoToDefinitionCommandHandler.cs index c47ea075..4a21c7aa 100644 --- a/src/VisualRust/RustGoToDefinitionCommandHandler.cs +++ b/src/VisualRust/RustGoToDefinitionCommandHandler.cs @@ -60,13 +60,13 @@ protected override bool Execute(VSConstants.VSStd97CmdID command, uint options, var dte = (EnvDTE.DTE)ServiceProvider.GetService(typeof(EnvDTE.DTE)); string filepath = dte.ActiveDocument.FullName; - using (var tmpFile = new TemporaryFile(snapshot.GetText(), System.IO.Path.GetDirectoryName(filepath))) + using (var tmpFile = new TemporaryFile(snapshot.GetText())) { var line = snapshotPoint.GetContainingLine(); // line.LineNumber uses 0 based indexing int row = line.LineNumber + 1; int column = snapshotPoint.Position - line.Start.Position; - var args = String.Format("find-definition {0} {1} \"{2}\"", row, column, tmpFile.Path); + var args = String.Format("find-definition {0} {1} \"{2}\" \"{3}\"", row, column, filepath, tmpFile.Path); var findOutput = Racer.RacerSingleton.Run(args); if (Regex.IsMatch(findOutput, "^MATCH")) { From 38b5e9dbfb3ee211ff279ade3bad4a2410a21edd Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Mon, 3 Jul 2017 21:45:24 -0700 Subject: [PATCH 08/19] Don't ctor *and* member inject IRustLexer. Also fix CompositionException s caused by spurious readonly. --- src/VisualRust/Text/RustClassifierProvider.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/VisualRust/Text/RustClassifierProvider.cs b/src/VisualRust/Text/RustClassifierProvider.cs index 0ca463f0..bf282fb8 100644 --- a/src/VisualRust/Text/RustClassifierProvider.cs +++ b/src/VisualRust/Text/RustClassifierProvider.cs @@ -30,13 +30,7 @@ public sealed class RustClassifierProvider : ITaggerProvider internal IStandardClassificationService StandardClassificationService = null; [Import] - readonly IRustLexer lexer; - - [ImportingConstructor] - public RustClassifierProvider(IRustLexer lexer) - { - this.lexer = lexer; - } + internal IRustLexer lexer = null; public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { From f29c5addc3aad0e283ac267f6873ad2328b3c524 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 5 Jul 2017 08:19:48 -0700 Subject: [PATCH 09/19] Fix ArgumentException when displaying errors from macros. Errors in macro expansions sometimes don't have real "file_name"s at every expansion level. This simply attempts to recursively expand until we can combine the paths sanely. Other alternatives might include comparing against target src_path, or looking for an ".rs" extension. --- src/VisualRust.Build/Rustc.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/VisualRust.Build/Rustc.cs b/src/VisualRust.Build/Rustc.cs index 4876a224..9be01472 100644 --- a/src/VisualRust.Build/Rustc.cs +++ b/src/VisualRust.Build/Rustc.cs @@ -382,19 +382,35 @@ public static void LogRustcMessage(RustcMessageJson msg, string rootPath, TaskLo if (String.IsNullOrEmpty(code) && primarySpan == null && msg.message.Contains("aborting due to")) return; + // primarySpan.file_name might not be legal (e.g. "file_name":"" is common) + string logFile = null; + var logSpan = primarySpan; + for (;;) + { + try + { + logFile = Path.Combine(rootPath, logSpan.file_name); // maybe checking for ".rs" extension is saner than trying this and seeing if it throws? + break; + } + catch (ArgumentException) // "Illegal characters in path." + { + logSpan = logSpan.expansion?.span; // see if expanding helps us find a real file + } + } + if (type == RustcMessageType.Error) { - if (primarySpan == null) + if (logSpan == null) log.LogError(msg.message); else - log.LogError(null, code, null, Path.Combine(rootPath, primarySpan.file_name), primarySpan.line_start, primarySpan.column_start, primarySpan.line_end, primarySpan.column_end, msg.message); + log.LogError(null, code, null, logFile, logSpan.line_start, logSpan.column_start, logSpan.line_end, logSpan.column_end, msg.message); } else { - if (primarySpan == null) + if (logSpan == null) log.LogWarning(msg.message); else - log.LogWarning(null, code, null, Path.Combine(rootPath, primarySpan.file_name), primarySpan.line_start, primarySpan.column_start, primarySpan.line_end, primarySpan.column_end, msg.message); + log.LogWarning(null, code, null, logFile, logSpan.line_start, logSpan.column_start, logSpan.line_end, logSpan.column_end, msg.message); } } From a8a1ae0fd583e116113db33c046a1de64941a481 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 5 Jul 2017 08:36:54 -0700 Subject: [PATCH 10/19] Enable building debug .vsix files --- src/VisualRust/VisualRust.2015.csproj | 2 +- src/VisualRust/VisualRust.2017.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualRust/VisualRust.2015.csproj b/src/VisualRust/VisualRust.2015.csproj index b899aa04..731010f3 100644 --- a/src/VisualRust/VisualRust.2015.csproj +++ b/src/VisualRust/VisualRust.2015.csproj @@ -31,7 +31,7 @@ prompt 4 true - false + True true diff --git a/src/VisualRust/VisualRust.2017.csproj b/src/VisualRust/VisualRust.2017.csproj index 1402da56..3fe6db6f 100644 --- a/src/VisualRust/VisualRust.2017.csproj +++ b/src/VisualRust/VisualRust.2017.csproj @@ -31,7 +31,7 @@ prompt 4 true - false + true true From 3e499d091a5fdb30e0062a222702c3904c614b35 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 5 Jul 2017 23:42:39 -0700 Subject: [PATCH 11/19] Separate racer match parsing out into it's own file, RacerMatch.cs. - Will make parsing available to future "signature help" popup hints. - Handle unescaping of rust strings, semicolon escapes, etc. - Document the heck out of members, complete with examples. --- src/VisualRust/Racer/RacerMatch.cs | 283 +++++++++++++++++++++++++ src/VisualRust/RustCompletionSource.cs | 49 ++--- src/VisualRust/VisualRust.projitems | 1 + 3 files changed, 302 insertions(+), 31 deletions(-) create mode 100644 src/VisualRust/Racer/RacerMatch.cs diff --git a/src/VisualRust/Racer/RacerMatch.cs b/src/VisualRust/Racer/RacerMatch.cs new file mode 100644 index 00000000..e920823c --- /dev/null +++ b/src/VisualRust/Racer/RacerMatch.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace VisualRust.Racer +{ + public struct RacerMatch + { + /// + /// The full text this match matches against. + /// Example: "read_line" + /// + public string MatchString; + + /// + /// Snippets appear to be equivalent to the MatchString, only containing parameter name information. + /// May be null unless using a "With Snippet" racer completion. + /// + /// Example: "read_line(${1:buf})" + /// + public string Snippet; + + /// + /// The 1-based line number where this match was defined. + /// Example: "280" + /// + public string LineNum; + + /// + /// The column where this match was defined. + /// Example: "11" + /// + public string CharNum; + + /// + /// The file where this match was defined. + /// Example: "C:\Users\User\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\io\stdio.rs" + /// + public string PathDisplay; + + /// + /// What kind of thing is being matched against. + /// Example: "Function" + /// + public string MatchType; + + /// + /// Seems to contain the signature of the match. + /// Example: "pub fn read_line(&self, buf: &mut String) -> io::Result<usize>" + /// + public string Context; + + /// + /// Contains the documentation for the match. Likely contains e.g. Markdown formatting - but the rust string has been unescaped. + /// May be null unless using a "With Snippet" racer completion. + /// + /// Example: "Locks this handle and reads a line of input into the specified buffer.\n\nFor detailed semantics of this method, ..." + /// + public string Documentation; + + + + public enum Type + { + /// + /// Generated by "racer.exe complete" + /// + Complete, + + /// + /// Generated by "racer.exe complete-with-snippet" + /// + CompleteWithSnippet + } + + public enum Interface + { + /// + /// Generated by "racer.exe --interface text ..." + /// + Text, + + /// + /// Generated by "racer.exe --interface tab-text ..." + /// + TabText, + + /// + /// Generated by "racer.exe ..." with no explicit "--interface ..." flags + /// + Default=Text + } + + + + /// + /// Try and parse a single "MATCH ..." line from "racer complete" or "racer complete-with-snippet" + /// + /// The "MATCH ..." text to try and parse. + /// Was "complete" or "complete-with-snippet" invoked? + /// Was "--interface [text|tab-text]" used? + /// The resulting parsed output. + /// True if the line was successfully parsed, false otherwise + public static bool TryParse(string line, Type type, Interface interface_, out RacerMatch match) + { + // Emitting code: https://github.com/racer-rust/racer/blob/4d694e1e17f58bbf01e52fc152065d4bc06157e2/src/bin/main.rs#L216-L254 + match = new RacerMatch(); + + if (!line.StartsWith(interface_ == Interface.TabText ? "MATCH\t" : "MATCH ")) return false; + + var seperator = interface_ == Interface.TabText ? '\t' : type == Type.CompleteWithSnippet ? ';' : ','; + var pos = 6; // always exactly after the initial "MATCH\t" or "MATCH " prefix + + bool success = false; + switch (type) { + case Type.Complete: + success + = TryParseStringUntilSkip (line, ref pos, seperator, out match.MatchString) + // no "snippet" field + && TryParseStringUntilSkip (line, ref pos, seperator, out match.LineNum) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.CharNum) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.PathDisplay) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.MatchType) + && TryParseStringUntilEnd (line, ref pos, out match.Context); + // no "docs" field + break; + + case Type.CompleteWithSnippet: + var escSepeator = interface_ == Interface.TabText ? 't' : ';'; + success + = TryParseStringUntilSkip (line, ref pos, seperator, out match.MatchString) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.Snippet) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.LineNum) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.CharNum) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.PathDisplay) + && TryParseStringUntilSkip (line, ref pos, seperator, out match.MatchType) + && TryParseEscStringUntilSkip (line, ref pos, escSepeator, seperator, out match.Context) + && TryParseSpanDebugStringUntilSkip(line, ref pos, '\0', out match.Documentation); + break; + } + + return success; + } + + private static bool TryParseStringUntilSkip(string line, ref int position, char seperator, out string span) + { + int end = line.IndexOf(seperator, position); + if (end == -1) + { + span = null; + return false; + } + else + { + span = line.Substring(position, end-position); + position = end+1; // skip seperator + return true; + } + } + + private static bool TryParseStringUntilEnd(string line, ref int position, out string span) + { + span = line.Substring(position); + return true; + } + + private static bool TryParseEscStringUntilSkip(string line, ref int position, char escSeperator, char seperator, out string span) + { + var sb = new StringBuilder(); + int start = position; + while (position < line.Length) + { + char ch = line[position++]; + if (ch == '\\') + { + if (position >= line.Length) + { + span = null; + return false; // out of buffer + } + var ch2 = line[position++]; + if (ch2 == escSeperator) + { + sb.Append(seperator); + } + else + { + sb.Append(ch); + --position; + } + } + else if (ch == seperator) + { + span = sb.ToString(); + return true; + } + else + { + sb.Append(ch); + } + } + span = null; + return false; + } + + private static bool TryParseSpanDebugStringUntilSkip(string line, ref int position, char seperator, out string span) + { + // String syntax ref: https://doc.rust-lang.org/reference/tokens.html + + var unescaped = new StringBuilder(); + + span = null; // Lots of failure cases + if (position >= line.Length) return false; // no more buffer left + if (line[position] != '"') return false; // string didn't start with a quote + + int start = ++position; // skip quote + int end = -1; + + while (end == -1) + { + if (position >= line.Length) return false; // string didn't end with a quote + char ch = line[position++]; + switch (ch) + { + case '\\': // escape + { + if (position >= line.Length) return false; + char ch2 = line[position++]; + + switch (ch2) + { + case 'u': + case 'U': + { + // Skip up to 6 hex digits + int n; + for (n=0; n<6 && position < line.Length; ++n, ++position) + { + char ch3 = line[position]; + var isHex = ('0' <= ch3 && ch3 <= '9') + || ('a' <= ch3 && ch3 <= 'f') + || ('A' <= ch3 && ch3 <= 'F'); + if (!isHex) break; + } + var value = Convert.ToInt32(line.Substring(position-n,n), 16); + unescaped.Append(char.ConvertFromUtf32(value)); + } + break; + case 'x': + { + if (position+2 > line.Length) return false; + var value = Convert.ToInt32(line.Substring(position,2), 16); + unescaped.Append((char)value); + position += 2; // Skip "\x??" + } + break; + case '\\': unescaped.Append('\\'); break; + case 'n': unescaped.Append('\n'); break; + case 'r': unescaped.Append('\r'); break; + case ';': unescaped.Append(';'); break; // Rust doesn't normally escape this, but racer sometimes does + case 't': unescaped.Append('\t'); break; + case '"': unescaped.Append('\"'); break; + case '\'': unescaped.Append('\''); break; + case '0': unescaped.Append('\0'); break; + } + } + break; + case '"': // terminating quote + end = position-1; // don't include quote + break; + default: + unescaped.Append(ch); + break; + } + } + + if (seperator != '\0' && (position >= line.Length || line[position++] != seperator)) return false; // expected to skip a final seperator + span = unescaped.ToString(); + return true; + } + } +} diff --git a/src/VisualRust/RustCompletionSource.cs b/src/VisualRust/RustCompletionSource.cs index 06521f7e..cfb1a3b0 100644 --- a/src/VisualRust/RustCompletionSource.cs +++ b/src/VisualRust/RustCompletionSource.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.Utilities; using System.Text.RegularExpressions; using System.Windows; +using VisualRust.Racer; namespace VisualRust { @@ -255,17 +256,8 @@ private IEnumerable GetCompletions(string racerResponse, out string return GetCompletions(lines); } - static readonly Regex ReDocsCodeSection = new Regex(@"(?<=^|\n)```\n(?.*?)\n```(?=\n|$)", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline); - static readonly Regex ReDocsIdentifier = new Regex(@"`(?.*?)`", RegexOptions.Compiled | RegexOptions.ExplicitCapture); - static readonly Regex ReDocsEscape = new Regex(@"\\.", RegexOptions.Compiled); - - static readonly Dictionary ReUnescape = new Dictionary() { - { @"\\", "\\" }, - { @"\n", "\n" }, - { @"\;", ";" }, - { @"\""","\"" }, - { @"\'", "\'" }, - }; + static readonly Regex ReDocsCodeSection = new Regex(@"(?<=^|\n)```(?[a-zA-Z_0-9]*)\n(?.*?)\n```(?=\n|$)", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline); + static readonly Regex ReDocsCodeInline = new Regex(@"`(?.*?)`", RegexOptions.Compiled | RegexOptions.ExplicitCapture); private IEnumerable GetCompletions(string[] lines) { @@ -275,32 +267,27 @@ private IEnumerable GetCompletions(string[] lines) foreach (var matchLine in matches) { - var tokens = matchLine.Substring(6).Split(new[]{';'}, 8); - var identifier = tokens[0]; // e.g. "read_line" - var shortSig = tokens[1]; // e.g. "read_line(${1:buf})" - var lineNo = tokens[2]; // e.g. "280" - var charNo = tokens[3]; // e.g. "11" - var path = tokens[4]; // e.g. "C:\Users\User\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\src\libstd\io\stdio.rs" - var langElemText = tokens[5]; // e.g. "Function" - var fullSig = tokens[6]; // e.g. "pub fn read_line(&self, buf: &mut String) -> io::Result" - var escapedDocs = tokens[7]; // e.g. "\"Locks this handle and reads a line of input into the specified buffer.\n\nFor detailed semantics of this method, ...\"" - - var docs = escapedDocs; - if (docs.StartsWith("\"") && docs.EndsWith("\"")) docs = docs.Substring(1, docs.Length-2); // strip quotes - docs = ReDocsCodeSection.Replace(docs, match => match.Groups["code"].Value); - docs = ReDocsIdentifier.Replace(docs, match => match.Groups["code"].Value); - docs = ReDocsEscape.Replace(docs, match => { string replacement; return ReUnescape.TryGetValue(match.Value, out replacement) ? replacement : match.Value; }); + RacerMatch racerMatch; + if (!RacerMatch.TryParse(matchLine, RacerMatch.Type.CompleteWithSnippet, RacerMatch.Interface.Default, out racerMatch)) + { + Utils.DebugPrintToOutput("Failed to parse racer match line found in racer autocomplete resource: {0}", matchLine); + continue; + } + + var docs = racerMatch.Documentation; + docs = ReDocsCodeSection.Replace(docs, codeSection => codeSection.Groups["code"].Value); + docs = ReDocsCodeInline .Replace(docs, codeInline => codeInline .Groups["code"].Value); CompletableLanguageElement elType; - if (!Enum.TryParse(langElemText, out elType)) + if (!Enum.TryParse(racerMatch.MatchType, out elType)) { - Utils.DebugPrintToOutput("Failed to parse language element found in racer autocomplete response: {0}", langElemText); + Utils.DebugPrintToOutput("Failed to parse language element found in racer autocomplete response: {0}", racerMatch.MatchType); continue; } - var displayText = identifier; - var insertionText = identifier; - var description = string.IsNullOrWhiteSpace(docs) ? fullSig : fullSig+"\n"+docs; + var displayText = racerMatch.MatchString; + var insertionText = racerMatch.MatchString; + var description = string.IsNullOrWhiteSpace(docs) ? racerMatch.Context : racerMatch.Context+"\n"+docs; var icon = GetCompletionIcon(elType); yield return new Completion(displayText, insertionText, description, icon, ""); } diff --git a/src/VisualRust/VisualRust.projitems b/src/VisualRust/VisualRust.projitems index 43d8f45c..f09e50c2 100644 --- a/src/VisualRust/VisualRust.projitems +++ b/src/VisualRust/VisualRust.projitems @@ -55,6 +55,7 @@ + From 7b9f314a6db4d54a2afff6942cdb3d758fb5a552 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Thu, 6 Jul 2017 00:10:59 -0700 Subject: [PATCH 12/19] Intellisense and goto def/help improvements, overhaul tokenization. User Interface changes: - F1 Help can now look up types, primitives. - Find definition now fully delegates to 'racer find-defition'. - Rust completions no longer triggered when definining new identifiers. Tokenization changes: - Replaced misused/understood "GetTokensAtPosition" with "GetLineTokensUpTo" and "GetTokensAt". - Documented and added explicit examples to help head off any confusion. - Added tracing of RustCompletionCommandHandler to see impacts. Fixed problems / rationale: - Single character tokens could previously never be "currentToken". - "currentToken" was usually null. - "leftToken" would often be the *last* token when typing in the *middle* of a line. Possible future paths: - Some kind of multiline token fetcher that fetches enough tokens for us to work with, without tokenizing the entire document. - Full blown AST? --- .../RustCompletionCommandHandler.cs | 73 ++++++++++++++----- src/VisualRust/RustF1HelpCommandHandler.cs | 14 +++- .../RustGoToDefinitionCommandHandler.cs | 10 --- src/VisualRust/Utils.cs | 46 ++++++++---- 4 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/VisualRust/RustCompletionCommandHandler.cs b/src/VisualRust/RustCompletionCommandHandler.cs index 423b1e36..3a30a65d 100644 --- a/src/VisualRust/RustCompletionCommandHandler.cs +++ b/src/VisualRust/RustCompletionCommandHandler.cs @@ -14,6 +14,7 @@ using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Utilities; using VSCommand = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID; +using System.Diagnostics; namespace VisualRust { @@ -73,27 +74,48 @@ protected override bool Execute(VSCommand command, uint options, IntPtr pvaIn, I { case VSConstants.VSStd2KCmdID.TYPECHAR: char ch = GetTypeChar(pvaIn); - var tokensAtCursor = Utils.GetTokensAtPosition(TextView.Caret.Position.BufferPosition); - - RustTokenTypes? leftTokenType = tokensAtCursor.Item1 != null ? (RustTokenTypes?)Utils.LexerTokenToRustToken(tokensAtCursor.Item1.Text, tokensAtCursor.Item1.Type) : null; - RustTokenTypes? currentTokenType = tokensAtCursor.Item2 != null ? (RustTokenTypes?)Utils.LexerTokenToRustToken(tokensAtCursor.Item2.Text, tokensAtCursor.Item2.Type) : null; - - RustTokenTypes[] cancelTokens = { RustTokenTypes.COMMENT, RustTokenTypes.STRING, RustTokenTypes.DOC_COMMENT, RustTokenTypes.WHITESPACE }; - - if (char.IsControl(ch) - || ch == ';' - || (leftTokenType.HasValue && cancelTokens.Contains(leftTokenType.Value)) - || (currentTokenType.HasValue && cancelTokens.Contains(currentTokenType.Value))) + var tokens = Utils.GetLineTokensUpTo(TextView.Caret.Position.BufferPosition); + var tokenTypes = tokens.Select(token => Utils.LexerTokenToRustToken(token.Text, token.Type)).ToList(); + int tokensCount = tokenTypes.Count; + RustTokenTypes? currentTokenType = tokensCount >= 1 ? (RustTokenTypes?)tokenTypes[tokensCount-1] : null; // the token we just helped type + Trace("TYPECHAR: ch={0}, currentToken={1}", ch, currentTokenType); + + RustTokenTypes[] cancelTokens = { RustTokenTypes.COMMENT, RustTokenTypes.STRING, RustTokenTypes.DOC_COMMENT, RustTokenTypes.WHITESPACE, RustTokenTypes.STRUCTURAL }; + if (char.IsControl(ch) || (currentTokenType.HasValue && cancelTokens.Contains(currentTokenType.Value))) { Cancel(); } - else if (leftTokenType == RustTokenTypes.STRUCTURAL) - { - Cancel(); - StartSession(); - } - else if (leftTokenType == RustTokenTypes.IDENT && currentTokenType == null) + else if (currentTokenType == RustTokenTypes.IDENT) { + Func isPreceededBy = (prefixStr) => + { + var prefix = prefixStr.Split(' '); + if (tokensCount-1 < 2*prefix.Length) + return false; // not enough tokens to be preceeded by all that + for (int i=0; i GetTokensAtPosition(SnapshotPoint snapshotPoint) + /// + /// This will return the tokens before the cursor, including any the cursor is in the middle of. + /// Given a cursor at '|' in the following strings... + /// + /// "let |foo = 42;" will result in ["let", " "]. + /// "let f|oo = 42;" will result in ["let", " ", "foo"]. + /// "let foo| = 42;" will result in ["let", " ", "foo"]. + /// "let foo |= 42;" will result in ["let", " ", "foo", " "]. + /// + internal static IList GetLineTokensUpTo(SnapshotPoint snapshotPoint) + { + var line = snapshotPoint.GetContainingLine(); + int col = snapshotPoint.Position - line.Start.Position; + var tokens = Utils.LexString(line.GetText()).TakeWhile(token => token.StartIndex < col).ToList(); + return tokens; + } + + /// + /// This will return the tokens immediately adjacent to the cursor (often the same token for both left and right). + /// Given a cursor at '|' in the following strings... + /// + /// " |ident" will result in (" ","ident"). + /// "id|ent" will result in ("ident","ident"). + /// "ident| " will result in ("ident"," "). + /// + internal static Tuple GetTokensAt(SnapshotPoint snapshotPoint) { var line = snapshotPoint.GetContainingLine(); var tokens = Utils.LexString(line.GetText()).ToList(); @@ -272,22 +297,11 @@ internal static Tuple GetTokensAtPosition(SnapshotPoint snapshot int col = snapshotPoint.Position - line.Start.Position; - IToken leftToken; - IToken currentToken = tokens.FirstOrDefault(t => col > t.StartIndex && col <= t.StopIndex); - - if (currentToken != null) - { - if (currentToken == tokens.First()) - leftToken = null; - else - leftToken = tokens[tokens.IndexOf(currentToken) - 1]; - } - else - { - leftToken = tokens.Last(); - } + // Note: These are often the same token when e.g. the cursor is in the middle of an ident token. + IToken leftToken = tokens.FirstOrDefault(t => col >= t.StartIndex && col <= t.StopIndex+1); + IToken rightToken = tokens.LastOrDefault (t => col >= t.StartIndex && col <= t.StopIndex+1); - return Tuple.Create(leftToken, currentToken); + return Tuple.Create(leftToken, rightToken); } } From 5251e8467a3cc62f3767b9717f60b7aa64c45433 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Sat, 8 Jul 2017 07:36:27 -0700 Subject: [PATCH 13/19] Fix MSBuild: Error span may be null. --- src/VisualRust.Build/Rustc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualRust.Build/Rustc.cs b/src/VisualRust.Build/Rustc.cs index 9be01472..3ffe5989 100644 --- a/src/VisualRust.Build/Rustc.cs +++ b/src/VisualRust.Build/Rustc.cs @@ -385,7 +385,7 @@ public static void LogRustcMessage(RustcMessageJson msg, string rootPath, TaskLo // primarySpan.file_name might not be legal (e.g. "file_name":"" is common) string logFile = null; var logSpan = primarySpan; - for (;;) + while (logSpan != null) { try { From 96c62f8e7b50b74783d6720d500c776b343ceddc Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 12 Jul 2017 09:06:21 -0700 Subject: [PATCH 14/19] Reduce completions to summary paragraphs (via RacerMatch accessors.) Previously, all documentation for an identifier was displayed. This included "# Examples" sections and lots of potentially long and rambling documentation. Rustdoc conventions suggest an initial "summary line", which would be a good defacto standard to display. In practice, the initial few paragraphs before the first header section also seem worth displaying, but that's perhaps more my personal taste. I've provided accessors for getting at "Plain" versions of both. Refactoring: - RustCompletionSource: Move regexps to RacerMatch. - RacerMatch: Expose Plain{Documentation,SummaryLine,SummaryParagraphs} --- src/VisualRust/Racer/RacerMatch.cs | 39 ++++++++++++++++++++++++++ src/VisualRust/RustCompletionSource.cs | 11 ++------ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/VisualRust/Racer/RacerMatch.cs b/src/VisualRust/Racer/RacerMatch.cs index e920823c..301cc8f6 100644 --- a/src/VisualRust/Racer/RacerMatch.cs +++ b/src/VisualRust/Racer/RacerMatch.cs @@ -59,6 +59,45 @@ public struct RacerMatch /// public string Documentation; + static readonly Regex ReDocsCodeSection = new Regex(@"(?<=^|\n)```(?[a-zA-Z_0-9]*)\n(?.*?)\n```(?=\n|$)", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline); + static readonly Regex ReDocsCodeInline = new Regex(@"`(?.*?)`", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + static readonly Regex ReDocsSummaryLine = new Regex(@"^(?.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + static readonly Regex ReDocsSummaryParas = new Regex(@"(?((^|(\r?\n))(?!#)(.*))*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + + /// + /// Documentation with some ancillary markdown such as ```code sections``` and `inline identifiers` stripped, for use in plaintext intellisense displays. + /// Other formatting such as # Headers will remain in place. + /// + public string PlainDocumentation + { + get + { + var d = Documentation ?? ""; + d = ReDocsCodeSection.Replace(d, codeSection => codeSection.Groups["code"].Value); + d = ReDocsCodeInline .Replace(d, codeInline => codeInline .Groups["code"].Value); + return d; + } + } + + public string PlainSummaryLine + { + get + { + var m = ReDocsSummaryLine.Match(PlainDocumentation); + return m.Success ? m.Groups["docs"].Value : ""; + } + } + + public string PlainSummaryParagraphs + { + get + { + var docs = PlainDocumentation; + var m = ReDocsSummaryParas.Match(PlainDocumentation); + return m.Success ? m.Groups["docs"].Value.TrimEnd() : ""; + } + } + public enum Type diff --git a/src/VisualRust/RustCompletionSource.cs b/src/VisualRust/RustCompletionSource.cs index cfb1a3b0..ddf9b0c7 100644 --- a/src/VisualRust/RustCompletionSource.cs +++ b/src/VisualRust/RustCompletionSource.cs @@ -256,13 +256,8 @@ private IEnumerable GetCompletions(string racerResponse, out string return GetCompletions(lines); } - static readonly Regex ReDocsCodeSection = new Regex(@"(?<=^|\n)```(?[a-zA-Z_0-9]*)\n(?.*?)\n```(?=\n|$)", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline); - static readonly Regex ReDocsCodeInline = new Regex(@"`(?.*?)`", RegexOptions.Compiled | RegexOptions.ExplicitCapture); - private IEnumerable GetCompletions(string[] lines) { - // Emitting code: https://github.com/racer-rust/racer/blob/4d694e1e17f58bbf01e52fc152065d4bc06157e2/src/bin/main.rs#L216-L254 - var matches = lines.Where(l => l.StartsWith("MATCH")).Distinct(StringComparer.Ordinal); foreach (var matchLine in matches) @@ -274,10 +269,6 @@ private IEnumerable GetCompletions(string[] lines) continue; } - var docs = racerMatch.Documentation; - docs = ReDocsCodeSection.Replace(docs, codeSection => codeSection.Groups["code"].Value); - docs = ReDocsCodeInline .Replace(docs, codeInline => codeInline .Groups["code"].Value); - CompletableLanguageElement elType; if (!Enum.TryParse(racerMatch.MatchType, out elType)) { @@ -287,6 +278,8 @@ private IEnumerable GetCompletions(string[] lines) var displayText = racerMatch.MatchString; var insertionText = racerMatch.MatchString; + var docs = racerMatch.PlainSummaryParagraphs; + //var docs = racerMatch.PlainSummaryLine; var description = string.IsNullOrWhiteSpace(docs) ? racerMatch.Context : racerMatch.Context+"\n"+docs; var icon = GetCompletionIcon(elType); yield return new Completion(displayText, insertionText, description, icon, ""); From 830f79f59f98f942abbaca2b48e33b98ec6ab2ec Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 12 Jul 2017 09:52:25 -0700 Subject: [PATCH 15/19] (Re)trigger intellisense after structural '.' or '::'. Floating point numbers aren't considered STRUCTURAL, so this doesn't incorrectly trigger for "12." and similar expressions. --- src/VisualRust/RustCompletionCommandHandler.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/VisualRust/RustCompletionCommandHandler.cs b/src/VisualRust/RustCompletionCommandHandler.cs index 3a30a65d..71688dd1 100644 --- a/src/VisualRust/RustCompletionCommandHandler.cs +++ b/src/VisualRust/RustCompletionCommandHandler.cs @@ -80,11 +80,23 @@ protected override bool Execute(VSCommand command, uint options, IntPtr pvaIn, I RustTokenTypes? currentTokenType = tokensCount >= 1 ? (RustTokenTypes?)tokenTypes[tokensCount-1] : null; // the token we just helped type Trace("TYPECHAR: ch={0}, currentToken={1}", ch, currentTokenType); - RustTokenTypes[] cancelTokens = { RustTokenTypes.COMMENT, RustTokenTypes.STRING, RustTokenTypes.DOC_COMMENT, RustTokenTypes.WHITESPACE, RustTokenTypes.STRUCTURAL }; + RustTokenTypes[] cancelTokens = { RustTokenTypes.COMMENT, RustTokenTypes.STRING, RustTokenTypes.DOC_COMMENT, RustTokenTypes.WHITESPACE }; if (char.IsControl(ch) || (currentTokenType.HasValue && cancelTokens.Contains(currentTokenType.Value))) { Cancel(); } + else if (currentTokenType == RustTokenTypes.STRUCTURAL) + { + var subtype = tokens.Last().Type; + if (subtype == RustLexer.RustLexer.DOT || subtype == RustLexer.RustLexer.MOD_SEP) + { + RestartSession(); + } + else + { + Cancel(); + } + } else if (currentTokenType == RustTokenTypes.IDENT) { Func isPreceededBy = (prefixStr) => From 8173634e8a5a88a7be5bb4fd07955137ab0414e1 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Tue, 18 Jul 2017 23:20:34 -0700 Subject: [PATCH 16/19] README.md: Document the use of submodules and lfs. The default errors that e.g. Visual Studio gives when failing to do either of these are unhelpful at best, downright confusing at worst. As I'm apparently not alone in overlooking these steps, they seem worth documenting. Ref: https://github.com/PistonDevelopers/VisualRust/issues/294 "Multiple Issues opening and building the solution in VS2017" --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 623b00f1..138143d2 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,16 @@ the time to spend coding. ### Code -1. Fork the main repository -2. Work on a feature in your own private branch -3. Once you are finished with you work or want someone to you, open a pull - request -4. Someone will review your code and merge it. Some fixes might be required on +1. Fork the main repository on github. +2. Check out the source code: + * Make sure [git lfs](https://git-lfs.github.com/) is installed **before** + cloning the repository or you'll need to delete and re-checkout some files. + * `git clone ...` your fork. + * `git submodule update --init` to grab MICore and MIDebugEngine. +3. Work on a feature in your own private branch. +4. Once you are finished with you work or want someone to you, open a pull + request. +5. Someone will review your code and merge it. Some fixes might be required on your side. ## Prerequisites From 9a5227f8c9b33dc331ad5ba3b0bcf928de29fa48 Mon Sep 17 00:00:00 2001 From: MaulingMonkey Date: Wed, 19 Jul 2017 19:51:39 -0700 Subject: [PATCH 17/19] Upgrade Microsoft.VSSDK.BuildTools in VisualRust.Templates to fix VS2017 build. --- src/VisualRust.Templates/VisualRust.Templates.csproj | 8 ++++---- src/VisualRust.Templates/packages.config | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualRust.Templates/VisualRust.Templates.csproj b/src/VisualRust.Templates/VisualRust.Templates.csproj index c2a359dc..915844f4 100644 --- a/src/VisualRust.Templates/VisualRust.Templates.csproj +++ b/src/VisualRust.Templates/VisualRust.Templates.csproj @@ -1,6 +1,6 @@  - + 14.0 $(VisualStudioVersion) @@ -102,10 +102,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - +