mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 13:27:16 +00:00
Add comprehensive PHP and CSV language support, refine JSON/TOML syntax highlighting, and document Homebrew install option
This commit is contained in:
parent
3eca225d8c
commit
8b0bc4a805
8 changed files with 120 additions and 18 deletions
|
|
@ -42,9 +42,10 @@ extension ContentView {
|
|||
@ViewBuilder
|
||||
private var languagePickerControl: some View {
|
||||
Picker("Language", selection: currentLanguageBinding) {
|
||||
ForEach(["swift", "python", "javascript", "typescript", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
let label: String = {
|
||||
switch lang {
|
||||
case "php": return "PHP"
|
||||
case "objective-c": return "Objective-C"
|
||||
case "csharp": return "C#"
|
||||
case "cpp": return "C++"
|
||||
|
|
@ -52,6 +53,7 @@ extension ContentView {
|
|||
case "xml": return "XML"
|
||||
case "yaml": return "YAML"
|
||||
case "toml": return "TOML"
|
||||
case "csv": return "CSV"
|
||||
case "ini": return "INI"
|
||||
case "sql": return "SQL"
|
||||
case "html": return "HTML"
|
||||
|
|
@ -286,9 +288,10 @@ extension ContentView {
|
|||
#else
|
||||
ToolbarItemGroup(placement: .automatic) {
|
||||
Picker("Language", selection: currentLanguageBinding) {
|
||||
ForEach(["swift", "python", "javascript", "typescript", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
let label: String = {
|
||||
switch lang {
|
||||
case "php": return "PHP"
|
||||
case "objective-c": return "Objective‑C"
|
||||
case "csharp": return "C#"
|
||||
case "cpp": return "C++"
|
||||
|
|
@ -296,6 +299,7 @@ extension ContentView {
|
|||
case "xml": return "XML"
|
||||
case "yaml": return "YAML"
|
||||
case "toml": return "TOML"
|
||||
case "csv": return "CSV"
|
||||
case "ini": return "INI"
|
||||
case "sql": return "SQL"
|
||||
case "html": return "HTML"
|
||||
|
|
|
|||
|
|
@ -873,7 +873,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", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "objective-c", "csharp", "json", "xml", "yaml", "toml", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"]
|
||||
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", "markdown", "bash", "zsh", "powershell", "standard", "plain"]
|
||||
|
||||
#if USE_FOUNDATION_MODELS
|
||||
// Attempt a lightweight model-based detection via AppleIntelligenceAIClient if available
|
||||
|
|
@ -896,6 +896,18 @@ struct ContentView: View {
|
|||
if lower.contains("c#") || lower.contains("c sharp") || lower.range(of: #"\bcs\b"#, options: .regularExpression) != nil || lower.contains(".cs") {
|
||||
return "csharp"
|
||||
}
|
||||
if lower.contains("<?php") || lower.contains("<?=") || lower.contains("$this->") || lower.contains("$_get") || lower.contains("$_post") || lower.contains("$_server") {
|
||||
return "php"
|
||||
}
|
||||
if text.contains(",") && text.contains("\n") {
|
||||
let lines = text.split(separator: "\n", omittingEmptySubsequences: true)
|
||||
if lines.count >= 2 {
|
||||
let commaCounts = lines.prefix(6).map { line in line.filter { $0 == "," }.count }
|
||||
if let firstCount = commaCounts.first, firstCount > 0 && commaCounts.dropFirst().allSatisfy({ $0 == firstCount || abs($0 - firstCount) <= 1 }) {
|
||||
return "csv"
|
||||
}
|
||||
}
|
||||
}
|
||||
// C# strong heuristic
|
||||
if lower.contains("using system") || lower.contains("namespace ") || lower.contains("public class") || lower.contains("public static void main") || lower.contains("static void main") || lower.contains("console.writeline") || lower.contains("console.readline") || lower.contains("class program") || lower.contains("get; set;") || lower.contains("list<") || lower.contains("dictionary<") || lower.contains("ienumerable<") || lower.range(of: #"\[[A-Za-z_][A-Za-z0-9_]*\]"#, options: .regularExpression) != nil {
|
||||
return "csharp"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class EditorViewModel: ObservableObject {
|
|||
"swift": "swift",
|
||||
"py": "python",
|
||||
"js": "javascript",
|
||||
"php": "php",
|
||||
"phtml": "php",
|
||||
"csv": "csv",
|
||||
"tsv": "csv",
|
||||
"html": "html",
|
||||
"css": "css",
|
||||
"c": "c",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ public struct LanguageDetector {
|
|||
"swift": "swift",
|
||||
"py": "python",
|
||||
"js": "javascript",
|
||||
"php": "php",
|
||||
"phtml": "php",
|
||||
"csv": "csv",
|
||||
"tsv": "csv",
|
||||
"ts": "javascript",
|
||||
"html": "html",
|
||||
"css": "css",
|
||||
|
|
@ -69,6 +73,8 @@ public struct LanguageDetector {
|
|||
var scores: [String: Int] = [
|
||||
"swift": 0,
|
||||
"csharp": 0,
|
||||
"php": 0,
|
||||
"csv": 0,
|
||||
"python": 0,
|
||||
"javascript": 0,
|
||||
"cpp": 0,
|
||||
|
|
@ -98,12 +104,23 @@ public struct LanguageDetector {
|
|||
if t.contains("```swift") { bump("swift", 100) }
|
||||
if t.contains("```python") { bump("python", 100) }
|
||||
if t.contains("```js") || t.contains("```javascript") { bump("javascript", 100) }
|
||||
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) }
|
||||
|
||||
// 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 raw.contains(",") && raw.contains("\n") {
|
||||
let lines = raw.split(separator: "\n", omittingEmptySubsequences: true)
|
||||
if lines.count >= 2 {
|
||||
let commaCounts = lines.prefix(6).map { line in line.filter { $0 == "," }.count }
|
||||
if let firstCount = commaCounts.first, firstCount > 0 && commaCounts.dropFirst().allSatisfy({ $0 == firstCount || abs($0 - firstCount) <= 1 }) {
|
||||
bump("csv", 80)
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.contains("#!/bin/bash") || t.contains("#!/usr/bin/env bash") { bump("bash", 90) }
|
||||
if t.contains("#!/bin/zsh") || t.contains("#!/usr/bin/env zsh") { bump("zsh", 90) }
|
||||
|
||||
|
|
@ -181,19 +198,27 @@ public struct LanguageDetector {
|
|||
if t.contains("\ndef ") || t.hasPrefix("def ") { bump("python", 15) }
|
||||
if t.contains("\nimport ") && t.contains(":\n") { bump("python", 8) }
|
||||
|
||||
// 6) JavaScript / TypeScript
|
||||
// 6) PHP
|
||||
if t.contains("$this->") || t.contains("$_get") || t.contains("$_post") || t.contains("$_server") || t.contains("$_session") {
|
||||
bump("php", 20)
|
||||
}
|
||||
if (t.contains("function ") && t.contains("$")) || t.contains("echo ") {
|
||||
bump("php", 10)
|
||||
}
|
||||
|
||||
// 7) JavaScript / TypeScript
|
||||
if t.contains("function ") || t.contains("=>") || t.contains("console.log") { bump("javascript", 15) }
|
||||
|
||||
// 7) C/C++
|
||||
// 8) C/C++
|
||||
if t.contains("#include") || t.contains("std::") { bump("cpp", 20) }
|
||||
if t.contains("int main(") { bump("cpp", 8) }
|
||||
|
||||
// 8) CSS
|
||||
// 9) CSS
|
||||
if t.contains("{") && t.contains("}") && t.contains(":") && t.contains(";") && !t.contains("func ") {
|
||||
bump("css", 8)
|
||||
}
|
||||
|
||||
// 9) Markdown
|
||||
// 10) Markdown
|
||||
if t.contains("\n# ") || t.hasPrefix("# ") || t.contains("\n- ") || t.contains("\n* ") { bump("markdown", 8) }
|
||||
|
||||
// Conflict resolution tweaks
|
||||
|
|
|
|||
|
|
@ -159,8 +159,27 @@ struct NeonVisionEditorApp: App {
|
|||
}
|
||||
|
||||
CommandMenu("Language") {
|
||||
ForEach(["swift", "python", "javascript", "typescript", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
Button(lang.capitalized) {
|
||||
ForEach(["swift", "python", "javascript", "typescript", "php", "java", "kotlin", "go", "ruby", "rust", "sql", "html", "css", "cpp", "csharp", "objective-c", "json", "xml", "yaml", "toml", "csv", "ini", "markdown", "bash", "zsh", "powershell", "standard", "plain"], id: \.self) { lang in
|
||||
let label: String = {
|
||||
switch lang {
|
||||
case "php": return "PHP"
|
||||
case "objective-c": return "Objective-C"
|
||||
case "csharp": return "C#"
|
||||
case "cpp": return "C++"
|
||||
case "json": return "JSON"
|
||||
case "xml": return "XML"
|
||||
case "yaml": return "YAML"
|
||||
case "toml": return "TOML"
|
||||
case "csv": return "CSV"
|
||||
case "ini": return "INI"
|
||||
case "sql": return "SQL"
|
||||
case "html": return "HTML"
|
||||
case "css": return "CSS"
|
||||
case "standard": return "Standard"
|
||||
default: return lang.capitalized
|
||||
}
|
||||
}()
|
||||
Button(label) {
|
||||
if let tab = viewModel.selectedTab {
|
||||
viewModel.updateTabLanguage(tab: tab, language: lang)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,14 @@ struct SidebarView: View {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
case "php":
|
||||
toc = lines.enumerated().compactMap { index, line in
|
||||
let t = line.trimmingCharacters(in: .whitespaces)
|
||||
if t.hasPrefix("function ") || t.hasPrefix("class ") || t.hasPrefix("interface ") || t.hasPrefix("trait ") {
|
||||
return "\(t) (Line \(index + 1))"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case "objective-c":
|
||||
toc = lines.enumerated().compactMap { index, line in
|
||||
let t = line.trimmingCharacters(in: .whitespaces)
|
||||
|
|
@ -154,7 +162,7 @@ struct SidebarView: View {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
case "html", "css", "json", "markdown":
|
||||
case "html", "css", "json", "markdown", "csv":
|
||||
toc = lines.enumerated().compactMap { index, line in
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
if !trimmed.isEmpty && (trimmed.hasPrefix("#") || trimmed.hasPrefix("<h")) {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,15 @@ func getSyntaxPatterns(for language: String, colors: SyntaxColors) -> [String: C
|
|||
"\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number,
|
||||
"//.*|/\\*([^*]|(\\*+[^*/]))*\\*+/": colors.comment
|
||||
]
|
||||
case "php":
|
||||
return [
|
||||
#"\b(function|class|interface|trait|namespace|use|public|private|protected|static|final|abstract|if|else|elseif|for|foreach|while|do|switch|case|default|return|try|catch|throw|new|echo)\b"#: colors.keyword,
|
||||
#"\$[A-Za-z_][A-Za-z0-9_]*|\$\{[^}]+\}"#: colors.variable,
|
||||
#"\"[^\"]*\"|'[^']*'"#: colors.string,
|
||||
#"\b([0-9]+(\.[0-9]+)?)\b"#: colors.number,
|
||||
#"//.*|#.*|/\*([^*]|(\*+[^*/]))*\*+/"#: colors.comment,
|
||||
#"<\?php|\?>"#: colors.meta
|
||||
]
|
||||
case "html":
|
||||
return ["<[^>]+>": colors.tag]
|
||||
case "css":
|
||||
|
|
@ -136,10 +145,11 @@ func getSyntaxPatterns(for language: String, colors: SyntaxColors) -> [String: C
|
|||
]
|
||||
case "json":
|
||||
return [
|
||||
"\"[^\"]+\"\\s*:": colors.property,
|
||||
"\"[^\"]*\"": colors.string,
|
||||
"\\b([0-9]+(\\.[0-9]+)?)\\b": colors.number,
|
||||
"\\b(true|false|null)\\b": colors.keyword
|
||||
#"\"[^\"]+\"\s*:"#: colors.property,
|
||||
#"\"([^\"\\]|\\.)*\""#: colors.string,
|
||||
#"\b(-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?)\b"#: colors.number,
|
||||
#"\b(true|false|null)\b"#: colors.keyword,
|
||||
#"[{}\[\],:]"#: colors.meta
|
||||
]
|
||||
case "markdown":
|
||||
return [
|
||||
|
|
@ -258,9 +268,19 @@ func getSyntaxPatterns(for language: String, colors: SyntaxColors) -> [String: C
|
|||
]
|
||||
case "toml":
|
||||
return [
|
||||
#"^\[[^\]]+\]"#: colors.meta,
|
||||
#"\b[0-9]+\b"#: colors.number,
|
||||
#"\"[^\"]*\""#: colors.string
|
||||
#"^\s*\[\[?[^\]]+\]?\]\s*$"#: colors.meta,
|
||||
#"^\s*[A-Za-z0-9_.-]+\s*="#: colors.property,
|
||||
#"\"([^\"\\]|\\.)*\"|'[^']*'"#: colors.string,
|
||||
#"\b(-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?)\b"#: colors.number,
|
||||
#"\b(true|false)\b"#: colors.keyword,
|
||||
#"(?m)#.*$"#: colors.comment
|
||||
]
|
||||
case "csv":
|
||||
return [
|
||||
#"\A([^\n,]+)(,\s*[^\n,]+)*"#: colors.meta,
|
||||
#"\"([^\"\n]|\"\")*\""#: colors.string,
|
||||
#"\b(-?[0-9]+(\.[0-9]+)?)\b"#: colors.number,
|
||||
#","#: colors.property
|
||||
]
|
||||
case "ini":
|
||||
return [
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -72,7 +72,7 @@ No background indexing. No telemetry. No plugin sprawl.
|
|||
|
||||
- Fast loading, including large text files
|
||||
- Automatic applied syntax highlighting for common languages
|
||||
(Python, C/C++, JavaScript, HTML, CSS, and others)
|
||||
(Python, PHP, C/C++, JavaScript, HTML, CSS, and others)
|
||||
- Clean, minimal UI optimized for readability
|
||||
- Native macOS 26 (Tahoe) look & behavior
|
||||
- Built with Swift and AppKit
|
||||
|
|
@ -121,5 +121,15 @@ If you find Neon Vision Editor useful and want to support its development:
|
|||
git clone https://github.com/h3pdesign/Neon-Vision-Editor.git
|
||||
cd Neon-Vision-Editor
|
||||
open "Neon Vision Editor.xcodeproj"
|
||||
```
|
||||
|
||||
## Homebrew install option
|
||||
|
||||
If you use Homebrew, you can install via cask:
|
||||
|
||||
```bash
|
||||
brew tap h3pdesign/tap
|
||||
brew install --cask neon-vision-editor
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue