Repository: microsoft/terminal (language: cpp)

Issue:
initialPosition (and others) should be available as commandline args
_initial issue description mentions "per-profile", but consider that they could be commandline arguments_

# Description of the new feature/enhancement

i want to create multiple shortcuts that launch wt at different sizes and screen positions. i'm talking about multiple instances of wt, not multiple tabs within wt.

however, the `initialPosition` setting is global and cannot be overridden on a per-profile basis. ditto for `initialRows`, `initialCols` and `launchMode`. because these are global settings, any shortcut can only launch wt at one position and size.

i'm looking for parity with CMD here. a shortcut to CMD has a bunch of settings tabs, including screen size and position. i have shortcuts to launch CMD in different places for various reasons. i'd like to use shortcuts to launch wt instead.

# Proposed technical implementation details (optional)

allow `initialPosition`, `initialRows`, `initialCols` and `launchMode` to be specified within a profile in settings.json. if that profile is the main one active at launch time, then use them instead of the globals.



Discussion / hints:
I'd prefer to let commandline arguments solve this one, honestly. I don't like the idea of profiles whose settings override application-level settings, because that opens up a pretty big can of worms.
To that end, I've renamed this. Please feel free to complain: I want to make sure I'm not shutting down valuable discussion here.
command line arguments is a fine solution. i would have no complaints.
+1 command line arguments would work for me as well
i think the strongest reason for this is that CMD has it and i depend on the feature. @DHowett-MSFT @cinnamon-msft should i be trying to write this myself and then send a PR? i can't what sort of priority this request has.
@bruceoberg cmd has a feature where you can specify the initial position by commandline argument?

As the engineering owner for cmd, I'm surprised to hear that. Do you have any more details?
Oh, I see. Per-profile. It doesn't _really_ have that: it's per shortcut file or exe, for which the equivalent is a terminal shortcut that spawns wt with arguments. We are not introducing launch settings to profiles, because they _don't make sense at that level_. Sorry.
(i got tricked by my own re-titling of your issue. Sorry about that!)
so i will never be able to create two shortcuts that launch two wt instances in two different positions? i don't need per-profile settings, i just need a command-line option to set the position and size.
I don't think anyone ever said that - DHowett just confused himself 😆 

We definitely want to add `--initialPostition`, `--initialRows`, `--initialCols` to the commandline arguments available. Then, you could use those in a shortcut to set the position and size of the Terminal. 
Any news on this subject?

How to start a new windows terminal from command line in a specific location and with specific window size?
Nope. We'll make sure to update this thread when there is. In the meantime, might I recommend the Subscribe button?
![image](https://user-images.githubusercontent.com/18356694/91237459-5cbb0c80-e700-11ea-9347-b9b1ec2813b1.png)
That way you'll be notified of any updates to this thread, without needlessly pinging everyone on this thread ☺️
Notes from #13307: Make sure there's a sentinel value or flag or something to also allow `CW_USEDEFAULT` as the position


Fix patch:
diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
index 6e24cc54485..0ac4cf51afe 100644
--- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
+++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
@@ -4,6 +4,7 @@
 #include "pch.h"
 #include "AppCommandlineArgs.h"
 #include "../types/inc/utils.hpp"
+#include "TerminalSettingsModel/ModelSerializationHelpers.h"
 #include <LibraryResources.h>
 
 using namespace winrt::Microsoft::Terminal::Settings::Model;
@@ -183,6 +184,15 @@ void AppCommandlineArgs::_buildParser()
     maximized->excludes(fullscreen);
     focus->excludes(fullscreen);
 
+    auto positionCallback = [this](std::string string) {
+        _position = LaunchPositionFromString(string);
+    };
+    _app.add_option_function<std::string>("--pos", positionCallback, RS_A(L"CmdPositionDesc"));
+    auto sizeCallback = [this](std::string string) {
+        _size = SizeFromString(string);
+    };
+    _app.add_option_function<std::string>("--size", sizeCallback, RS_A(L"CmdSizeDesc"));
+
     _app.add_option("-w,--window",
                     _windowTarget,
                     RS_A(L"CmdWindowTargetArgDesc"));
@@ -709,7 +719,7 @@ void AppCommandlineArgs::_resetStateToDefault()
     // DON'T clear _launchMode here! This will get called once for every
     // subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
     // the "global" fullscreen flag (-F).
-    // Same with _windowTarget.
+    // Same with _windowTarget, _position and _size.
 }
 
 // Function Description:
