Giter Club home page Giter Club logo

codeeditor's Introduction

CodeEditor

SwiftUI Swift5 macOS iOS visionOS Build and Test

A SwiftUI TextEditor View with syntax highlighting using Highlight.js.

It builds on top of Highlightr which does the wrapping of Highlight.js. CodeEditor then packages things up for SwiftUI.

Example usage in SVG Shaper for SwiftUI (used for editing SVG and Swift source):

SVG Shaper Screenshot

(Shaper is not actually using Highlightr, but is otherwise quite similar).

Highlightr example:

Highlight Example

Usage

Adding the Package

The Swift package URL is: https://github.com/ZeeZide/CodeEditor.git

Using it in a SwiftUI App

To use CodeEditor as a source code viewer, simply pass the source code as a string:

struct ContentView: View {

    var body: some View {
        CodeEditor(source: "let a = 42")
    }
}

If it should act as an actual editor, pass in a string Binding:

struct ContentView: View {

    @State private var source = "let a = 42\n"
    
    var body: some View {
        CodeEditor(source: $source, language: .swift, theme: .ocean)
    }
}

Languages and Themes

Highlight.js. supports more than 180 languages and over 80 different themes.

The available languages and themes can be accessed using:

CodeEditor.availableLanguages
CodeEditor.availableThemes

They can be used in a SwiftUI Picker like so:

struct MyEditor: View {
  
    @State private var source   = "let it = be"
    @State private var language = CodeEditor.Language.swift

    var body: some View {
        Picker("Language", selection: $language) {
            ForEach(CodeEditor.availableLanguages) { language in
                Text("\(language.rawValue.capitalized)")
                    .tag(language)
            }
        }
    
        CodeEditor(source: $source, language: language)
    }
}

Note: The CodeEditor doesn't do automatic theme changes if the appearance changes.

Smart Indent and Open/Close Pairing

Inspired by NTYSmartTextView, CodeEditor now also supports (on macOS):

  • smarter indents (preserving the indent of the previous line)
  • soft indents (insert a configurable amount of spaces if the user presses tabs)
  • auto character pairing, e.g. when entering {, the matching } will be auto-added

To enable smart indents, add the smartIndent flag, e.g.:

CodeEditor(source: $source, language: language, 
           flags: [ .selectable, .editable, .smartIndent ])

It is enabled for editors by default.

To configure soft indents, use the indentStyle parameter, e.g.

CodeEditor(source: $source, language: language,
           indentStyle: .softTab(width: 2))

It defaults to tabs, as per system settings.

Auto character pairing is automatic based on the language. E.g. there is a set of defaults for C like languages (e.g. Swift), Python or XML. The defaults can be overridden using the respective static variable in CodeEditor, or the desired pairing can be set explicitly:

CodeEditor(source: $source, language: language,
           autoPairs: [ "{": "}", "<": ">", "'": "'" ])

Font Sizing

On macOS the editor supports sizing of the font (using Cmd +/Cmd - and the font panel). To enable sizing commands, the WindowScene needs to have the proper commands applied, e.g.:

WindowGroup {
    ContentView()
}
.commands {
    TextFormattingCommands()
}

To persist the size, the fontSize binding is available.

Selection and Scrolling

The selected text can be observed and modified via another Binding:

 struct ContentView: View {
    static private let initialSource = "let a = 42\n"

    @State private var source = Self.initialSource
    @State private var selection = Self.initialSource.endIndex..<Self.initialSource.endIndex

    var body: some View {
        CodeEditor(source: $source,
                   selection: $selection,
                   language: .swift,
                   theme: .ocean,
                   autoscroll: false)
        Button("Select All") {
            selection = source.startIndex..<source.endIndex
        }
    }
}

When autoscroll is true, the editor automatically scrolls to the respective cursor position when selection is modfied from the outside, i.e. programatically.

Highlightr and Shaper

Based on the excellent Highlightr. This means that it is using JavaScriptCore as the actual driver. As Highlightr says:

It will never be as fast as a native solution, but it's fast enough to be used on a real time editor.

The editor is similar to (but not exactly the same) the one used by SVG Shaper for SwiftUI, for its SVG and Swift editor parts.

Complete Example

import SwiftUI
import CodeEditor

struct ContentView: View {
  
  #if os(macOS)
    @AppStorage("fontsize") var fontSize = Int(NSFont.systemFontSize)
  #endif
  @State private var source = "let a = 42"
  @State private var language = CodeEditor.Language.swift
  @State private var theme    = CodeEditor.ThemeName.pojoaque

  var body: some View {
    VStack(spacing: 0) {
      HStack {
        Picker("Language", selection: $language) {
          ForEach(CodeEditor.availableLanguages) { language in
            Text("\(language.rawValue.capitalized)")
              .tag(language)
          }
        }
        Picker("Theme", selection: $theme) {
          ForEach(CodeEditor.availableThemes) { theme in
            Text("\(theme.rawValue.capitalized)")
              .tag(theme)
          }
        }
      }
      .padding()
    
      Divider()
    
      #if os(macOS)
        CodeEditor(source: $source, language: language, theme: theme,
                   fontSize: .init(get: { CGFloat(fontSize)  },
                                   set: { fontSize = Int($0) }))
          .frame(minWidth: 640, minHeight: 480)
      #else
        CodeEditor(source: $source, language: language, theme: theme)
      #endif
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Who

CodeEditor is brought to you by ZeeZide. We like feedback, GitHub stars, cool contract work, presumably any form of praise you can think of.

codeeditor's People

Contributors

condo97 avatar darrarski avatar gin66 avatar helje5 avatar kinark avatar mcblooder avatar rminsh avatar snq-2001 avatar themomax avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

codeeditor's Issues

color markings of lines

add feature to allow marking of the line with specific color (like XCode and many other editors mark recent changes)

Keyboard shortcuts

Hello,
Thanks for this great library!

A beginner's question: what's the easiest way to add keyboard shortcuts? I'd like to execute a closure upon receiving a specific keyboard combination, ex. Cmd-R.

Thanks!

Multiple instances in custom views are messing up the text state on macOS

Hello and first of all thank you for this great library :)

I'm trying to replicate something similar to Xcode tabs, with some kind of top "tab bar" selecting each code file I want to edit.

Since SwiftUI's TabView doesn't (currently) allow for tab bar customization, I've created my own using buttons in a HStack. When I click a button, the "main view" (containing the CodeEditor for selected file) changes and shows me file contents.

The bug I've found is very strange, after changing one of two tabs the global "state" starts to mixup, showing up either wrong file content (the previous one) or resetting contents after window loses focus or when my tab changes.

System's TextEditor seems to work fine.

I have a strong suspect this is somehow related to SwiftUI.

I hope attached video is "self explaining" (window always has focus, but something strange also happens when you focus any other application).

you can find it here https://github.com/stefanomondino/CodeEditor in the Demo folder.

Registrazione.schermo.2022-05-15.alle.14.54.31.mov

Also, it's worth nothing that also CodeEditorView (a very similar project) has the same issue.

Let me know if you have any idea, thanks!

Auto-language detection and syntax highlighting not working when language is not explicitly specified

Hello,

I see this code in CodeAttributedString of Highlightr. It has a non-nil check for language else it doesn't proceed and hence doesn't highlight nor auto-detect the language. Is this expected?

    func highlight(_ range: NSRange)
    {
        if(language == nil)
        {
            return;
        }
        
        if let highlightDelegate = highlightDelegate
        {
            let shouldHighlight : Bool? = highlightDelegate.shouldHighlight?(range)
            if(shouldHighlight != nil && !shouldHighlight!)
            {
                return;
            }
        }

        
        let string = (self.string as NSString)
        let line = string.substring(with: range)
        DispatchQueue.global().async
        {
            let tmpStrg = self.highlightr.highlight(line, as: self.language!)
            DispatchQueue.main.async(execute: {
                //Checks to see if this highlighting is still valid.
                if((range.location + range.length) > self.stringStorage.length)
                {
                    self.highlightDelegate?.didHighlight?(range, success: false)
                    return;
                }
                
                if(tmpStrg?.string != self.stringStorage.attributedSubstring(from: range).string)
                {
                    self.highlightDelegate?.didHighlight?(range, success: false)
                    return;
                }
                
                self.beginEditing()
                tmpStrg?.enumerateAttributes(in: NSMakeRange(0, (tmpStrg?.length)!), options: [], using: { (attrs, locRange, stop) in
                    var fixedRange = NSMakeRange(range.location+locRange.location, locRange.length)
                    fixedRange.length = (fixedRange.location + fixedRange.length < string.length) ? fixedRange.length : string.length-fixedRange.location
                    fixedRange.length = (fixedRange.length >= 0) ? fixedRange.length : 0
                    self.stringStorage.setAttributes(attrs, range: fixedRange)
                })
                self.endEditing()
                self.edited(TextStorageEditActions.editedAttributes, range: range, changeInLength: 0)
                self.highlightDelegate?.didHighlight?(range, success: true)
            })
            
        }
        
    }

Add a Binding to report the currently selected range

I want to insert some text in the code editor. Markdown example: Select some text, press Cmd-B for Bold, and have two asterisks inserted before and after the selection. But to do this, I need to get the code editor's selection range. How do I get the selection range?

Binding does not work on iOS 15.2

Description

When a user enters text into CodeEditor, the binding is not called. This happens only on iOS. When using CodeEditor in a macOS app, there are no issues.

Environment

  • Xcode v13.2.1
  • iOS 15.2 Simulator

Steps to reproduce

  1. Embed CodeEditor in SwiftUI iOS application

    struct ContentView: View {
      @State var text = "TEST"
    
      var body: some View {
        VStack {
          CodeEditor(source: $text)
            .frame(height: 100)
    
          Divider()
    
          Text(text)
            .frame(maxWidth: .infinity, alignment: .leading)
            .multilineTextAlignment(.leading)
    
          Spacer()
        }
      }
    }
  2. Run the app in iOS Simulator

  3. Enter some text into CodeEditor

  4. Notice that the text property is not updated.

autoscroll not working

Not having any luck setting a selection and then it scrolling to that position per the example.

[BUG] - CodeEditor is affected by ColorPicker

When CodeEditor and ColorPicker are used at the same time, the color of CodeEditor changes

import SwiftUI
import CodeEditor

struct ContentView: View {
    @State private var source = """
struct ContentView: View {
    @State private var source = "let a = 42\n"
    @State var color: Color = Color.white
    var body: some View {
        HStack {
            ColorPicker("Color Picker", selection: $color)
            CodeEditor(source: $source, language: .swift, theme: .ocean)
        }
    }
}
"""
    @State var color: Color = Color.white
    var body: some View {
        HStack {
            ColorPicker("Color Picker", selection: $color)
            CodeEditor(source: $source, language: .swift, theme: .ocean)
        }
    }
}
2022-04-21.12.20.51.mov

When a range replaced with a string, the cursor is not at the end of the newly replaced string.

I'm using the below function to insert the text at the current selection ( current cursor position is enough for me, though)

func insertText(_ text: String, at range: Range<String.Index>, in string: String) -> String {
        var newString = string
        newString.replaceSubrange(range, with: text)
        return newString
    }

With the help of the selection binding, the text insertion is quite easy ( thanks for that), however, the cursor stays at the beginning of the inserted text ( the one that replaced the text of the range).

In the usual approach, on the other hand, the cursor must be placed at the end of newly inserted text ( when the range size is 0)

If I select a range more than the cursor position, this time, there is no cursor but only a selection that has the same size of the replaced text. I was expecting that the selection after the text inserts keep the size of the replaced part.

Ok, this may be the expected behavior since we may just be replacing the text that the editor's string with a new value.

Is there a way to achieve the text insertion so that the cursor moved to the end of the inserted string? This is necessary for me. Maybe there is a workaround for this one can move the cursor to the x position.


P.S. Overall, it is a great CodeEditor.

Set one or many highlighted lines

Highlight one multiple line to see the current editing line.
Of many lines like warnings/errors in XCode (or +/- lines on git diff tools)

How to turn off smart dash

If using lua, then double dash aka -- is comment character.
iOS has the feature to merge -- into a longer single dash.....
Any idea, how to turn this off in CodeEditor ? I could not find any solution

How to use a customized font in IOS?

I switched from SwiftUI's TextEditor to this CodeEditor. it really amazed me.
However, I found I cannot specify the font and font size as I did in the TextEditor. The code is as follow,

CodeEditor(source: $formulaInput, language: .python, theme: .default,
                       autoPairs: ["(" : ")", "{" : "}", "[" : "]"])
                .font(.custom("SF Mono Bold", size: fontSize))

Can you teach me how to use a customized font?
Thank you so much

How to search for text

How can this be extended, or how to access the underlying text view to do the following:

  1. handle command-f to find text
  2. having found text, highlight those strings in the body

Code formatting

Is it possible to format the code, such as JSON or other file formats ?

Problems using ColorPicker and CodeEditor at the same time

I found a new issue regarding the relationship between ColorPicker and CodeEditor

(1) Start ColorPicker
(2) Double-tap Text in CodeEditor

↓ Result ↓

The color of the Text is reflected in the ColorPicker

import SwiftUI
import CodeEditor

struct ContentView: View {
    @State var source: String = """
import SwiftUI
import CodeEditor

struct ContentView: View {
    @State var source: String = ""
    @State var color: Color = Color.white
    var body: some View {
        HStack {
            ColorPicker("", selection: $color)
            CodeEditor(source: $source)
        }
    }
}
"""
    @State var color: Color = Color.white
    var body: some View {
        ZStack {
            color
                .edgesIgnoringSafeArea(.all)
            HStack {
                ColorPicker("ColorPicker", selection: $color)
                CodeEditor(source: $source, language: .swift, theme: .pojoaque)
            }
        }
    }
}
2022-04-22.0.14.52.mp4

Problems with integrating CodeView into an Action Extension

I am using this to build an App for reading codes on iOS. This package works fine for building SwiftUI Views. However, when I am using it to build an Action Extension, this error below occurs. Can you help me with that? Thanks a lot!

Undefined symbols for architecture x86_64:
"nominal type descriptor for CodeViewer.CodeViewer", referenced from:
_symbolic ____y___________y_____y_____GGACy_____y__________y_____SgGGGQo 7SwiftUI4ViewPAAE18navigationBarItems7leading8trailingQrqd___qd_0_tAaBRd__AaBRd_0_r0_lFQO 10CodeViewerAGV AA6HStackV AA6ButtonV AA4TextV AA15ModifiedContentV AA5ImageV AA30_EnvironmentKeyWritingModifierV AA4FontV in EditorView.o
_symbolic ____y_____y___________y_____y_____GGACy_____y__________y_____SgGGGQo_______Qo 7SwiftUI4ViewPAAE5sheet11isPresented9onDismiss7contentQrAA7BindingVySbG_yycSgqd__yctAaBRd__lFQO AcAE18navigationBarItems7leading8trailingQrqd___qd_0_tAaBRd__AaBRd_0_r0_lFQO 10CodeViewerAOV AA6HStackV AA6ButtonV AA4TextV AA15ModifiedContentV AA5ImageV AA30_EnvironmentKeyWritingModifierV AA4FontV 15ActionExtension05ThemeC0V in EditorView.o
"nominal type descriptor for CodeViewer.CodeWebView.Theme", referenced from:
_symbolic _____y_____G 7SwiftUI5StateV 10CodeViewer0D7WebViewC5ThemeO in EditorView.o
"CodeViewer.CodeViewer.init(content: SwiftUI.Binding<Swift.String>, mode: CodeViewer.CodeWebView.Mode, darkTheme: CodeViewer.CodeWebView.Theme, lightTheme: CodeViewer.CodeWebView.Theme, isReadOnly: Swift.Bool, fontSize: Swift.Int, textDidChanged: ((Swift.String) -> ())?) -> CodeViewer.CodeViewer", referenced from:
ActionExtension.EditorView.body.getter : some in EditorView.o
"protocol conformance descriptor for CodeViewer.CodeViewer : SwiftUI.View in CodeViewer", referenced from:
lazy protocol witness table accessor for type CodeViewer.CodeViewer and conformance CodeViewer.CodeViewer : SwiftUI.View in CodeViewer in EditorView.o
"type metadata accessor for CodeViewer.CodeViewer", referenced from:
ActionExtension.EditorView.body.getter : some in EditorView.o
lazy protocol witness table accessor for type CodeViewer.CodeViewer and conformance CodeViewer.CodeViewer : SwiftUI.View in CodeViewer in EditorView.o
outlined destroy of CodeViewer.CodeViewer in EditorView.o
l_get_witness_table qd0__7SwiftUI4ViewHD3_AaBPAAE5sheet11isPresented9onDismiss7contentQrAA7BindingVySbG_yycSgqd__yctAaBRd__lFQOyAcAE18navigationBarItems7leading8trailingQrqd___qd_0_tAaBRd__AaBRd_0_r0_lFQOy10CodeViewerAOV_AA6HStackVyAA6ButtonVyAA4TextVGGATyAA15ModifiedContentVyAA5ImageVAA30_EnvironmentKeyWritingModifierVyAA4FontVSgGGGQo__15ActionExtension05ThemeC0VQo_HO in EditorView.o
"type metadata for CodeViewer.CodeWebView.Theme", referenced from:
property wrapper backing initializer of ActionExtension.EditorView.(theme in _1FD4E17BFA9D43C14FDD2767A1577070) : CodeViewer.CodeWebView.Theme in EditorView.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.