Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 12 additions & 48 deletions dev/pyRevitLabs.PyRevit.Runtime/ScriptConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using pyRevitLabs.Common;
using pyRevitLabs.CommonWPF.Controls;
using pyRevitLabs.Emojis;
using pyRevitLabs.PyRevit;

namespace PyRevitLabs.PyRevit.Runtime {
public struct ScriptConsoleDebugger {
Expand Down Expand Up @@ -171,7 +172,7 @@ public partial class ScriptConsole : ScriptConsoleTemplate, IComponentConnector,
private System.Windows.Forms.HtmlElement _lastDocumentBody = null;
private UIApplication _uiApp;

private List<ScriptConsoleDebugger> _supportedDebuggers =
private List<ScriptConsoleDebugger> _supportedDebuggers =
new List<ScriptConsoleDebugger> {
new ScriptConsoleDebugger() {
Name = "Pdb (IronPython|CPython)",
Expand Down Expand Up @@ -376,39 +377,15 @@ public string GetFullHtml() {
return ScriptConsoleConfigs.DOCTYPE + head.OuterHtml + ActiveDocument.Body.OuterHtml;
}

private static (bool closeOthers, string closeMode) ReadCloseOtherOutputsSetting() {
try {
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var cfg = System.IO.Path.Combine(appData, "pyRevit", "pyRevit_config.ini");
if (!System.IO.File.Exists(cfg))
return (false, "current_command");

bool inCore = false;
bool close = false;
string mode = "current_command";
foreach (var raw in System.IO.File.ReadAllLines(cfg)) {
var line = raw.Trim();
if (line.Length == 0 || line.StartsWith("#") || line.StartsWith(";"))
continue;

if (line == "[core]") { inCore = true; continue; }
if (line.StartsWith("[") && line.EndsWith("]")) { inCore = false; continue; }

if (!inCore) continue;

if (line.StartsWith("close_other_outputs", StringComparison.InvariantCultureIgnoreCase))
close = line.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) >= 0;

else if (line.StartsWith("close_mode", StringComparison.InvariantCultureIgnoreCase)) {
var parts = line.Split(new[] { '=' }, 2);
if (parts.Length == 2)
mode = parts[1].Trim().Trim('"');
}
}
return (close, mode);
}
catch {
return (false, "current_command");
private void ApplyCloseOthersConfig()
{
if (PyRevitConfigs.GetCloseOtherOutputs())
{
var mode = PyRevitConfigs.GetCloseOutputMode();
this.Dispatcher.BeginInvoke(new Action(() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using BeginInvoke for UI marshalling may introduce unnecessary delay. Consider whether the console closing operation needs to be asynchronous or if Invoke (synchronous) would be more appropriate for immediate feedback.

The current implementation may cause timing issues where the new console opens before others finish closing.

actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

{
CloseOtherOutputs(filterByCommandId: mode == OutputCloseMode.CurrentCommand);
}));
}
}

Expand All @@ -421,19 +398,6 @@ public void CloseOtherOutputs(bool filterByCommandId = true) {
}
}

private void ApplyCloseOtherOutputsIfConfigured() {
var (close, mode) = ReadCloseOtherOutputsSetting();
if (!close) return;

this.Dispatcher.BeginInvoke(new Action(() => {
if (string.Equals(mode, "current_command", StringComparison.InvariantCultureIgnoreCase))
CloseOtherOutputs(filterByCommandId: true);
else if (string.Equals(mode, "close_all", StringComparison.InvariantCultureIgnoreCase)) {
CloseOtherOutputs(filterByCommandId: false);
}
}));
}

private void SetupDefaultPage(string styleSheetFilePath = null) {
string cssFilePath;
if (styleSheetFilePath != null)
Expand Down Expand Up @@ -840,7 +804,7 @@ public void SelfDestructTimer(int seconds) {
private void Window_Loaded(object sender, System.EventArgs e) {
var outputWindow = (ScriptConsole)sender;
ScriptConsoleManager.AppendToOutputWindowList(this);
ApplyCloseOtherOutputsIfConfigured();
ApplyCloseOthersConfig();
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
Expand Down
45 changes: 45 additions & 0 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public enum PyRevitLogLevels
Debug
}

public enum OutputCloseMode
{
CurrentCommand,
CloseAll
}

public static class PyRevitConfigs
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
Expand Down Expand Up @@ -505,6 +511,45 @@ public static void SetLoadBetaTools(bool state)
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLoadBetaKey, state);
}

// close other outputs config
public static bool GetCloseOtherOutputs()
{
var cfg = GetConfigFile();
var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOtherOutputsKey);
return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsCloseOtherOutputsDefault;
}

public static void SetCloseOtherOutputs(bool state)
{
var cfg = GetConfigFile();
cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOtherOutputsKey, state);
}

public static OutputCloseMode GetCloseOutputMode()
{
var cfg = GetConfigFile();
var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);

var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim().Trim('"', '\'');
Copy link
Contributor Author

@MohamedAsli MohamedAsli Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dosymep is there a better way than trimming the config value ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I remember, you don't need to trim it yourself, it should do it automatically

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string trimming logic with both quotation marks seems unusual and may indicate a configuration parsing issue. The standard pyRevit configuration pattern doesn't typically require quote trimming. Consider whether this complexity is necessary or if it masks an underlying issue.

var s = (raw ?? PyRevitConsts.ConfigsCloseOutputModeDefault).Trim();
actions

Feedback: Rate this comment to help me improve future code reviews:

  • 👍 Good - Helpful and accurate
  • 👎 Poor - Wrong, unclear, or unhelpful
  • Skip if you don't have any strong opinions either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove trim, the pyRevit library trimmed values by default

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static OutputCloseMode GetCloseOutputMode()
{
    var cfg = GetConfigFile();
    var raw = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey);

    return Enum.TryParse<OutputCloseMode>(raw, true, out OutputCloseMode outCloseMode) 
        ? outCloseMode 
        : OutputCloseMode.CurrentCommand;
}


if (s.Equals(PyRevitConsts.ConfigsCloseOutputModeCloseAll, StringComparison.InvariantCultureIgnoreCase))
{
return OutputCloseMode.CloseAll;
}

return OutputCloseMode.CurrentCommand;
}

public static void SetCloseOutputMode(OutputCloseMode mode)
{
var cfg = GetConfigFile();
var value = (mode == OutputCloseMode.CloseAll)
? PyRevitConsts.ConfigsCloseOutputModeCloseAll
: PyRevitConsts.ConfigsCloseOutputModeCurrentCommand;

cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey, value);
}
Comment on lines +543 to +551
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static void SetCloseOutputMode(OutputCloseMode outputCloseMode)
{
    var cfg = GetConfigFile();
    cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCloseOutputModeKey, outputCloseMode.ToString());
}


// cpythonengine
public static int GetCpythonEngineVersion()
{
Expand Down
10 changes: 8 additions & 2 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static class PyRevitConsts {
public const string ReleaseDirName = "release";
public const string SitePackagesDirName = "site-packages";
public const string PyRevitfileFilename = "PyRevitfile";

public const string NetFxFolder = "netfx";
public const string NetCoreFolder = "netcore";

Expand Down Expand Up @@ -85,6 +85,12 @@ public static class PyRevitConsts {
public const int ConfigsMinDriveSpaceDefault = 0;
public const string ConfigsLoadBetaKey = "loadbeta";
public const bool ConfigsLoadBetaDefault = false;
public const string ConfigsCloseOtherOutputsKey = "closeotheroutputs";
public const bool ConfigsCloseOtherOutputsDefault = false;
public const string ConfigsCloseOutputModeKey = "closeoutputmode";
public const string ConfigsCloseOutputModeDefault = "currentcommand";
public const string ConfigsCloseOutputModeCurrentCommand = "currentcommand";
public const string ConfigsCloseOutputModeCloseAll = "closeall";
Comment on lines +91 to +93
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove I think

public const string ConfigsCPythonEngineKey = "cpyengine";
public const int ConfigsCPythonEngineDefault = 0;
public const string ConfigsLocaleKey = "user_locale";
Expand Down Expand Up @@ -179,7 +185,7 @@ public static class PyRevitConsts {
public static string FindConfigFileInDirectory(string sourcePath) {
var configMatcher = new Regex(ConfigsFileRegexPattern, RegexOptions.IgnoreCase);
// capture exceptions that might occur getting the files under sourcePath
//
//
try {
if (CommonUtils.VerifyPath(sourcePath))
foreach (string subFile in Directory.GetFiles(sourcePath))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,11 @@ def _setup_core_options(self):

self.minimize_consoles_cb.IsChecked = user_config.output_close_others

if user_config.output_close_mode == 'current_command':
mode = user_config.output_close_mode_enum
if mode == PyRevit.OutputCloseMode.CurrentCommand:
self.closewindows_current_rb.IsChecked = True
self.closewindows_close_all_rb.IsChecked = False
else: # 'close_all'
else:
self.closewindows_current_rb.IsChecked = False
self.closewindows_close_all_rb.IsChecked = True

Expand Down Expand Up @@ -857,9 +858,9 @@ def _save_core_options(self):

user_config.output_close_others = self.minimize_consoles_cb.IsChecked
if self.closewindows_current_rb.IsChecked:
user_config.output_close_mode = 'current_command'
user_config.output_close_mode_enum = PyRevit.OutputCloseMode.CurrentCommand
else:
user_config.output_close_mode = 'close_all'
user_config.output_close_mode_enum = PyRevit.OutputCloseMode.CloseAll

def _save_engines(self):
# set active cpython engine
Expand Down
44 changes: 30 additions & 14 deletions pyrevitlib/pyrevit/userconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,31 +305,47 @@ def load_beta(self, state):
def output_close_others(self):
"""Whether to close other output windows."""
return self.core.get_option(
'close_other_outputs',
default_value=False,
CONSTS.ConfigsCloseOtherOutputsKey,
default_value=CONSTS.ConfigsCloseOtherOutputsDefault,
)

@output_close_others.setter
def output_close_others(self, state):
self.core.set_option(
'close_other_outputs',
CONSTS.ConfigsCloseOtherOutputsKey,
value=state
)

@property
def output_close_mode(self):
"""Output window closing mode: 'current_command' or 'close_all'."""
return self.core.get_option(
'close_mode',
default_value='current_command',
def output_close_mode_enum(self):
"""Output window closing mode as enum (CurrentCommand | CloseAll)."""
value = self.core.get_option(
CONSTS.ConfigsCloseOutputModeKey,
default_value=CONSTS.ConfigsCloseOutputModeDefault,
)
if not value:
value = CONSTS.ConfigsCloseOutputModeDefault

@output_close_mode.setter
def output_close_mode(self, mode):
self.core.set_option(
'close_mode',
value=mode
)
value_lc = str(value).lower()

if value_lc == str(CONSTS.ConfigsCloseOutputModeCloseAll).lower():
return PyRevit.OutputCloseMode.CloseAll
else:
return PyRevit.OutputCloseMode.CurrentCommand

@output_close_mode_enum.setter
def output_close_mode_enum(self, mode):
"""Store string in INI, mapped from enum."""
if mode == PyRevit.OutputCloseMode.CloseAll:
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=CONSTS.ConfigsCloseOutputModeCloseAll
)
else:
self.core.set_option(
CONSTS.ConfigsCloseOutputModeKey,
value=CONSTS.ConfigsCloseOutputModeCurrentCommand
Comment on lines +336 to +347
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@property
def output_close_mode(self):
    """Output window closing mode as enum (CurrentCommand | CloseAll)."""
    value = self.core.get_option(
        CONSTS.ConfigsCloseOutputModeKey,
        default_value=CONSTS.ConfigsCloseOutputModeDefault,
    )

   if System.Enum.TryParse[PyRevit.OutputCloseMode](value, true, out PyRevit.OutputCloseMode output_close_mode):
        return output_close_mode
        
   return PyRevit.OutputCloseMode.CurrentCommand

@output_close_mode.setter
def output_close_mode(self, output_close_mode):
    """Store string in INI, mapped from enum."""
    self.core.set_option(
        CONSTS.ConfigsCloseOutputModeKey, 
        value=str(output_close_mode)
    )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed methods (remove enum suffix)

)
Comment on lines 320 to 348
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dosymep With enum, I had to deal with mapping somewhere.
Feel free to redirect me, if you see a better way to do plz

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good, I hope in the future I will rewrite all the code for working with settings, and there will be a more convenient option

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove only the enum suffix in name, I don't think users need to see it


@property
def cpython_engine_version(self):
Expand Down