@@ -936,6 +946,16 @@ std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> AppComman
     return _launchMode;
 }
 
+std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> AppCommandlineArgs::GetPosition() const noexcept
+{
+    return _position;
+}
+
+std::optional<til::size> AppCommandlineArgs::GetSize() const noexcept
+{
+    return _size;
+}
+
 // Method Description:
 // - Attempts to parse an array of commandline args into a list of
 //   commands to execute, and then parses these commands. As commands are
diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h
index de076ec99c1..31eac78168b 100644
--- a/src/cascadia/TerminalApp/AppCommandlineArgs.h
+++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h
@@ -41,6 +41,8 @@ class TerminalApp::AppCommandlineArgs final
 
     std::optional<uint32_t> GetPersistedLayoutIdx() const noexcept;
     std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> GetLaunchMode() const noexcept;
+    std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> GetPosition() const noexcept;
+    std::optional<til::size> GetSize() const noexcept;
 
     int ParseArgs(const winrt::Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
     void DisableHelpInExitMessage();
@@ -119,6 +121,8 @@ class TerminalApp::AppCommandlineArgs final
 
     const Commandline* _currentCommandline{ nullptr };
     std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> _launchMode{ std::nullopt };
+    std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchPosition> _position{ std::nullopt };
+    std::optional<til::size> _size{ std::nullopt };
     bool _isHandoffListener{ false };
     std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs> _startupActions;
     std::string _exitMessage;
diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp
index c17b7dff7f1..a4279620833 100644
--- a/src/cascadia/TerminalApp/AppLogic.cpp
+++ b/src/cascadia/TerminalApp/AppLogic.cpp
@@ -633,12 +633,17 @@ namespace winrt::TerminalApp::implementation
             }
         }
 
-        if (proposedSize.Width == 0 && proposedSize.Height == 0)
+        if (_appArgs.GetSize().has_value() || (proposedSize.Width == 0 && proposedSize.Height == 0))
         {
             // Use the default profile to determine how big of a window we need.
             const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, nullptr, nullptr) };
 
-            proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(), dpi);
+            const til::size emptySize{};
+            const auto commandlineSize = _appArgs.GetSize().value_or(emptySize);
+            proposedSize = TermControl::GetProposedDimensions(settings.DefaultSettings(),
+                                                              dpi,
+                                                              commandlineSize.width,
+                                                              commandlineSize.height);
         }
 
         // GH#2061 - If the global setting "Always show tab bar" is
@@ -738,6 +743,12 @@ namespace winrt::TerminalApp::implementation
             }
         }
 
+        // Commandline args trump everything else
+        if (_appArgs.GetPosition().has_value())
+        {
+            initialPosition = _appArgs.GetPosition().value();
+        }
+
         return {
             initialPosition.X ? initialPosition.X.Value() : defaultInitialX,
             initialPosition.Y ? initialPosition.Y.Value() : defaultInitialY
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index 195535985d9..5f6d01f167c 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -394,6 +394,12 @@
   <data name="CmdWindowTargetArgDesc" xml:space="preserve">
     <value>Specify a terminal window to run the given commandline in. "0" always refers to the current window. </value>
   </data>
+  <data name="CmdPositionDesc" xml:space="preserve">
+    <value>Specify the position for the terminal, in "x,y" format.</value>
+  </data>
+  <data name="CmdSizeDesc" xml:space="preserve">
+    <value>Specify the number of columns and rows for the terminal, in "c,r" format.</value>
+  </data>
   <data name="NewTabSplitButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
     <value>Press the button to open a new terminal tab with your default profile. Open the flyout to select which profile you want to open.</value>
   </data>
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index a965f35ff8c..00b0b46ecef 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -2032,15 +2032,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     // - dpi: The DPI we should create the terminal at. This affects things such
     //   as font size, scrollbar and other control scaling, etc. Make sure the
     //   caller knows what monitor the control is about to appear on.
+    // - commandlineCols: Number of cols specified on the commandline
+    // - commandlineRows: Number of rows specified on the commandline
     // Return Value:
     // - a size containing the requested dimensions in pixels.
-    winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi)
+    winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const IControlSettings& settings,
+                                                                        const uint32_t dpi,
+                                                                        int32_t commandlineCols,
+                                                                        int32_t commandlineRows)
     {
         // If the settings have negative or zero row or column counts, ignore those counts.
         // (The lower TerminalCore layer also has upper bounds as well, but at this layer
         //  we may eventually impose different ones depending on how many pixels we can address.)
-        const auto cols = ::base::saturated_cast<float>(std::max(settings.InitialCols(), 1));
-        const auto rows = ::base::saturated_cast<float>(std::max(settings.InitialRows(), 1));
+        const auto cols = ::base::saturated_cast<float>(std::max(commandlineCols > 0 ?
+                                                                     commandlineCols :
+                                                                     settings.InitialCols(),
+                                                                 1));
+        const auto rows = ::base::saturated_cast<float>(std::max(commandlineRows > 0 ?
+                                                                     commandlineRows :
+                                                                     settings.InitialRows(),
+                                                                 1));
 
         const winrt::Windows::Foundation::Size initialSize{ cols, rows };
 
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index afa134dbef3..bed685ede24 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -114,7 +114,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
         const Windows::UI::Xaml::Thickness GetPadding();
 
-        static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi);
+        static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings,
+                                                               const uint32_t dpi,
+                                                               int32_t commandlineCols,
+                                                               int32_t commandlineRows);
         static Windows::Foundation::Size GetProposedDimensions(const IControlSettings& settings, const uint32_t dpi, const winrt::Windows::Foundation::Size& initialSizeInChars);
 
         void BellLightOn();
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index 29080e6b8de..a287989d19f 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -21,7 +21,10 @@ namespace Microsoft.Terminal.Control
                     IControlAppearance unfocusedAppearance,
                     Microsoft.Terminal.TerminalConnection.ITerminalConnection connection);
 
-        static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings, UInt32 dpi);
+        static Windows.Foundation.Size GetProposedDimensions(IControlSettings settings,
+                                                             UInt32 dpi,
+                                                             Int32 commandlineCols,
+                                                             Int32 commandlineRows);
 
         void UpdateControlSettings(IControlSettings settings);
         void UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance);
diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
index 04f0f38d7b8..a1aaf301ce8 100644
--- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
+++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
@@ -92,6 +92,7 @@
     <ClInclude Include="VsDevShellGenerator.h" />
     <ClInclude Include="VsSetupConfiguration.h" />
     <ClInclude Include="WslDistroGenerator.h" />
+    <ClInclude Include="ModelSerializationHelpers.h" />
   </ItemGroup>
   <!-- ========================= Cpp Files ======================== -->
   <ItemGroup>
diff --git a/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h
new file mode 100644
index 00000000000..d8c9201f662
--- /dev/null
+++ b/src/cascadia/TerminalSettingsModel/ModelSerializationHelpers.h
@@ -0,0 +1,82 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- ModelSerializationHelpers.h
+
+Abstract:
+- Helper methods for serializing/de-serializing model data.
+
+--*/
+
+#pragma once
+
+// Function Description:
+// - Helper for converting a pair of comma separated, potentially absent integer values
+//   into the corresponding left and right values. The leftValue and rightValue functions
+//   will be called back with the associated parsed integer value, assuming it's present.
+//   (100, 100): leftValue and rightValue functions both called back with 100.
+//   (100, ), (100, abc): leftValue function called back with 100
+//   (, 100), (abc, 100): rightValue function called back with 100
+//   (,): no function called back
+//   (100, 100, 100): we only read the first two values, this is equivalent to (100, 100)
+// Arguments:
+// - string: The string to parse
+// - leftValue: Function called back with the value before the comma
+// - rightValue: Function called back with the value after the comma
+_TIL_INLINEPREFIX void ParseCommaSeparatedPair(const std::string& string,
+                                               std::function<void(int32_t)> leftValue,
+                                               std::function<void(int32_t)> rightValue)
+{
+    static constexpr auto singleCharDelim = ',';
+    std::stringstream tokenStream(string);
+    std::string token;
+    uint8_t index = 0;
+
+    // Get values till we run out of delimiter separated values in the stream
+    // or we hit max number of allowable values (= 2)
+    // Non-numeral values or empty string will be caught as exception and we do not assign them
+    for (; std::getline(tokenStream, token, singleCharDelim) && (index < 2); index++)
+    {
+        try
+        {
+            int32_t value = std::stol(token);
+            if (index == 0)
+            {
+                leftValue(value);
+            }
+
+            if (index == 1)
+            {
+                rightValue(value);
+            }
+        }
+        catch (...)
+        {
+            // Do nothing
+        }
+    }
+}
+
+// See: ParseCommaSeparatedPair
+_TIL_INLINEPREFIX ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition LaunchPositionFromString(const std::string& string)
+{
+    ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition initialPosition;
+    ParseCommaSeparatedPair(
+        string,
+        [&initialPosition](int32_t left) { initialPosition.X = left; },
+        [&initialPosition](int32_t right) { initialPosition.Y = right; });
+    return initialPosition;
+}
+
+// See: ParseCommaSeparatedPair
+_TIL_INLINEPREFIX ::til::size SizeFromString(const std::string& string)
+{
+    til::size size{};
+    ParseCommaSeparatedPair(
+        string,
+        [&size](int32_t left) { size.width = left; },
+        [&size](int32_t right) { size.height = right; });
+    return size;
+}
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
index a26c0b7e73d..47c6f8ebcef 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h
@@ -17,6 +17,7 @@ Module Name:
 
 #include "JsonUtils.h"
 #include "SettingsTypes.h"
+#include "ModelSerializationHelpers.h"
 
 JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Core::CursorStyle)
 {
@@ -317,51 +318,12 @@ JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::Control::CopyFormat)
     }
 };
 
-// Type Description:
-// - Helper for converting the initial position string into
-//   2 coordinate values. We allow users to only provide one coordinate,
-//   thus, we use comma as the separator:
-//   (100, 100): standard input string
-//   (, 100), (100, ): if a value is missing, we set this value as a default
-//   (,): both x and y are set to default
-//   (abc, 100): if a value is not valid, we treat it as default
-//   (100, 100, 100): we only read the first two values, this is equivalent to (100, 100)
 template<>
 struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition>
 {
     ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition FromJson(const Json::Value& json)
     {
-        ::winrt::Microsoft::Terminal::Settings::Model::LaunchPosition ret;
-        auto initialPosition{ json.asString() };
-        static constexpr auto singleCharDelim = ',';
-        std::stringstream tokenStream(initialPosition);
-        std::string token;
-        uint8_t initialPosIndex = 0;
-
-        // Get initial position values till we run out of delimiter separated values in the stream
-        // or we hit max number of allowable values (= 2)
-        // Non-numeral values or empty string will be caught as exception and we do not assign them
-        for (; std::getline(tokenStream, token, singleCharDelim) && (initialPosIndex < 2); initialPosIndex++)
-        {
-            try
-            {
-                int64_t position = std::stol(token);
-                if (initialPosIndex == 0)
-                {
-                    ret.X = position;
-                }
-
-                if (initialPosIndex == 1)
-                {
-                    ret.Y = position;
-                }
-            }
-            catch (...)
-            {
-                // Do nothing
-            }
-        }
-        return ret;
+        return LaunchPositionFromString(json.asString());
     }
 
     bool CanConvert(const Json::Value& json)


Test patch: