Skip to content

Code Editor Programming Discussion

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

The ATF Code Editor Sample shows how to interface third party software to an ATF application: the ActiproSoftware SyntaxEditor, which is provided in a set of DLLs. CodeEditor uses this software to access an editing control with language syntax sensitive editing for plain text, C#, Lua, Python, XML, and other format files with appropriate extensions. CodeEditor handles text files in these formats, so it does not store application data using a data model. Its data model could be considered to be the formats for the file types, and these are specified in proprietary XML files, such as CSharpDefinition.xml.

CodeEditor uses only a fraction of the capabilities ActiproSoftware SyntaxEditor provides.

CodeEditor uses interfaces and classes in the Sce.Atf.Controls.SyntaxEditorControl namespace, which comprises the Atf.SyntaxEditorControl assembly. The items in this namespace provide a layer between CodeEditor and SyntaxEditor.

Programming Overview

The CodeEditor application is basically a container for the ActiproSoftware SyntaxEditor editing control. CodeEditor itself uses some standard ATF facilities for handling documents in its CodeDocument class and Editor component. CodeEditor also provides source control with its own simple SourceControlContext component, but mainly with the ATF components SourceControlCommands and PerforceService.

The Sce.Atf.Controls.SyntaxEditorControl namespace contains interfaces and classes CodeEditor uses to communicate with the editing control. The most important is the ISyntaxEditorControl interface, which describes a framework for a syntax editor and could be used for syntax editors other than ActiproSoftware's. SyntaxEditorControl implements ISyntaxEditorControl by using ActiproSoftware's SyntaxEditor class. Other items in Sce.Atf.Controls.SyntaxEditorControl play a minor supporting role.

CodeEditor Document Handling

CodeEditor handles documents in a similar way as other samples by implementing IDocument and IDocumentClient. For a description of the general process, see Implementing a Document and Its Client in the section Documents in ATF.

CodeDocument Class

CodeDocument is the document class, implementing IDocument. Its constructor sheds light on interfacing with the editing control:

public CodeDocument(Uri uri)
{
    if (uri == null)
        throw new ArgumentNullException("uri");

    m_uri = uri;

    string filePath = uri.LocalPath;
    string fileName = Path.GetFileName(filePath);

    m_type = GetDocumentType(fileName);

    m_editor = TextEditorFactory.CreateSyntaxHighlightingEditor();

    Languages lang = GetDocumentLanguage(fileName);
    m_editor.SetLanguage(lang);
    Control ctrl = (Control)m_editor;
    ctrl.Tag = this;

    m_editor.EditorTextChanged += editor_EditorTextChanged;

    m_controlInfo = new ControlInfo(fileName, filePath, StandardControlGroup.Center);
    // tell ControlHostService this control should be considered a document in the menu,
    // and using the full path of the document for menu text to avoid adding a number in the end
    // in control header,  which is not desirable for documents that have the same name
    // but located at different directories.
    m_controlInfo.IsDocument = true;
}

This constructor does some initial housekeeping with the give URI. Next, the constructor gets an editing control object using the factory method TextEditorFactory.CreateSyntaxHighlightingEditor() and saves it in the field m_editor. This editing control implements ISyntaxEditorControl, which is the interface to access an editing control's capabilities. For information on this interface, see ISyntaxEditorControl Interface. The constructor sets the editing control's language, gleaned from the URI file's extension by the GetDocumentLanguage() method. The constructor finishes up by setting the Tag property in the control, subscribing to the EditorTextChanged event and setting up a new ControlInfo.

CodeDocument provides a few simple properties, such as Control, which gets the editing control. It also provides Read() and Write() methods, which are straightforward, because CodeEditor only works with text files. Here's Read(), for example:

public void Read()
{
    string filePath = m_uri.LocalPath;
    if (File.Exists(filePath))
    {
        using (StreamReader stream = new StreamReader(filePath, Encoding.UTF8))
        {
            m_editor.Text = stream.ReadToEnd();
            m_editor.Dirty = false;
        }
    }
}

The editing control's Text property is set to the text read from the file.

CodeDocument also implements IDocument and IResource. These simple interfaces handle the Dirty and Uri properties and their related events, such as DirtyChanged and UriChanged.

CodeDocument also contains a couple of utility methods GetDocumentType() and GetDocumentLanguage() that provide their information by examining the file's extension, which indicates the document type.

Editor Component

The Editor component provides CodeEditor's document client. It is also the control host client and command client for the main editing control, so it implements both IControlHostClient and ICommandClient. It implements ICommandClient for the standard editing commands:

public class Editor : IControlHostClient, IInitializable, ICommandClient

For information about how ATF handles controls and control host clients, see Using Controls in ATF in the section Controls in ATF. To learn about commands and their clients, see Using Commands in ATF in Commands in ATF.

