Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have shell not hold reference to original files when adding an explicit file reference #51

Open
jerhon opened this issue Aug 20, 2014 · 6 comments
Labels
Milestone

Comments

@jerhon
Copy link

jerhon commented Aug 20, 2014

If a DLL file reference is added in the workspace, add the ability to recompile the DLL via visual studio while the object is still referenced by CShell

In order to gain new functionality after recompiliation possibly provide a dialog that notifies the user a reference has been updated and ask to reload.

Also add the ability to easily reload the REPL window from the C# interactive UI.

Perhaps a .ResetWorkspace could be on the shell in addition to the OpenWorkspace() and CloseWorkspace()

@georgiosd
Copy link

+1 on this one. At least the capability to unload the AppDomain and load it again

@lukebuehler
Copy link
Owner

Good input, thanks.

The dll is locked by the underlying scripting engine. So we don't have much control over that. So shadow copying (what VS uses) is not really an option.

Running the scripting engine in a seperate AppDomain is a possibility. But it would make the interaction from the scripts with the UI much more cumbersome, since everything has to pass through MarshalByRefObjects or another serialization interface. Currently the scripting engine runs in the same AppDomain as the UI.

This is problem is REALLY annoying me too. Since at our company we have many CShell extensions and libraries that we work on daily and I have to always restart CShell whenver there's a change to our library. So, trust me, if I could easily fix this I would.

@lukebuehler
Copy link
Owner

On second thoughts about closing this. I will not close it but just assign it into the far future.

@lukebuehler lukebuehler reopened this Sep 12, 2014
@lukebuehler lukebuehler added this to the vFuture milestone Sep 12, 2014
@georgiosd
Copy link

I have a "free time" project where i ve integrated scriptcs with avalonedit and the completion sample. I ve managed to make it work on a different app domain but it took quite a different structure than cshell. Want me to go over the diffs? (I tried to base it on cshell initally but it proved too "complete" for me to get started)

Sent from a device with a small keyboard

On 12 Sep 2014, at 21:26, "Lukas Buhler" [email protected] wrote:

Good input, thanks.

The dll is locked by the underlying scripting engine. So we don't have much control over that. So shadow copying (what VS uses) is not really an option.

Running the scripting engine in a seperate AppDomain is a possibility. But it would make the interaction from the scripts with the UI much more cumbersome, since everything has to pass through MarshalByRefObjects or another serialization interface. Currently the scripting engine runs in the same AppDomain as the UI.

This is problem is REALLY annoying me too. Since at our company we have many CShell extensions and libraries that we work on daily and I have to always restart CShell whenver there's a change to our library. So, trust me, if I could easily fix this I would.


Reply to this email directly or view it on GitHub.

@lukebuehler
Copy link
Owner

Yes, the devil is in the detail. In particular, it turned out to be difficult to have a tight integration between script code completion, REPL code completion, loaded libraries, and the scripting engine when running it all in a separate app domain.

I've tried to keep CShell as simple as possible in terms of architecture (maybe, turns out, a bit too simple), so that it has more of a hackers tool feel to it... Wanna unload the app domain? Just close and open the whole app... It's just difficult to stay simple without ending up re-implementing SharpDevelop/MonoDevelop/VisualStudio which are all much more mature projects of course.

I'd love to hear your input in how you designed it in your project!

@georgiosd
Copy link

Hey Luke!

Ok, here's my approach.

As you say, starting to implement either MarshalByRef or Serializable is a rabbit hole that you don't want to go down, especially when it comes to completion. So, instead, I separated the completion from the script engine. As far as I could tell, the only reason to have them mixed, was to maintain the same references/namespaces.

Here are the steps I took, not necessarily in this order:

  • Leave IScriptEngine alone and not inherit from it. Instead of your IReplExecutor, I made a new interface IRepl that is constructed with the script engine (allowing any ScriptCs engine to be used too). It's pretty simple: a script engine getter, a command-aware Execute() (which accepts my own command interface as I found the ScriptCs one limiting), getters for variables/namespaces/refs and Terminate(). Note that Execute() will return my own ReplScriptResult that contains only serializiable types:
[Serializable]
    public class ReplScriptResult
    {
        public string[] Messages { get; private set; }

        public bool IsCompleteSubmission { get; private set; }

        public string ReturnValueAsString { get; private set; }

        public ReplScriptResult(ScriptResult result)
        {
            IsCompleteSubmission = result.IsCompleteSubmission;

            Messages = GetMessages(result);

            ReturnValueAsString = ToPrettyString(result.ReturnValue);
        }

        private static string[] GetMessages(ScriptResult scriptResult)
        {
            var msgs = new List<string>();

            if (scriptResult.CompileExceptionInfo != null)
            {
                var ex = scriptResult.CompileExceptionInfo.SourceException;
                msgs.Add(ex.Message);
            }

            if (scriptResult.ExecuteExceptionInfo != null)
            {
                var ex = scriptResult.ExecuteExceptionInfo.SourceException;
                msgs.Add(ex.ToString());
            }

            return msgs.ToArray();
        }

        private static string ToPrettyString(object o)
        {
            if (o == null)
                return "null";

            if (o is String)
                return o.ToString();

            var enumerable = o as IEnumerable;
            if (enumerable != null)
            {
                var items = enumerable.Cast<object>().Take(21).ToList();
                var firstItems = items.Take(20).ToList();
                var sb = new StringBuilder();
                sb.Append("{");
                sb.Append(String.Join(", ", firstItems));
                if (items.Count > firstItems.Count)
                    sb.Append("...");
                sb.Append("}");
                return sb.ToString();
            }

            return o.ToString();
        }
    }
  • Use ObservableCollection<T> to contain the refs/namespaces - this allows the completion engine to be notified of additions and updates its own collection.
  • There is also an IReplFactory which does the obvious. Both the implementations of IRepl and IReplFactory inherit from MarshalByRef so that they can be created in the new AppDomain
  • I also introduced an ICompletionProvider which also does the obvious - provides completion results either based on the default document or based on the IRepl. Always constructed on the UI AppDomain.
  • Finally, I have my ReplControl WPF control which has a dependency property Repl of type IRepl. The view model maintains the app domain and has a Reset() command that will create a new app domain+factory and then assign a new IRepl to the bound property. When the dependency property is set, I then create a new ReplCompletionProvider

How's it sound?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants