Add new language support and fixtures (v0.4.3-beta)

This commit is contained in:
h3p 2026-02-08 23:41:39 +01:00
parent a6a5289382
commit 6475747725
15 changed files with 234 additions and 21 deletions

View file

@ -4,6 +4,16 @@ All notable changes to **Neon Vision Editor** are documented in this file.
The format follows *Keep a Changelog*. Versions use semantic versioning with prerelease tags.
## [v0.4.3-beta] - 2026-02-08
### Added
- Syntax highlighting for **COBOL**, **Dotenv**, **Proto**, **GraphQL**, **reStructuredText**, and **Nginx**.
- Language picker/menu entries for the new languages.
- Sample fixtures for manual verification of detection and highlighting.
### Improved
- Extension and dotfile language detection for `.cob`, `.cbl`, `.cobol`, `.env*`, `.proto`, `.graphql`, `.gql`, `.rst`, and `.conf`.
## [v0.4.2-beta] - 2026-02-08
### Added

View file

@ -265,7 +265,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 105;
CURRENT_PROJECT_VERSION = 107;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;
@ -303,7 +303,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 0.4.2;
MARKETING_VERSION = 0.4.3;
PRODUCT_BUNDLE_IDENTIFIER = "h3p.Neon-Vision-Editor";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -338,7 +338,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 105;
CURRENT_PROJECT_VERSION = 107;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;
@ -376,7 +376,7 @@
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 26.0;
MARKETING_VERSION = 0.4.2;
MARKETING_VERSION = 0.4.3;
PRODUCT_BUNDLE_IDENTIFIER = "h3p.Neon-Vision-Editor";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -179,10 +179,16 @@ struct NeonVisionEditorApp: App {
}
CommandMenu("Language") {
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
let label: String = {
switch lang {
case "php": return "PHP"
case "cobol": return "COBOL"
case "dotenv": return "Dotenv"
case "proto": return "Proto"
case "graphql": return "GraphQL"
case "rst": return "reStructuredText"
case "nginx": return "Nginx"
case "objective-c": return "Objective-C"
case "csharp": return "C#"
case "cpp": return "C++"

View file

@ -26,7 +26,6 @@ public struct LanguageDetector {
"tsv": "csv",
"toml": "toml",
"ini": "ini",
"conf": "ini",
"yaml": "yaml",
"yml": "yaml",
"xml": "xml",
@ -59,6 +58,16 @@ public struct LanguageDetector {
"json5": "json",
"md": "markdown",
"markdown": "markdown",
"env": "dotenv",
"proto": "proto",
"graphql": "graphql",
"gql": "graphql",
"rst": "rst",
"conf": "nginx",
"nginx": "nginx",
"cob": "cobol",
"cbl": "cobol",
"cobol": "cobol",
"sh": "bash",
"bash": "bash",
"zsh": "zsh"
@ -75,7 +84,8 @@ public struct LanguageDetector {
".bash_logout": "bash",
".profile": "bash",
".vimrc": "vim",
".env": "ini",
".env": "dotenv",
".envrc": "dotenv",
".gitconfig": "ini"
]
@ -88,6 +98,9 @@ public struct LanguageDetector {
public func preferredLanguage(for fileURL: URL?) -> String? {
guard let fileURL else { return nil }
let fileName = fileURL.lastPathComponent.lowercased()
if fileName.hasPrefix(".env") {
return "dotenv"
}
if let mapped = dotfileMap[fileName] {
return mapped
}
@ -131,6 +144,11 @@ public struct LanguageDetector {
"csv": 0,
"python": 0,
"javascript": 0,
"dotenv": 0,
"proto": 0,
"graphql": 0,
"rst": 0,
"nginx": 0,
"cpp": 0,
"c": 0,
"css": 0,
@ -166,11 +184,20 @@ public struct LanguageDetector {
if t.contains("```php") { bump("php", 100) }
if t.contains("```csharp") || t.contains("```cs") { bump("csharp", 100) }
if t.contains("```cpp") || t.contains("```c++") { bump("cpp", 100) }
if t.contains("```proto") { bump("proto", 100) }
if t.contains("```graphql") || t.contains("```gql") { bump("graphql", 100) }
if t.contains("```dotenv") || t.contains("```env") { bump("dotenv", 100) }
if t.contains("```rst") { bump("rst", 100) }
// 2) Single-language quick checks
if let first = trimmed.first, (first == "{" || first == "[") && t.contains(":") { bump("json", 90) }
if t.contains("<html") || t.contains("<body") || t.contains("</") { bump("html", 90) }
if t.contains("<?php") || t.contains("<?=") { bump("php", 90) }
if t.contains("syntax = \"proto") { bump("proto", 90) }
if t.contains("schema {") || t.contains("type query") { bump("graphql", 70) }
if t.contains("server {") || t.contains("http {") || t.contains("location /") { bump("nginx", 70) }
if t.contains(".. toctree::") || t.contains(".. code-block::") { bump("rst", 70) }
if t.range(of: #"(?m)^[A-Z_][A-Z0-9_]*\s*="#, options: .regularExpression) != nil { bump("dotenv", 70) }
if raw.contains(",") && raw.contains("\n") {
let lines = raw.split(separator: "\n", omittingEmptySubsequences: true)
if lines.count >= 2 {
@ -277,7 +304,20 @@ public struct LanguageDetector {
bump("css", 8)
}
// 10) Markdown
// 10) Proto
if t.contains("message ") || t.contains("enum ") || t.contains("rpc ") { bump("proto", 10) }
// 11) GraphQL
if t.contains("type ") && t.contains("{") && t.contains("}") { bump("graphql", 8) }
if t.contains("fragment ") || t.contains("mutation") || t.contains("subscription") { bump("graphql", 8) }
// 12) Nginx
if t.contains("proxy_pass") || t.contains("server_name") || t.contains("error_log") { bump("nginx", 8) }
// 13) reStructuredText
if t.contains("::") && t.contains("\n====") { bump("rst", 6) }
// 14) Markdown
if t.contains("\n# ") || t.hasPrefix("# ") || t.contains("\n- ") || t.contains("\n* ") { bump("markdown", 8) }
// Conflict resolution tweaks

View file

@ -319,6 +319,51 @@ func getSyntaxPatterns(for language: String, colors: SyntaxColors) -> [String: C
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
#"//.*|/\*([^*]|(\*+[^*/]))*\*+/"#: colors.comment
]
case "cobol":
return [
#"(?i)\b(identification|environment|data|procedure|division|section|program-id|author|installati?on|date-written|date-compiled|working-storage|linkage|file-control|input-output|select|assign|fd|01|77|88|level|pic|picture|value|values|move|add|subtract|multiply|divide|compute|if|else|end-if|evaluate|when|perform|until|varying|go|to|goback|stop|run|call|accept|display|open|close|read|write|rewrite|delete|string|unstring|initialize|set|inspect)\b"#: colors.keyword,
#"\"[^\"]*\"|'[^']*'"#: colors.string,
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
#"(?m)^\s*\*.*$|(?m)^\s*\*>.*$"#: colors.comment
]
case "dotenv":
return [
#"(?m)^\s*[A-Z_][A-Z0-9_]*\s*="#: colors.property,
#"\"[^\"]*\"|'[^']*'"#: colors.string,
#"(?m)#.*$"#: colors.comment
]
case "proto":
return [
#"\b(syntax|package|import|option|message|enum|service|rpc|returns|repeated|map|oneof|reserved|required|optional)\b"#: colors.keyword,
#"\b(int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes|double|float)\b"#: colors.type,
#"\"[^\"]*\""#: colors.string,
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
#"//.*|/\*([^*]|(\*+[^*/]))*\*+/"#: colors.comment
]
case "graphql":
return [
#"\b(type|interface|enum|union|input|scalar|schema|extend|implements|directive|on|query|mutation|subscription|fragment)\b"#: colors.keyword,
#"\b([A-Z][A-Za-z0-9_]*)\b"#: colors.type,
#"\"[^\"]*\""#: colors.string,
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
#"(?m)#.*$"#: colors.comment
]
case "rst":
return [
#"(?m)^\s*([=\-`:'\"~^_*+<>#]{3,})\s*$"#: colors.keyword,
#"(?m)^\s*\.\.\s+[A-Za-z-]+::.*$"#: colors.meta,
#"(?m)^:?[A-Za-z-]+:\s+.*$"#: colors.property,
#"\*\*[^*]+\*\*"#: colors.def,
#"(?m)#.*$"#: colors.comment
]
case "nginx":
return [
#"\b(http|server|location|upstream|map|if|set|return|rewrite|proxy_pass|listen|server_name|root|index|try_files|include|error_page|access_log|error_log|gzip|ssl|add_header)\b"#: colors.keyword,
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
#"\"[^\"]*\"|'[^']*'"#: colors.string,
#"(?m)#.*$"#: colors.comment,
#"[{};]"#: colors.meta
]
case "standard":
return [
// Strings (double/single/backtick)

View file

@ -46,7 +46,6 @@ class EditorViewModel: ObservableObject {
"tsv": "csv",
"toml": "toml",
"ini": "ini",
"conf": "ini",
"yaml": "yaml",
"yml": "yaml",
"xml": "xml",
@ -79,6 +78,16 @@ class EditorViewModel: ObservableObject {
"json5": "json",
"md": "markdown",
"markdown": "markdown",
"env": "dotenv",
"proto": "proto",
"graphql": "graphql",
"gql": "graphql",
"rst": "rst",
"conf": "nginx",
"nginx": "nginx",
"cob": "cobol",
"cbl": "cobol",
"cobol": "cobol",
"sh": "bash",
"bash": "bash",
"zsh": "zsh"

View file

@ -42,10 +42,16 @@ extension ContentView {
@ViewBuilder
private var languagePickerControl: some View {
Picker("Language", selection: currentLanguageBinding) {
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
let label: String = {
switch lang {
case "php": return "PHP"
case "cobol": return "COBOL"
case "dotenv": return "Dotenv"
case "proto": return "Proto"
case "graphql": return "GraphQL"
case "rst": return "reStructuredText"
case "nginx": return "Nginx"
case "objective-c": return "Objective-C"
case "csharp": return "C#"
case "cpp": return "C++"
@ -291,10 +297,16 @@ extension ContentView {
#else
ToolbarItemGroup(placement: .automatic) {
Picker("Language", selection: currentLanguageBinding) {
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
let label: String = {
switch lang {
case "php": return "PHP"
case "cobol": return "COBOL"
case "dotenv": return "Dotenv"
case "proto": return "Proto"
case "graphql": return "GraphQL"
case "rst": return "reStructuredText"
case "nginx": return "Nginx"
case "objective-c": return "ObjectiveC"
case "csharp": return "C#"
case "cpp": return "C++"

View file

@ -1098,7 +1098,7 @@ struct ContentView: View {
/// Returns a supported language string used by syntax highlighting and the language picker.
private func detectLanguageWithAppleIntelligence(_ text: String) async -> String {
// Supported languages in our picker
let supported = ["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "objective-c", "csharp", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"]
let supported = ["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "cobol", "dotenv", "proto", "graphql", "rst", "nginx", "sql", "html", "css", "cpp", "objective-c", "csharp", "json", "xml", "yaml", "toml", "csv", "ini", "vim", "log", "ipynb", "markdown", "bash", "zsh", "powershell", "standard", "plain"]
#if USE_FOUNDATION_MODELS
// Attempt a lightweight model-based detection via AppleIntelligenceAIClient if available
@ -1124,6 +1124,24 @@ struct ContentView: View {
if lower.contains("<?php") || lower.contains("<?=") || lower.contains("$this->") || lower.contains("$_get") || lower.contains("$_post") || lower.contains("$_server") {
return "php"
}
if lower.contains("syntax = \"proto") || lower.contains("message ") || (lower.contains("enum ") && lower.contains("rpc ")) {
return "proto"
}
if lower.contains("type query") || lower.contains("schema {") || (lower.contains("interface ") && lower.contains("implements ")) {
return "graphql"
}
if lower.contains("server {") || lower.contains("http {") || lower.contains("location /") {
return "nginx"
}
if lower.contains(".. code-block::") || lower.contains(".. toctree::") || (lower.contains("::") && lower.contains("\n====")) {
return "rst"
}
if lower.contains("\n") && lower.range(of: #"(?m)^[A-Z_][A-Z0-9_]*=.*$"#, options: .regularExpression) != nil {
return "dotenv"
}
if lower.contains("identification division") || lower.contains("procedure division") || lower.contains("working-storage section") || lower.contains("environment division") {
return "cobol"
}
if text.contains(",") && text.contains("\n") {
let lines = text.split(separator: "\n", omittingEmptySubsequences: true)
if lines.count >= 2 {

View file

@ -17,7 +17,7 @@
</p>
> Status: **beta**
> Latest release: **v0.4.2-beta**
> Latest release: **v0.4.3-beta**
> Platform target: **macOS 26 (Tahoe)**
> Apple Silicon: tested / Intel: not tested
@ -25,7 +25,7 @@
Prebuilt binaries are available on [GitHub Releases](https://github.com/h3pdesign/Neon-Vision-Editor/releases).
- Latest release: **v0.4.2-beta**
- Latest release: **v0.4.3-beta**
- Architecture: Apple Silicon (Intel not tested)
- Notarization: *not yet*
@ -112,18 +112,26 @@ If macOS blocks first launch:
## Changelog
### v0.4.3-beta (summary)
- Added syntax highlighting for **COBOL**, **Dotenv**, **Proto**, **GraphQL**, **reStructuredText**, and **Nginx**.
- Added extension and dotfile mapping for `.cob`, `.cbl`, `.cobol`, `.env*`, `.proto`, `.graphql`, `.gql`, `.rst`, and `.conf`.
- Added language picker entries for the new languages across toolbar and command menus.
- Added sample fixtures for manual verification of new language detection and highlighting.
### v0.4.2-beta (summary)
- Fixed toolbar/menu actions to target the active window only.
- Fixed multi-window command routing to use the focused window model.
- Unified persistence behavior for Brain Dump and translucent window toggles.
- Removed duplicate `Cmd+F` binding conflict in toolbar wiring.
- Verified command-system changes on macOS and iOS simulator builds.
- Added syntax highlighting support for `vim`, `log`, and `ipynb` files.
- Added syntax highlighting support for `vim`, `log`, and `ipynb`.
- Added extension-based auto-detection for `.vim`, `.log`, `.ipynb`, and `.vimrc`.
- Improved header file default highlighting by mapping `.h` to `cpp`.
- Added language picker entries for **Vim**, **Log**, and **Jupyter Notebook**.
- Fixed drag & drop import of larger text and csv files
### v0.4.1-beta (summary)
- App Store security and distribution readiness updates.
- Added release/distribution documentation and checklist updates.
Full release history: [`CHANGELOG.md`](CHANGELOG.md)
@ -143,12 +151,12 @@ Full release history: [`CHANGELOG.md`](CHANGELOG.md)
## Release Integrity
- Tag: `v0.4.2-beta`
- Tag: `v0.4.3-beta`
- Tagged commit: `bc7ce77f6e08c3a751b5f80ac60ab2045956e736`
- Verify local tag target:
```bash
git rev-parse --verify v0.4.2-beta
git rev-parse --verify v0.4.3-beta
```
- Verify downloaded artifact checksum locally:

View file

@ -0,0 +1,5 @@
APP_NAME="Neon Vision Editor"
API_URL=https://api.example.test
MAX_WORKERS=4
FEATURE_FLAGS="syntax,ai"
# Comment line

View file

@ -0,0 +1,13 @@
# Language Fixtures
Manual verification checklist:
1. Open each file in the app.
2. Confirm the language picker switches to the expected language:
- `.env.sample` -> Dotenv
- `schema.proto` -> Proto
- `schema.graphql` -> GraphQL
- `index.rst` -> reStructuredText
- `nginx.conf` -> Nginx
3. Confirm syntax highlighting shows keywords, strings, and comments where applicable.
4. Paste each file's contents into a new tab and confirm the picker updates.

View file

@ -0,0 +1,15 @@
Neon Vision Editor
==================
Overview
--------
.. code-block:: bash
brew install --cask neon-vision-editor
Features
--------
* Fast startup
* Lightweight UI

View file

@ -0,0 +1,8 @@
server {
listen 80;
server_name example.test;
location / {
proxy_pass http://127.0.0.1:8080;
}
}

View file

@ -0,0 +1,12 @@
schema {
query: Query
}
type Query {
document(id: ID!): Document
}
type Document {
id: ID!
name: String
}

View file

@ -0,0 +1,12 @@
syntax = "proto3";
package neonvision.editor;
message Document {
string id = 1;
string name = 2;
}
service EditorService {
rpc GetDocument (Document) returns (Document);
}