Document Client

Editor contains the simple class DocumentClient that is the document client for CodeEditor:

private class DocumentClient : IDocumentClient

Its constructor takes an instance of Editor:

public DocumentClient(Editor editor, string extension)
{
    m_editor = editor;
    string fileType = CodeDocument.GetDocumentType(extension);
    m_info = new DocumentClientInfo(fileType, extension, null, null);
}

The constructor saves the Editor instance in the field m_editor. Do not confuse this with the m_editor field in the CodeDocument class, which holds an instance of the ActiproSoftware SyntaxEditor editing control.

This constructor also creates a DocumentClientInfo based on the document's type, indicated by its extension.

The document client uses the IControlHostService object passed to the Editor constructor to do some of its work. For example, Open() creates a CodeDocument object, reads the document, and then registers the ActiproSoftware SyntaxEditor editing control held in the CodeDocument.Control property with the Control Host Service:

public IDocument Open(Uri uri)
{
    CodeDocument doc = new CodeDocument(uri);
    doc.Read();

    m_editor.m_controlHostService.RegisterControl(
        doc.Control,
        doc.ControlInfo,
        m_editor);

    return doc;
}

The other IDocumentClient methods are even simpler that Open().

Editor's constructor creates a DocumentClient object for every file type that CodeEditor supports for later use:

// create a document client for each file type
m_txtDocumentClient = new DocumentClient(this, ".txt");
...
m_cgDocumentClient = new DocumentClient(this, ".cg");

It is typical for an ATF application to create a document client for each document type it handles.

Control Host Client

This client handles the editing control. The Activate() method, called when the control becomes active, informs the Document Registry and Command Service of the active document and control host client:

public void Activate(Control control)
{
    if (control.Tag is CodeDocument)
    {
        IDocument doc = (IDocument)control.Tag;
        m_documentRegistry.ActiveDocument = doc;
        m_commandService.SetActiveClient(this);
    }
}

Close() gets the active document, if any, and asks the Document Service to close it, which prompts the user to save the document if it was modified:

public bool Close(Control control)
{
    CodeDocument document = control.Tag as CodeDocument;
    if (document != null)
        return m_documentService.Close(document);

    return true;
}

Command Client

The Editor component's IInitializable.Initialize() is called after Editor is constructed and finishes initialization that can't be done in the constructor. In this case, it uses the Command Service component to register the Edit commands:

// register commands
m_commandService.RegisterCommand(CommandInfo.EditUndo, this);
...
m_commandService.RegisterCommand(CommandInfo.EditDelete, this);

m_commandService.RegisterCommand(
    Command.FindReplace,
    StandardMenu.Edit,
    StandardCommandGroup.EditOther,
    "Find and Replace...",
    "Find and replace text",
    Keys.None,
    Resources.FindImage,
    CommandVisibility.Menu,
    this);

m_commandService.RegisterCommand(
    Command.Goto,
    StandardMenu.Edit,
    StandardCommandGroup.EditOther,
    "Go to...",
    "Go to line",
    Keys.None,
    null,
    CommandVisibility.Menu,
    this);

In addition to standard Edit commands, it registers a couple of commands for search and going to a line in the file.

CodeEditor imports the StandardFileCommands component to create the actual Edit menu items for the edit commands.

The command client relies on the editing control to actually perform editing commands. Here's what ICommandClient.DoCommand() does:

public void DoCommand(object commandTag)
{
    CodeDocument activeDocument = m_documentRegistry.ActiveDocument as CodeDocument;
    if (commandTag is StandardCommand)
    {
        switch ((StandardCommand)commandTag)
        {
            case StandardCommand.EditUndo:
                activeDocument.Editor.Undo();
                break;
            ...
            case StandardCommand.EditDelete:
                activeDocument.Editor.Delete();
                break;
        }
    }
    else if (commandTag is Command)
    {
        switch ((Command)commandTag)
        {
            case Command.FindReplace:
                activeDocument.Editor.ShowFindReplaceForm();
                break;

            case Command.Goto:
                activeDocument.Editor.ShowGoToLineForm();
                break;
        }
    }
}

The variable activeDocument is adapted to a CodeDocument object, so the value activeDocument.Editor is the editing control. The editing control methods invoked here, such as Undo(), are in the ISyntaxEditorControl interface, which the editing control implements. For details on this interface, see ISyntaxEditorControl Interface.

SourceControlContext Component

SourceControlContext implements ISourceControlContext, which gets an enumeration of the IResources under source control with its Resources property. A source control facility can examine the URIs in this property and adapt the IResource to IDocument to track a document's dirty flag.

CodeEditor also imports the SourceControlCommands and PerforceService components, which actually do the source control management work.

SourceControlCommands registers the commands for source control and also provides the command client to perform them. In addition, it implements IContextMenuCommandProvider to provide a context menu with the source control commands. Its command client relies on a SourceControlService component to do the actual source control, which allows using different source control providers.

SourceControlService is an abstract component that implements ISourceControlService, the interface for source control services. PerforceService derives from SourceControlService and uses the Perforce Client for its source control provider.

Sce.Atf.Controls.SyntaxEditorControl Namespace

The interfaces and classes in Sce.Atf.Controls.SyntaxEditorControl comprise the layer between CodeEditor and the ActiproSoftware SyntaxEditor. The most important are ISyntaxEditorControl and its implementer, SyntaxEditorControl.

ISyntaxEditorControl Interface

ISyntaxEditorControl is an interface for syntax aware editing controls in general, not just the ActiproSoftware SyntaxEditor. This interface contains properties, methods, and events to do various things:

  • Get the actual text editing control.
  • Get or set text in the control.
  • Get or set information about the text, such as the number of lines.
  • Get or set user interface information, such as splitter locations.
  • Notify when key press and change events occur.
  • Determine if editing commands are doable and do them.
  • Enable facilities, such as word wrapping.
  • Perform selection operations.
  • Do search and replace operations.
There are also interfaces for search and replace: ISyntaxEditorFindReplaceOptions, ISyntaxEditorFindReplaceResult, and ISyntaxEditorFindReplaceResultSet.

SyntaxEditorControl Class

SyntaxEditorControl derives from SyntaxEditor, the actual ActiproSoftware SyntaxEditor editing control, defined in the ActiproSoftware DLLs. SyntaxEditorControl implements ISyntaxEditorControl by using the properties and methods of SyntaxEditor. CodeEditor also uses some ATF facilities; for more information, see ATF Facilities. CodeEditor could use another editing control package by implementing ISyntaxEditorControl for that editing control's API.

SyntaxEditorControl's constructor sets default values for various ISyntaxEditorControl properties and subscribes to the events in ISyntaxEditorControl.

The SetLanguage() method sets the control to one of the built-in languages. This is where the XML language definition files, such as PythonDefinition.xml and LuaDefinition.xml, are used to set SyntaxEditor's language. These files have a proprietary format.

SyntaxEditorControl also contains classes to implement ISyntaxEditorFindReplaceOptions, ISyntaxEditorFindReplaceResult, and ISyntaxEditorFindReplaceResultSet.

SyntaxEditorControl is an internal class to abide by SyntaxEditor's licensing terms.

ATF Facilities

The namespace Sce.Atf.Controls.SyntaxEditorControl contains other ATF facilities that are primarily used in SyntaxEditorControl.

Breakpoints

CodeEditor provides breakpoint facilities, as would be expected. An interface and classes provide breakpoint support:

  • IBreakpoint: Properties describing a breakpoint, such as Enabled and LineNumber.
  • BreakpointEventArgs: Breakpoint event argument information for the BreakpointChanging event, providing a constructor and properties, such as IsSet and LineNumber. The BreakpointChanging event is not implemented in CodeEditor.
  • BreakpointIndicator: Breakpoint indicator, implementing IBreakpoint. Its DrawGlyph() method draws the indicator.

Event Arguments

Several classes besides BreakpointEventArgs provide event arguments for events defined in ISourceControlContext.

  • EditorTextChangedEventArgs: Event arguments for the EditorTextChanged event, describing the nature of the text change.
  • MouseHoverOverTokenEventArgs: Event arguments for the MouseHoveringOverToken event. The MouseHoveringOverToken event is not implemented in CodeEditor.
  • ShowContextMenuEventArg: Event arguments for the ShowContextMenu event. The event is raised when the context menu should be displayed by right-clicking the SyntaxEditor control.

Language Related

These provide support for languages in CodeEditor:

  • Languages: Enumeration of languages supported in CodeEditor.
  • LuaDynamicSyntaxLanguage: Adds folding for code blocks in the Lua language.

Support Entities

These miscellaneous items generally support the CodeEditor:

  • SyntaxEditorRegions: Enumeration of regions in the SyntaxEditorControl user interface for hit testing. These include items such as splitters, scroll bars, and margins.
  • Token: A struct for a text token representing a word in a document. Besides the constructor, it holds token properties, such as the lexeme, the base unit of meaning of some word that can have a variety of forms (run with the forms runs, ran, running, etc.).
  • TextEditorFactory: Factory with methods to produce editing objects:
    • CreateSyntaxHighlightingEditor(): Provide a SyntaxEditorControl object. Used by the CodeDocument constructor.
    • CreateSyntaxEditorFindReplaceOptions(): Get a SyntaxEditorFindReplaceOptions object.

Topics in this section

Clone this wiki locally