diff --git a/csharp_mod/Extensions.cs b/csharp_mod/Extensions.cs index 6367094..648062b 100644 --- a/csharp_mod/Extensions.cs +++ b/csharp_mod/Extensions.cs @@ -3,6 +3,7 @@ namespace Slang; using System; using System.Collections.Generic; using System.Text; +using Assets.Scripts.UI; using StationeersIC10Editor; public static unsafe class SlangExtensions @@ -133,4 +134,34 @@ public static unsafe class SlangExtensions return SlangFormatter.ColorDefault; } } + + public static unsafe List ToList(this Vec_FfiDocumentedItem_t vec) + { + var toReturn = new List((int)vec.len); + + var currentPtr = vec.ptr; + + for (int i = 0; i < (int)vec.len; i++) + { + var doc = currentPtr[i]; + var docItemName = doc.item_name.AsString(); + + var formattedText = TextMeshProFormatter.FromMarkdown(doc.docs.AsString()); + + var pediaPage = new StationpediaPage( + $"slang-item-{docItemName}", + docItemName, + formattedText + ); + + pediaPage.Text = formattedText; + pediaPage.Description = formattedText; + pediaPage.ParsePage(); + + toReturn.Add(pediaPage); + } + + Ffi.free_docs_vec(vec); + return toReturn; + } } diff --git a/csharp_mod/FfiGlue.cs b/csharp_mod/FfiGlue.cs index 5c49ef5..3489eb4 100644 --- a/csharp_mod/FfiGlue.cs +++ b/csharp_mod/FfiGlue.cs @@ -121,6 +121,31 @@ public unsafe partial class Ffi { slice_ref_uint16_t input); } +[StructLayout(LayoutKind.Sequential, Size = 48)] +public unsafe struct FfiDocumentedItem_t { + public Vec_uint8_t item_name; + + public Vec_uint8_t docs; +} + +/// +/// Same as [Vec][rust::Vec], but with guaranteed #[repr(C)] layout +/// +[StructLayout(LayoutKind.Sequential, Size = 24)] +public unsafe struct Vec_FfiDocumentedItem_t { + public FfiDocumentedItem_t * ptr; + + public UIntPtr len; + + public UIntPtr cap; +} + +public unsafe partial class Ffi { + [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern + void free_docs_vec ( + Vec_FfiDocumentedItem_t v); +} + public unsafe partial class Ffi { [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern void free_ffi_diagnostic_vec ( @@ -164,6 +189,11 @@ public unsafe partial class Ffi { Vec_uint8_t s); } +public unsafe partial class Ffi { + [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern + Vec_FfiDocumentedItem_t get_docs (); +} + public unsafe partial class Ffi { [DllImport(RustLib, ExactSpelling = true)] public static unsafe extern Vec_FfiToken_t tokenize_line ( diff --git a/csharp_mod/Marshal.cs b/csharp_mod/Marshal.cs index c058e6b..3a7b385 100644 --- a/csharp_mod/Marshal.cs +++ b/csharp_mod/Marshal.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using Assets.Scripts.UI; using StationeersIC10Editor; public struct Range @@ -151,6 +152,14 @@ public static class Marshal } } + /// + /// Gets the currently documented items from the Slang compiler and returns new StationpediaPages with correct formatting. + /// + public static unsafe List GetSlangDocs() + { + return Ffi.get_docs().ToList(); + } + private static string ExtractNativeLibrary(string libName) { string destinationPath = Path.Combine(Path.GetTempPath(), libName); diff --git a/csharp_mod/Patches.cs b/csharp_mod/Patches.cs index 0222b50..a96809e 100644 --- a/csharp_mod/Patches.cs +++ b/csharp_mod/Patches.cs @@ -224,4 +224,14 @@ public static class SlangPatches _currentlyEditingMotherboard = null; _motherboardCachedCode = null; } + + [HarmonyPatch(typeof(Stationpedia), nameof(Stationpedia.Regenerate))] + [HarmonyPostfix] + public static void Stationpedia_Regenerate() + { + foreach (var page in Marshal.GetSlangDocs()) + { + Stationpedia.Register(page); + } + } } diff --git a/csharp_mod/StationpediaDocumentation.cs b/csharp_mod/StationpediaDocumentation.cs deleted file mode 100644 index e03814a..0000000 --- a/csharp_mod/StationpediaDocumentation.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Assets.Scripts.UI; - -namespace Slang -{ - public static class SlangDocs - { - public static StationpediaPage[] Pages - { - get - { - return - [ - new StationpediaPage( - "slang-init", - "Slang", - "Slang is a new high level language built specifically for Stationeers" - ), - ]; - } - } - } -} diff --git a/csharp_mod/TmpFormatter.cs b/csharp_mod/TmpFormatter.cs new file mode 100644 index 0000000..aab5802 --- /dev/null +++ b/csharp_mod/TmpFormatter.cs @@ -0,0 +1,54 @@ +using System.Text.RegularExpressions; + +namespace Slang; + +public static class TextMeshProFormatter +{ + private const string CODE_COLOR = "#FFD700"; + + public static string FromMarkdown(string markdown) + { + if (string.IsNullOrEmpty(markdown)) + return ""; + + // 1. Normalize Line Endings + string text = markdown.Replace("\r\n", "\n"); + + // 2. Handle Code Blocks (```) + text = Regex.Replace( + text, + @"```\s*(.*?)\s*```", + match => + { + var codeContent = match.Groups[1].Value; + return $"{codeContent}"; // Gold color for code + }, + RegexOptions.Singleline + ); + + // 3. Handle Headers (## Header) + // Convert ## Header to large bold text + text = Regex.Replace( + text, + @"^##(\s+)?(.+)$", + "$1", + RegexOptions.Multiline + ); + + // 4. Handle Inline Code (`code`) + text = Regex.Replace(text, @"`([^`]+)`", $"$1"); + + // 5. Handle Bold (**text**) + text = Regex.Replace(text, @"\*\*(.+?)\*\*", "$1"); + + // 6. Handle Italics (*text*) + text = Regex.Replace(text, @"\*(.+?)\*", "$1"); + + // 7. Convert Newlines to TMP Line Breaks + // Stationpedia needs
or explicit newlines. + // Often just ensuring \n is preserved is enough, but
is safer for HTML-like parsers. + text = text.Replace("\n", "
"); + + return text; + } +} diff --git a/rust_compiler/libs/helpers/src/lib.rs b/rust_compiler/libs/helpers/src/lib.rs index 3060c3e..18359cf 100644 --- a/rust_compiler/libs/helpers/src/lib.rs +++ b/rust_compiler/libs/helpers/src/lib.rs @@ -5,6 +5,8 @@ mod macros; pub trait Documentation { /// Retreive documentation for this specific item. fn docs(&self) -> String; + + fn get_all_documentation() -> Vec<(&'static str, String)>; } pub mod prelude { diff --git a/rust_compiler/libs/helpers/src/macros.rs b/rust_compiler/libs/helpers/src/macros.rs index 822096b..9c51e46 100644 --- a/rust_compiler/libs/helpers/src/macros.rs +++ b/rust_compiler/libs/helpers/src/macros.rs @@ -55,7 +55,7 @@ macro_rules! documented { )* } - // 2. Implement the Trait + // 2. Implement the Documentation Trait impl Documentation for $name { fn docs(&self) -> String { match self { @@ -79,6 +79,33 @@ macro_rules! documented { )* } } + + // 3. Implement Static Documentation Provider + #[allow(dead_code)] + fn get_all_documentation() -> Vec<(&'static str, String)> { + vec![ + $( + ( + stringify!($variant), + { + // Re-use the same extraction logic + let doc_lines: &[Option<&str>] = &[ + $( + documented!(@doc_filter #[ $($variant_attr)* ]) + ),* + ]; + doc_lines.iter() + .filter_map(|&d| d) + .collect::>() + .join("\n") + .trim() + .to_string() + } + ) + ),* + ] + } } }; } + diff --git a/rust_compiler/libs/parser/src/sys_call.rs b/rust_compiler/libs/parser/src/sys_call.rs index 9e217fc..96f5a26 100644 --- a/rust_compiler/libs/parser/src/sys_call.rs +++ b/rust_compiler/libs/parser/src/sys_call.rs @@ -236,6 +236,13 @@ impl Documentation for SysCall { Self::Math(m) => m.docs(), } } + + fn get_all_documentation() -> Vec<(&'static str, String)> { + let mut all_docs = System::get_all_documentation(); + all_docs.extend(Math::get_all_documentation()); + + all_docs + } } impl std::fmt::Display for SysCall { diff --git a/rust_compiler/libs/tokenizer/src/token.rs b/rust_compiler/libs/tokenizer/src/token.rs index 59f2c7f..ca61cee 100644 --- a/rust_compiler/libs/tokenizer/src/token.rs +++ b/rust_compiler/libs/tokenizer/src/token.rs @@ -95,6 +95,10 @@ impl Documentation for TokenType { _ => "".into(), } } + + fn get_all_documentation() -> Vec<(&'static str, String)> { + Keyword::get_all_documentation() + } } impl From for u32 { diff --git a/rust_compiler/src/ffi/mod.rs b/rust_compiler/src/ffi/mod.rs index 938a5a7..ee31887 100644 --- a/rust_compiler/src/ffi/mod.rs +++ b/rust_compiler/src/ffi/mod.rs @@ -1,6 +1,6 @@ use compiler::Compiler; use helpers::Documentation; -use parser::Parser; +use parser::{sys_call::SysCall, Parser}; use safer_ffi::prelude::*; use std::io::BufWriter; use tokenizer::{ @@ -27,6 +27,13 @@ pub struct FfiRange { end_line: u32, } +#[derive_ReprC] +#[repr(C)] +pub struct FfiDocumentedItem { + item_name: safer_ffi::String, + docs: safer_ffi::String, +} + impl From for FfiRange { fn from(value: lsp_types::Range) -> Self { Self { @@ -77,6 +84,11 @@ pub fn free_string(s: safer_ffi::String) { drop(s) } +#[ffi_export] +pub fn free_docs_vec(v: safer_ffi::Vec) { + drop(v) +} + /// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because /// we want to avoid GC. So we pass it to Rust to handle all the memory allocations. /// This should result in the ability to compile many times without triggering frame drops @@ -184,3 +196,26 @@ pub fn diagnose_source(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec< res.unwrap_or(vec![].into()) } + +#[ffi_export] +pub fn get_docs() -> safer_ffi::Vec { + let res = std::panic::catch_unwind(|| { + let mut docs = SysCall::get_all_documentation(); + docs.extend(TokenType::get_all_documentation()); + + docs + }); + + let Ok(result) = res else { + return vec![].into(); + }; + + result + .into_iter() + .map(|(key, doc)| FfiDocumentedItem { + item_name: key.into(), + docs: doc.into(), + }) + .collect::>() + .into() +}